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