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