1/*
2 * Copyright (c) 1997-2006 Erez Zadok
3 * Copyright (c) 1990 Jan-Simon Pendry
4 * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
5 * Copyright (c) 1990 The Regents of the University of California.
6 * All rights reserved.
7 *
8 * This code is derived from software contributed to Berkeley by
9 * Jan-Simon Pendry at Imperial College, London.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 *    must display the following acknowledgment:
21 *      This product includes software developed by the University of
22 *      California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 *    may be used to endorse or promote products derived from this software
25 *    without specific prior written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
38 *
39 *
40 * File: am-utils/amd/info_exec.c
41 *
42 */
43
44/*
45 * Get info from executable map
46 *
47 * Original from Erik Kline, 2004.
48 */
49
50#ifdef HAVE_CONFIG_H
51# include <config.h>
52#endif /* HAVE_CONFIG_H */
53#include <am_defs.h>
54#include <amd.h>
55
56#define	MAX_LINE_LEN		1500
57
58/* forward declarations */
59int exec_init(mnt_map *m, char *map, time_t *tp);
60int exec_search(mnt_map *m, char *map, char *key, char **pval, time_t *tp);
61
62
63/*
64 * a timed fgets()
65 */
66static char *
67fgets_timed(char *s, int size, int rdfd, int secs)
68{
69  fd_set fds;
70  struct timeval timeo;
71  time_t start, now;
72  int rval=0, i=0;
73
74  if (!s || size < 0 || rdfd < 0)
75    return 0;
76
77  s[0] = 0;
78  if (size == 0)
79    return s;
80
81  start = clocktime(NULL);
82  while (s[i] != '\n'  &&  i < size-1) {
83    s[i+1] = 0; /* places the requisite trailing '\0' */
84
85    /* ready for reading */
86    rval = read(rdfd, (void *)(s+i), 1);
87    if (rval == 1) {
88      if (s[i] == 0) {
89        rval = 0;
90        break;
91      }
92      i++;
93      continue;
94    } else if (rval == 0) {
95      break;
96    } else if (rval < 0  &&  errno != EAGAIN  &&  errno != EINTR) {
97      plog(XLOG_WARNING, "fgets_timed read error: %m");
98      break;
99    }
100
101    timeo.tv_usec = 0;
102    now = clocktime(NULL) - start;
103    if (secs <= 0)
104      timeo.tv_sec = 0;
105    else if (now < secs)
106      timeo.tv_sec = secs - now;
107    else {
108      /* timed out (now>=secs) */
109      plog(XLOG_WARNING, "executable map read timed out (> %d secs)", secs);
110      rval = -1;
111      break;
112    }
113
114    FD_ZERO(&fds);
115    FD_SET(rdfd, &fds);
116
117    rval = select(rdfd+1, &fds, 0, 0, &timeo);
118    if (rval < 0) {
119      /* error selecting */
120      plog(XLOG_WARNING, "fgets_timed select error: %m");
121      if (errno == EINTR)
122        continue;
123      rval = -1;
124      break;
125    } else if (rval == 0) {
126      /* timed out */
127      plog(XLOG_WARNING, "executable map read timed out (> %d secs)", secs);
128      rval = -1;
129      break;
130    }
131  }
132
133  if (rval > 0)
134    return s;
135
136  close(rdfd);
137  return (rval == 0 ? s : 0);
138}
139
140
141static int
142read_line(char *buf, int size, int fd)
143{
144  int done = 0;
145
146  while (fgets_timed(buf, size, fd, gopt.exec_map_timeout)) {
147    int len = strlen(buf);
148    done += len;
149    if (len > 1  &&  buf[len - 2] == '\\' &&
150        buf[len - 1] == '\n') {
151      buf += len - 2;
152      size -= len - 2;
153      *buf = '\n';
154      buf[1] = '\0';
155    } else {
156      return done;
157    }
158  }
159
160  return done;
161}
162
163
164/*
165 * Try to locate a value in a query answer
166 */
167static int
168exec_parse_qanswer(int fd, char *map, char *key, char **pval, time_t *tp)
169{
170  char qanswer[MAX_LINE_LEN], *dc = 0;
171  int chuck = 0;
172  int line_no = 0;
173
174  while (read_line(qanswer, sizeof(qanswer), fd)) {
175    char *cp;
176    char *hash;
177    int len = strlen(qanswer);
178    line_no++;
179
180    /*
181     * Make sure we got the whole line
182     */
183    if (qanswer[len - 1] != '\n') {
184      plog(XLOG_WARNING, "line %d in \"%s\" is too long", line_no, map);
185      chuck = 1;
186    } else {
187      qanswer[len - 1] = '\0';
188    }
189
190    /*
191     * Strip comments
192     */
193    hash = strchr(qanswer, '#');
194    if (hash)
195      *hash = '\0';
196
197    /*
198     * Find beginning of value (query answer)
199     */
200    for (cp = qanswer; *cp && !isascii((int)*cp) && !isspace((int)*cp); cp++)
201      ;;
202
203    /* Ignore blank lines */
204    if (!*cp)
205      goto again;
206
207    /*
208     * Return a copy of the data
209     */
210    dc = strdup(cp);
211    *pval = dc;
212    dlog("%s returns %s", key, dc);
213
214    close(fd);
215    return 0;
216
217  again:
218    /*
219     * If the last read didn't get a whole line then
220     * throw away the remainder before continuing...
221     */
222    if (chuck) {
223      while (fgets_timed(qanswer, sizeof(qanswer), fd, gopt.exec_map_timeout) &&
224	     !strchr(qanswer, '\n')) ;
225      chuck = 0;
226    }
227  }
228
229  return ENOENT;
230}
231
232
233static int
234set_nonblock(int fd)
235{
236  int val;
237
238  if (fd < 0)
239     return 0;
240
241  if ((val = fcntl(fd, F_GETFL, 0)) < 0) {
242    plog(XLOG_WARNING, "set_nonblock fcntl F_GETFL error: %m");
243    return 0;
244  }
245
246  val |= O_NONBLOCK;
247  if (fcntl(fd, F_SETFL, val) < 0) {
248    plog(XLOG_WARNING, "set_nonblock fcntl F_SETFL error: %m");
249    return 0;
250  }
251
252  return 1;
253}
254
255
256static int
257exec_map_open(char *emap, char *key)
258{
259  pid_t p1, p2;
260  int pdes[2], nullfd, i;
261  char *argv[3];
262
263  if (!emap)
264    return 0;
265
266  argv[0] = emap;
267  argv[1] = key;
268  argv[2] = NULL;
269
270  if ((nullfd = open("/dev/null", O_WRONLY|O_NOCTTY)) < 0)
271    return -1;
272
273  if (pipe(pdes) < 0) {
274    close(nullfd);
275    return -1;
276  }
277
278  switch ((p1 = vfork())) {
279  case -1:
280    /* parent: fork error */
281    close(nullfd);
282    close(pdes[0]);
283    close(pdes[1]);
284    return -1;
285  case 0:
286    /* child #1 */
287    p2 = vfork();
288    switch (p2) {
289    case -1:
290      /* child #1: fork error */
291      exit(errno);
292    case 0:
293      /* child #2: init will reap our status */
294      if (pdes[1] != STDOUT_FILENO) {
295	dup2(pdes[1], STDOUT_FILENO);
296	close(pdes[1]);
297      }
298
299      if (nullfd != STDERR_FILENO) {
300	dup2(nullfd, STDERR_FILENO);
301	close(nullfd);
302      }
303
304      for (i=0; i<FD_SETSIZE; i++)
305	if (i != STDOUT_FILENO  &&  i != STDERR_FILENO)
306	  close(i);
307
308      /* make the write descriptor non-blocking */
309      if (!set_nonblock(STDOUT_FILENO)) {
310	close(STDOUT_FILENO);
311	exit(-1);
312      }
313
314      execve(emap, argv, NULL);
315      exit(errno);		/* in case execve failed */
316    }
317
318    /* child #1 */
319    exit(0);
320  }
321
322  /* parent */
323  close(nullfd);
324  close(pdes[1]);
325
326  /* anti-zombie insurance */
327  while (waitpid(p1,0,0) < 0)
328    if (errno != EINTR)
329      exit(errno);
330
331  /* make the read descriptor non-blocking */
332  if (!set_nonblock(pdes[0])) {
333    close(pdes[0]);
334    return -1;
335  }
336
337  return pdes[0];
338}
339
340
341/*
342 * Check for various permissions on executable map without trying to
343 * fork a new executable-map process.
344 *
345 * return: >0 (errno) if failed
346 *          0 if ok
347 */
348static int
349exec_check_perm(char *map)
350{
351  struct stat sb;
352
353  /* sanity and permission checks */
354  if (!map) {
355    dlog("exec_check_permission got a NULL map");
356    return EINVAL;
357  }
358  if (stat(map, &sb)) {
359    plog(XLOG_ERROR, "map \"%s\" stat failure: %m", map);
360    return errno;
361  }
362  if (!S_ISREG(sb.st_mode)) {
363    plog(XLOG_ERROR, "map \"%s\" should be regular file", map);
364    return EINVAL;
365  }
366  if (sb.st_uid != 0) {
367    plog(XLOG_ERROR, "map \"%s\" owned by uid %u (must be 0)", map, (u_int) sb.st_uid);
368    return EACCES;
369  }
370  if (!(sb.st_mode & S_IXUSR)) {
371    plog(XLOG_ERROR, "map \"%s\" should be executable", map);
372    return EACCES;
373  }
374  if (sb.st_mode & (S_ISUID|S_ISGID)) {
375    plog(XLOG_ERROR, "map \"%s\" should not be setuid/setgid", map);
376    return EACCES;
377  }
378  if (sb.st_mode & S_IWOTH) {
379    plog(XLOG_ERROR, "map \"%s\" should not be world writeable", map);
380    return EACCES;
381  }
382
383  return 0;			/* all is well */
384}
385
386
387int
388exec_init(mnt_map *m, char *map, time_t *tp)
389{
390  /*
391   * Basically just test that the executable map can be found
392   * and has proper permissions.
393   */
394  return exec_check_perm(map);
395}
396
397
398int
399exec_search(mnt_map *m, char *map, char *key, char **pval, time_t *tp)
400{
401  int mapfd, ret;
402
403  if ((ret = exec_check_perm(map)) != 0) {
404    return ret;
405  }
406
407  if (!key)
408    return 0;
409
410  if (logfp)
411    fflush(logfp);
412  dlog("exec_search \"%s\", key: \"%s\"", map, key);
413  mapfd = exec_map_open(map, key);
414
415  if (mapfd >= 0) {
416    if (tp)
417      *tp = clocktime(NULL);
418
419    return exec_parse_qanswer(mapfd, map, key, pval, tp);
420  }
421
422  return errno;
423}
424