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