1 /* $OpenBSD: atrun.c,v 1.52 2019/10/20 13:33:30 millert Exp $ */ 2 3 /* 4 * Copyright (c) 2002-2003 Todd C. Miller <millert@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 * 18 * Sponsored in part by the Defense Advanced Research Projects 19 * Agency (DARPA) and Air Force Research Laboratory, Air Force 20 * Materiel Command, USAF, under agreement number F39502-99-1-0512. 21 */ 22 23 #include <sys/types.h> 24 #include <sys/resource.h> 25 #include <sys/stat.h> 26 #include <sys/time.h> 27 #include <sys/wait.h> 28 29 #include <bitstring.h> /* for structs.h */ 30 #include <bsd_auth.h> 31 #include <ctype.h> 32 #include <dirent.h> 33 #include <err.h> 34 #include <errno.h> 35 #include <fcntl.h> 36 #include <limits.h> 37 #include <login_cap.h> 38 #include <pwd.h> 39 #include <signal.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <syslog.h> 44 #include <time.h> 45 #include <unistd.h> 46 47 #include "config.h" 48 #include "pathnames.h" 49 #include "macros.h" 50 #include "structs.h" 51 #include "funcs.h" 52 #include "globals.h" 53 54 static void run_job(const atjob *, int, const char *); 55 56 /* 57 * Scan the at jobs dir and build up a list of jobs found. 58 */ 59 int 60 scan_atjobs(at_db **db, struct timespec *ts) 61 { 62 DIR *atdir = NULL; 63 int dfd, pending; 64 const char *errstr; 65 time_t run_time; 66 char *queue; 67 at_db *new_db, *old_db = *db; 68 atjob *job; 69 struct dirent *file; 70 struct stat sb; 71 72 dfd = open(_PATH_AT_SPOOL, O_RDONLY|O_DIRECTORY|O_CLOEXEC); 73 if (dfd == -1) { 74 syslog(LOG_ERR, "(CRON) OPEN FAILED (%s)", _PATH_AT_SPOOL); 75 return (0); 76 } 77 if (fstat(dfd, &sb) != 0) { 78 syslog(LOG_ERR, "(CRON) FSTAT FAILED (%s)", _PATH_AT_SPOOL); 79 close(dfd); 80 return (0); 81 } 82 if (old_db != NULL && timespeccmp(&old_db->mtime, &sb.st_mtim, ==)) { 83 close(dfd); 84 return (0); 85 } 86 87 if ((atdir = fdopendir(dfd)) == NULL) { 88 syslog(LOG_ERR, "(CRON) OPENDIR FAILED (%s)", _PATH_AT_SPOOL); 89 close(dfd); 90 return (0); 91 } 92 93 if ((new_db = malloc(sizeof(*new_db))) == NULL) { 94 closedir(atdir); 95 return (0); 96 } 97 new_db->mtime = sb.st_mtim; /* stash at dir mtime */ 98 TAILQ_INIT(&new_db->jobs); 99 100 pending = 0; 101 while ((file = readdir(atdir)) != NULL) { 102 if (fstatat(dfd, file->d_name, &sb, AT_SYMLINK_NOFOLLOW) != 0 || 103 !S_ISREG(sb.st_mode)) 104 continue; 105 106 /* 107 * at jobs are named as RUNTIME.QUEUE 108 * RUNTIME is the time to run in seconds since the epoch 109 * QUEUE is a letter that designates the job's queue 110 */ 111 if ((queue = strchr(file->d_name, '.')) == NULL) 112 continue; 113 *queue++ = '\0'; 114 run_time = strtonum(file->d_name, 0, LLONG_MAX, &errstr); 115 if (errstr != NULL) 116 continue; 117 if (!isalpha((unsigned char)*queue)) 118 continue; 119 120 job = malloc(sizeof(*job)); 121 if (job == NULL) { 122 while ((job = TAILQ_FIRST(&new_db->jobs))) { 123 TAILQ_REMOVE(&new_db->jobs, job, entries); 124 free(job); 125 } 126 free(new_db); 127 closedir(atdir); 128 return (0); 129 } 130 job->uid = sb.st_uid; 131 job->gid = sb.st_gid; 132 job->queue = *queue; 133 job->run_time = run_time; 134 TAILQ_INSERT_TAIL(&new_db->jobs, job, entries); 135 if (ts != NULL && run_time <= ts->tv_sec) 136 pending = 1; 137 } 138 closedir(atdir); 139 140 /* Free up old at db and install new one */ 141 if (old_db != NULL) { 142 while ((job = TAILQ_FIRST(&old_db->jobs))) { 143 TAILQ_REMOVE(&old_db->jobs, job, entries); 144 free(job); 145 } 146 free(old_db); 147 } 148 *db = new_db; 149 150 return (pending); 151 } 152 153 /* 154 * Loop through the at job database and run jobs whose time have come. 155 */ 156 void 157 atrun(at_db *db, double batch_maxload, time_t now) 158 { 159 char atfile[PATH_MAX]; 160 struct stat sb; 161 double la; 162 int dfd, len; 163 atjob *job, *tjob, *batch = NULL; 164 165 if (db == NULL) 166 return; 167 168 dfd = open(_PATH_AT_SPOOL, O_RDONLY|O_DIRECTORY|O_CLOEXEC); 169 if (dfd == -1) { 170 syslog(LOG_ERR, "(CRON) OPEN FAILED (%s)", _PATH_AT_SPOOL); 171 return; 172 } 173 174 TAILQ_FOREACH_SAFE(job, &db->jobs, entries, tjob) { 175 /* Skip jobs in the future */ 176 if (job->run_time > now) 177 continue; 178 179 len = snprintf(atfile, sizeof(atfile), "%lld.%c", 180 (long long)job->run_time, job->queue); 181 if (len < 0 || len >= sizeof(atfile)) { 182 TAILQ_REMOVE(&db->jobs, job, entries); 183 free(job); 184 continue; 185 } 186 187 if (fstatat(dfd, atfile, &sb, AT_SYMLINK_NOFOLLOW) != 0) { 188 TAILQ_REMOVE(&db->jobs, job, entries); 189 free(job); 190 continue; /* disapeared from queue */ 191 } 192 if (!S_ISREG(sb.st_mode)) { 193 syslog(LOG_WARNING, "(CRON) NOT REGULAR (%s)", 194 atfile); 195 TAILQ_REMOVE(&db->jobs, job, entries); 196 free(job); 197 continue; /* was a file, no longer is */ 198 } 199 200 /* 201 * Pending jobs have the user execute bit set. 202 */ 203 if (sb.st_mode & S_IXUSR) { 204 /* new job to run */ 205 if (isupper(job->queue)) { 206 /* we run one batch job per atrun() call */ 207 if (batch == NULL || 208 job->run_time < batch->run_time) 209 batch = job; 210 } else { 211 /* normal at job */ 212 run_job(job, dfd, atfile); 213 TAILQ_REMOVE(&db->jobs, job, entries); 214 free(job); 215 } 216 } 217 } 218 219 /* Run a single batch job if there is one pending. */ 220 if (batch != NULL 221 && (batch_maxload == 0.0 || 222 ((getloadavg(&la, 1) == 1) && la <= batch_maxload)) 223 ) { 224 len = snprintf(atfile, sizeof(atfile), "%lld.%c", 225 (long long)batch->run_time, batch->queue); 226 if (len < 0 || len >= sizeof(atfile)) 227 ; 228 else 229 run_job(batch, dfd, atfile); 230 TAILQ_REMOVE(&db->jobs, batch, entries); 231 free(job); 232 } 233 234 close(dfd); 235 } 236 237 /* 238 * Check the at job header for sanity and extract the 239 * uid, gid, mailto user and always_mail flag. 240 * 241 * The header should look like this: 242 * #!/bin/sh 243 * # atrun uid=123 gid=123 244 * # mail joeuser 0 245 */ 246 static int 247 parse_header(FILE *fp, uid_t *nuid, gid_t *ngid, char *mailto, int *always_mail) 248 { 249 char *cp, *ep, *line = NULL; 250 const char *errstr; 251 size_t size = 0; 252 int lineno = 0; 253 ssize_t len; 254 int ret = -1; 255 256 for (lineno = 1; (len = getline(&line, &size, fp)) != -1; lineno++) { 257 if (line[--len] != '\n') 258 break; 259 line[len] = '\0'; 260 261 switch (lineno) { 262 case 1: 263 if (strcmp(line, "#!/bin/sh") != 0) 264 goto done; 265 break; 266 case 2: 267 if (strncmp(line, "# atrun uid=", 12) != 0) 268 goto done; 269 270 /* Pull out uid */ 271 cp = line + 12; 272 if ((ep = strchr(cp, ' ')) == NULL) 273 goto done; 274 *ep++ = '\0'; 275 *nuid = strtonum(cp, 0, UID_MAX - 1, &errstr); 276 if (errstr != NULL) 277 goto done; 278 279 /* Pull out gid */ 280 if (strncmp(ep, "gid=", 4) != 0) 281 goto done; 282 cp = ep + 4; 283 *ngid = strtonum(cp, 0, GID_MAX - 1, &errstr); 284 if (errstr != NULL) 285 goto done; 286 break; 287 case 3: 288 /* Pull out mailto user (and always_mail flag) */ 289 if (strncmp(line, "# mail ", 7) != 0) 290 goto done; 291 for (cp = line + 7; *cp == ' '; cp++) 292 continue; 293 if (*cp == '\0') 294 goto done; 295 for (ep = cp; *ep != ' ' && *ep != '\0'; ep++) 296 continue; 297 if (*ep != ' ') 298 goto done; 299 *ep++ = '\0'; 300 if (strlcpy(mailto, cp, MAX_UNAME) >= MAX_UNAME) 301 goto done; 302 *always_mail = *ep == '1'; 303 304 /* success */ 305 ret = 0; 306 goto done; 307 default: 308 /* can't happen */ 309 goto done; 310 } 311 } 312 done: 313 free(line); 314 return ret; 315 } 316 317 /* 318 * Run the specified job contained in atfile. 319 */ 320 static void 321 run_job(const atjob *job, int dfd, const char *atfile) 322 { 323 struct stat sb; 324 struct passwd *pw; 325 login_cap_t *lc; 326 auth_session_t *as; 327 pid_t pid; 328 uid_t nuid; 329 gid_t ngid; 330 FILE *fp; 331 int waiter; 332 size_t nread; 333 char mailto[MAX_UNAME], buf[BUFSIZ]; 334 int fd, always_mail; 335 int output_pipe[2]; 336 char *nargv[2], *nenvp[1]; 337 338 /* Open the file and unlink it so we don't try running it again. */ 339 if ((fd = openat(dfd, atfile, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0)) == -1) { 340 syslog(LOG_ERR, "(CRON) CAN'T OPEN (%s)", atfile); 341 return; 342 } 343 unlinkat(dfd, atfile, 0); 344 345 /* Fork so other pending jobs don't have to wait for us to finish. */ 346 switch (fork()) { 347 case 0: 348 /* child */ 349 break; 350 case -1: 351 /* error */ 352 syslog(LOG_ERR, "(CRON) CAN'T FORK (%m)"); 353 /* FALLTHROUGH */ 354 default: 355 /* parent */ 356 close(fd); 357 return; 358 } 359 360 /* Close fds opened by the parent. */ 361 close(cronSock); 362 close(dfd); 363 364 /* 365 * We don't want the main cron daemon to wait for our children-- 366 * we will do it ourselves via waitpid(). 367 */ 368 (void) signal(SIGCHLD, SIG_DFL); 369 370 /* 371 * Verify the user still exists and their account has not expired. 372 */ 373 pw = getpwuid(job->uid); 374 if (pw == NULL) { 375 syslog(LOG_WARNING, "(CRON) ORPHANED JOB (%s)", atfile); 376 _exit(EXIT_FAILURE); 377 } 378 if (pw->pw_expire && time(NULL) >= pw->pw_expire) { 379 syslog(LOG_NOTICE, "(%s) ACCOUNT EXPIRED, JOB ABORTED (%s)", 380 pw->pw_name, atfile); 381 _exit(EXIT_FAILURE); 382 } 383 384 /* Sanity checks */ 385 if (fstat(fd, &sb) == -1) { 386 syslog(LOG_ERR, "(%s) FSTAT FAILED (%s)", pw->pw_name, atfile); 387 _exit(EXIT_FAILURE); 388 } 389 if (!S_ISREG(sb.st_mode)) { 390 syslog(LOG_WARNING, "(%s) NOT REGULAR (%s)", pw->pw_name, 391 atfile); 392 _exit(EXIT_FAILURE); 393 } 394 if ((sb.st_mode & ALLPERMS) != (S_IRUSR | S_IWUSR | S_IXUSR)) { 395 syslog(LOG_WARNING, "(%s) BAD FILE MODE (%s)", pw->pw_name, 396 atfile); 397 _exit(EXIT_FAILURE); 398 } 399 if (sb.st_uid != 0 && sb.st_uid != job->uid) { 400 syslog(LOG_WARNING, "(%s) WRONG FILE OWNER (%s)", pw->pw_name, 401 atfile); 402 _exit(EXIT_FAILURE); 403 } 404 if (sb.st_gid != cron_gid) { 405 syslog(LOG_WARNING, "(%s) WRONG FILE GROUP (%s)", pw->pw_name, 406 atfile); 407 _exit(EXIT_FAILURE); 408 } 409 if (sb.st_nlink > 1) { 410 syslog(LOG_WARNING, "(%s) BAD LINK COUNT (%s)", pw->pw_name, 411 atfile); 412 _exit(EXIT_FAILURE); 413 } 414 if ((fp = fdopen(dup(fd), "r")) == NULL) { 415 syslog(LOG_ERR, "(CRON) DUP FAILED (%m)"); 416 _exit(EXIT_FAILURE); 417 } 418 if (parse_header(fp, &nuid, &ngid, mailto, &always_mail) == -1) { 419 syslog(LOG_ERR, "(%s) BAD FILE FORMAT (%s)", pw->pw_name, 420 atfile); 421 _exit(EXIT_FAILURE); 422 } 423 (void)fclose(fp); 424 if (!safe_p(pw->pw_name, mailto)) 425 _exit(EXIT_FAILURE); 426 if ((uid_t)nuid != job->uid) { 427 syslog(LOG_WARNING, "(%s) UID MISMATCH (%s)", pw->pw_name, 428 atfile); 429 _exit(EXIT_FAILURE); 430 } 431 if ((gid_t)ngid != job->gid) { 432 syslog(LOG_WARNING, "(%s) GID MISMATCH (%s)", pw->pw_name, 433 atfile); 434 _exit(EXIT_FAILURE); 435 } 436 437 /* mark ourselves as different to PS command watchers */ 438 setproctitle("atrun %s", atfile); 439 440 if (pipe(output_pipe) != 0) { /* child's stdout/stderr */ 441 syslog(LOG_ERR, "(CRON) PIPE (%m)"); 442 _exit(EXIT_FAILURE); 443 } 444 445 /* Fork again, child will run the job, parent will catch output. */ 446 switch ((pid = fork())) { 447 case -1: 448 syslog(LOG_ERR, "(CRON) CAN'T FORK (%m)"); 449 _exit(EXIT_FAILURE); 450 /*NOTREACHED*/ 451 case 0: 452 /* Write log message now that we have our real pid. */ 453 syslog(LOG_INFO, "(%s) ATJOB (%s)", pw->pw_name, atfile); 454 455 /* Connect grandchild's stdin to the at job file. */ 456 if (lseek(fd, 0, SEEK_SET) == -1) { 457 syslog(LOG_ERR, "(CRON) LSEEK (%m)"); 458 _exit(EXIT_FAILURE); 459 } 460 if (fd != STDIN_FILENO) { 461 dup2(fd, STDIN_FILENO); 462 close(fd); 463 } 464 465 /* Connect stdout/stderr to the pipe from our parent. */ 466 if (output_pipe[WRITE_PIPE] != STDOUT_FILENO) { 467 dup2(output_pipe[WRITE_PIPE], STDOUT_FILENO); 468 close(output_pipe[WRITE_PIPE]); 469 } 470 dup2(STDOUT_FILENO, STDERR_FILENO); 471 close(output_pipe[READ_PIPE]); 472 473 (void) setsid(); 474 475 /* 476 * From this point on, anything written to stderr will be 477 * mailed to the user as output. 478 */ 479 480 /* Setup execution environment as per login.conf */ 481 if ((lc = login_getclass(pw->pw_class)) == NULL) { 482 warnx("unable to get login class for %s", 483 pw->pw_name); 484 syslog(LOG_ERR, "(CRON) CAN'T GET LOGIN CLASS (%s)", 485 pw->pw_name); 486 _exit(EXIT_FAILURE); 487 488 } 489 if (setusercontext(lc, pw, pw->pw_uid, LOGIN_SETALL)) { 490 warn("setusercontext failed for %s", pw->pw_name); 491 syslog(LOG_ERR, "(%s) SETUSERCONTEXT FAILED (%m)", 492 pw->pw_name); 493 _exit(EXIT_FAILURE); 494 } 495 496 /* Run any approval scripts. */ 497 as = auth_open(); 498 if (as == NULL || auth_setpwd(as, pw) != 0) { 499 warn("auth_setpwd"); 500 syslog(LOG_ERR, "(%s) AUTH_SETPWD FAILED (%m)", 501 pw->pw_name); 502 _exit(EXIT_FAILURE); 503 } 504 if (auth_approval(as, lc, pw->pw_name, "cron") <= 0) { 505 warnx("approval failed for %s", pw->pw_name); 506 syslog(LOG_ERR, "(%s) APPROVAL FAILED (cron)", 507 pw->pw_name); 508 _exit(EXIT_FAILURE); 509 } 510 auth_close(as); 511 login_close(lc); 512 513 /* If this is a low priority job, nice ourself. */ 514 if (job->queue > 'b') { 515 if (setpriority(PRIO_PROCESS, 0, job->queue - 'b') != 0) 516 syslog(LOG_ERR, "(%s) CAN'T NICE (%m)", 517 pw->pw_name); 518 } 519 520 (void) signal(SIGPIPE, SIG_DFL); 521 522 /* 523 * Exec /bin/sh with stdin connected to the at job file 524 * and stdout/stderr hooked up to our parent. 525 * The at file will set the environment up for us. 526 */ 527 nargv[0] = "sh"; 528 nargv[1] = NULL; 529 nenvp[0] = NULL; 530 if (execve(_PATH_BSHELL, nargv, nenvp) != 0) { 531 warn("unable to execute %s", _PATH_BSHELL); 532 syslog(LOG_ERR, "(%s) CAN'T EXEC (%s: %m)", pw->pw_name, 533 _PATH_BSHELL); 534 _exit(EXIT_FAILURE); 535 } 536 break; 537 default: 538 /* parent */ 539 break; 540 } 541 542 /* Close the atfile's fd and the end of the pipe we don't use. */ 543 close(fd); 544 close(output_pipe[WRITE_PIPE]); 545 546 /* Read piped output (if any) from the at job. */ 547 if ((fp = fdopen(output_pipe[READ_PIPE], "r")) == NULL) { 548 syslog(LOG_ERR, "(%s) FDOPEN (%m)", pw->pw_name); 549 (void) _exit(EXIT_FAILURE); 550 } 551 nread = fread(buf, 1, sizeof(buf), fp); 552 if (nread != 0 || always_mail) { 553 FILE *mail; 554 pid_t mailpid; 555 size_t bytes = 0; 556 int status = 0; 557 char mailcmd[MAX_COMMAND]; 558 char hostname[HOST_NAME_MAX + 1]; 559 560 if (gethostname(hostname, sizeof(hostname)) != 0) 561 strlcpy(hostname, "unknown", sizeof(hostname)); 562 if (snprintf(mailcmd, sizeof mailcmd, MAILFMT, 563 MAILARG) >= sizeof mailcmd) { 564 syslog(LOG_ERR, "(%s) ERROR (mailcmd too long)", 565 pw->pw_name); 566 (void) _exit(EXIT_FAILURE); 567 } 568 if (!(mail = cron_popen(mailcmd, "w", pw, &mailpid))) { 569 syslog(LOG_ERR, "(%s) POPEN (%s)", pw->pw_name, mailcmd); 570 (void) _exit(EXIT_FAILURE); 571 } 572 fprintf(mail, "From: %s (Atrun Service)\n", pw->pw_name); 573 fprintf(mail, "To: %s\n", mailto); 574 fprintf(mail, "Subject: Output from \"at\" job\n"); 575 fprintf(mail, "Auto-Submitted: auto-generated\n"); 576 fprintf(mail, "\nYour \"at\" job on %s\n\"%s/%s\"\n", 577 hostname, _PATH_AT_SPOOL, atfile); 578 fprintf(mail, "\nproduced the following output:\n\n"); 579 580 /* Pipe the job's output to sendmail. */ 581 do { 582 bytes += nread; 583 fwrite(buf, nread, 1, mail); 584 } while ((nread = fread(buf, 1, sizeof(buf), fp)) != 0); 585 586 /* 587 * If the mailer exits with non-zero exit status, log 588 * this fact so the problem can (hopefully) be debugged. 589 */ 590 if ((status = cron_pclose(mail, mailpid)) != 0) { 591 syslog(LOG_NOTICE, "(%s) MAIL (mailed %zu byte%s of " 592 "output but got status 0x%04x)", pw->pw_name, 593 bytes, (bytes == 1) ? "" : "s", status); 594 } 595 } 596 597 fclose(fp); /* also closes output_pipe[READ_PIPE] */ 598 599 /* Wait for grandchild to die. */ 600 for (;;) { 601 if (waitpid(pid, &waiter, 0) == -1) { 602 if (errno == EINTR) 603 continue; 604 break; 605 } else { 606 /* 607 if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) 608 Debug(DPROC, (", dumped core")) 609 */ 610 break; 611 } 612 } 613 _exit(EXIT_SUCCESS); 614 } 615