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