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