1 /* $OpenBSD: shutdown.c,v 1.56 2024/04/28 16:43:42 florian Exp $ */ 2 /* $NetBSD: shutdown.c,v 1.9 1995/03/18 15:01:09 cgd Exp $ */ 3 4 /* 5 * Copyright (c) 1988, 1990, 1993 6 * The Regents of the University of California. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 #include <sys/types.h> 34 #include <sys/resource.h> 35 #include <sys/syslog.h> 36 #include <sys/wait.h> 37 38 #include <ctype.h> 39 #include <fcntl.h> 40 #include <sys/termios.h> 41 #include <pwd.h> 42 #include <signal.h> 43 #include <stdio.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #include <time.h> 47 #include <unistd.h> 48 #include <limits.h> 49 #include <errno.h> 50 #include <err.h> 51 52 #include "pathnames.h" 53 54 #ifdef DEBUG 55 #undef _PATH_NOLOGIN 56 #define _PATH_NOLOGIN "./nologin" 57 #undef _PATH_FASTBOOT 58 #define _PATH_FASTBOOT "./fastboot" 59 #endif 60 61 #define H *60*60LL 62 #define M *60LL 63 #define S *1LL 64 #define TEN_HOURS (10*60*60) 65 #define NOLOG_TIME (5*60) 66 struct interval { 67 time_t timeleft; 68 time_t timetowait; 69 } tlist[] = { 70 { 0, 0 }, 71 { 10 H, 5 H }, 72 { 5 H, 3 H }, 73 { 2 H, 1 H }, 74 { 1 H, 30 M }, 75 { 30 M, 10 M }, 76 { 20 M, 10 M }, 77 { 10 M, 5 M }, 78 { 5 M, 3 M }, 79 { 2 M, 1 M }, 80 { 1 M, 30 S }, 81 { 30 S, 30 S }, 82 { 0, 0 } 83 }; 84 const int tlistlen = sizeof(tlist) / sizeof(tlist[0]); 85 #undef H 86 #undef M 87 #undef S 88 89 static time_t offset, shuttime; 90 static int dofast, dohalt, doreboot, dopower, dodump, mbuflen, nosync; 91 static volatile sig_atomic_t killflg, timed_out; 92 static char *whom, mbuf[BUFSIZ]; 93 94 void badtime(void); 95 void __dead die_you_gravy_sucking_pig_dog(void); 96 void doitfast(void); 97 void __dead finish(int); 98 void getoffset(char *); 99 void __dead loop(void); 100 void nolog(time_t); 101 void timeout(int); 102 void timewarn(time_t); 103 void usage(void); 104 105 int 106 main(int argc, char *argv[]) 107 { 108 char when[64]; 109 char *p, *endp; 110 struct passwd *pw; 111 struct tm *lt; 112 int arglen, ch, len, readstdin = 0; 113 pid_t forkpid; 114 115 #ifndef DEBUG 116 if (geteuid()) 117 errx(1, "NOT super-user"); 118 #endif 119 while ((ch = getopt(argc, argv, "dfhknpr-")) != -1) 120 switch (ch) { 121 case '-': 122 readstdin = 1; 123 break; 124 case 'd': 125 dodump = 1; 126 break; 127 case 'f': 128 dofast = 1; 129 break; 130 case 'h': 131 dohalt = 1; 132 break; 133 case 'k': 134 killflg = 1; 135 break; 136 case 'n': 137 nosync = 1; 138 break; 139 case 'p': 140 dopower = 1; 141 break; 142 case 'r': 143 doreboot = 1; 144 break; 145 default: 146 usage(); 147 } 148 argc -= optind; 149 argv += optind; 150 151 if (argc < 1) 152 usage(); 153 154 if (dofast && nosync) { 155 warnx("incompatible switches -f and -n."); 156 usage(); 157 } 158 if (doreboot && dohalt) { 159 warnx("incompatible switches -h and -r."); 160 usage(); 161 } 162 if (doreboot && dopower) { 163 warnx("incompatible switches -p and -r."); 164 usage(); 165 } 166 167 if (unveil(_PATH_CONSOLE, "rw") == -1) 168 err(1, "unveil %s", _PATH_CONSOLE); 169 if (unveil(_PATH_RC, "r") == -1) 170 err(1, "unveil %s", _PATH_RC); 171 if (unveil(_PATH_WALL, "x") == -1) 172 err(1, "unveil %s", _PATH_WALL); 173 if (unveil(_PATH_FASTBOOT, "wc") == -1) 174 err(1, "unveil %s", _PATH_FASTBOOT); 175 if (unveil(_PATH_NOLOGIN, "wc") == -1) 176 err(1, "unveil %s", _PATH_NOLOGIN); 177 if (dohalt || dopower) { 178 if (unveil(_PATH_HALT, "x") == -1) 179 err(1, "unveil %s", _PATH_HALT); 180 } else if (doreboot) { 181 if (unveil(_PATH_REBOOT, "x") == -1) 182 err(1, "unveil %s", _PATH_REBOOT); 183 } else { 184 if (unveil(_PATH_BSHELL, "x") == -1) 185 err(1, "unveil %s", _PATH_BSHELL); 186 } 187 if (pledge("stdio rpath wpath cpath getpw tty id proc exec", NULL) == -1) 188 err(1, "pledge"); 189 190 getoffset(*argv++); 191 192 if (*argv) { 193 for (p = mbuf, len = sizeof(mbuf); *argv; ++argv) { 194 arglen = strlen(*argv); 195 if ((len -= arglen) <= 2) 196 break; 197 if (p != mbuf) 198 *p++ = ' '; 199 memcpy(p, *argv, arglen); 200 p += arglen; 201 } 202 *p = '\n'; 203 *++p = '\0'; 204 } 205 206 if (readstdin) { 207 p = mbuf; 208 endp = mbuf + sizeof(mbuf) - 2; 209 for (;;) { 210 if (!fgets(p, endp - p + 1, stdin)) 211 break; 212 for (; *p && p < endp; ++p) 213 ; 214 if (p == endp) { 215 *p = '\n'; 216 *++p = '\0'; 217 break; 218 } 219 } 220 } 221 mbuflen = strlen(mbuf); 222 223 if (offset > 0) { 224 shuttime = time(NULL) + offset; 225 lt = localtime(&shuttime); 226 if (lt != NULL) { 227 strftime(when, sizeof(when), "%a %b %e %T %Z %Y", lt); 228 printf("Shutdown at %s.\n", when); 229 } else 230 printf("Shutdown soon.\n"); 231 } else 232 printf("Shutdown NOW!\n"); 233 234 if (!(whom = getlogin())) 235 whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???"; 236 237 #ifdef DEBUG 238 (void)putc('\n', stdout); 239 #else 240 (void)setpriority(PRIO_PROCESS, 0, PRIO_MIN); 241 242 forkpid = fork(); 243 if (forkpid == -1) 244 err(1, "fork"); 245 if (forkpid) { 246 (void)printf("shutdown: [pid %ld]\n", (long)forkpid); 247 exit(0); 248 } 249 setsid(); 250 #endif 251 openlog("shutdown", LOG_CONS, LOG_AUTH); 252 loop(); 253 /* NOTREACHED */ 254 } 255 256 void 257 loop(void) 258 { 259 struct timespec timeout; 260 int broadcast, i, logged; 261 262 broadcast = 1; 263 264 for (i = 0; i < tlistlen - 1; i++) { 265 if (offset > tlist[i + 1].timeleft) { 266 tlist[i].timeleft = offset; 267 tlist[i].timetowait = offset - tlist[i + 1].timeleft; 268 break; 269 } 270 } 271 272 /* 273 * Don't spam the users: skip our offset's warning broadcast if 274 * there's a broadcast scheduled after ours and it's relatively 275 * imminent. 276 */ 277 if (offset > TEN_HOURS || 278 (offset > 0 && tlist[i].timetowait < tlist[i+1].timetowait / 5)) 279 broadcast = 0; 280 281 for (logged = 0; i < tlistlen; i++) { 282 if (broadcast) 283 timewarn(tlist[i].timeleft); 284 broadcast = 1; 285 if (!logged && tlist[i].timeleft <= NOLOG_TIME) { 286 logged = 1; 287 nolog(tlist[i].timeleft); 288 } 289 timeout.tv_sec = tlist[i].timetowait; 290 timeout.tv_nsec = 0; 291 nanosleep(&timeout, NULL); 292 } 293 die_you_gravy_sucking_pig_dog(); 294 } 295 296 static char *restricted_environ[] = { 297 "PATH=" _PATH_STDPATH, 298 NULL 299 }; 300 301 void 302 timewarn(time_t timeleft) 303 { 304 static char hostname[HOST_NAME_MAX+1]; 305 char when[64]; 306 struct tm *lt; 307 static int first; 308 int fd[2]; 309 pid_t pid, wpid; 310 311 if (!first++) 312 (void)gethostname(hostname, sizeof(hostname)); 313 314 if (pipe(fd) == -1) { 315 syslog(LOG_ERR, "pipe: %m"); 316 return; 317 } 318 switch (pid = fork()) { 319 case -1: 320 syslog(LOG_ERR, "fork: %m"); 321 close(fd[0]); 322 close(fd[1]); 323 return; 324 case 0: 325 if (dup2(fd[0], STDIN_FILENO) == -1) { 326 syslog(LOG_ERR, "dup2: %m"); 327 _exit(1); 328 } 329 if (fd[0] != STDIN_FILENO) 330 close(fd[0]); 331 close(fd[1]); 332 /* wall(1)'s undocumented '-n' flag suppresses its banner. */ 333 execle(_PATH_WALL, _PATH_WALL, "-n", (char *)NULL, 334 restricted_environ); 335 syslog(LOG_ERR, "%s: %m", _PATH_WALL); 336 _exit(1); 337 default: 338 close(fd[0]); 339 } 340 341 dprintf(fd[1], 342 "\007*** %sSystem shutdown message from %s@%s ***\007\n", 343 timeleft ? "": "FINAL ", whom, hostname); 344 345 if (timeleft > 10 * 60) { 346 shuttime = time(NULL) + timeleft; 347 lt = localtime(&shuttime); 348 if (lt != NULL) { 349 strftime(when, sizeof(when), "%H:%M %Z", lt); 350 dprintf(fd[1], "System going down at %s\n\n", when); 351 } else 352 dprintf(fd[1], "System going down in %lld minute%s\n\n", 353 (long long)(timeleft / 60), 354 (timeleft > 60) ? "s" : ""); 355 } else if (timeleft > 59) { 356 dprintf(fd[1], "System going down in %lld minute%s\n\n", 357 (long long)(timeleft / 60), (timeleft > 60) ? "s" : ""); 358 } else if (timeleft) 359 dprintf(fd[1], "System going down in 30 seconds\n\n"); 360 else 361 dprintf(fd[1], "System going down IMMEDIATELY\n\n"); 362 363 if (mbuflen) 364 (void)write(fd[1], mbuf, mbuflen); 365 close(fd[1]); 366 367 /* 368 * If we wait longer than 30 seconds for wall(1) to exit we'll 369 * throw off our schedule. 370 */ 371 signal(SIGALRM, timeout); 372 siginterrupt(SIGALRM, 1); 373 alarm(30); 374 while ((wpid = wait(NULL)) != pid && !timed_out) 375 continue; 376 alarm(0); 377 signal(SIGALRM, SIG_DFL); 378 if (timed_out) { 379 syslog(LOG_NOTICE, 380 "wall[%ld] is taking too long to exit; continuing", 381 (long)pid); 382 timed_out = 0; 383 } 384 } 385 386 void 387 timeout(int signo) 388 { 389 timed_out = 1; 390 } 391 392 void 393 die_you_gravy_sucking_pig_dog(void) 394 { 395 396 syslog(LOG_NOTICE, "%s by %s: %s", 397 doreboot ? "reboot" : dopower ? "power-down" : dohalt ? "halt" : 398 "shutdown", whom, mbuf); 399 (void)sleep(2); 400 401 (void)printf("\r\nSystem shutdown time has arrived\007\007\r\n"); 402 if (killflg) { 403 (void)printf("\rbut you'll have to do it yourself\r\n"); 404 finish(0); 405 } 406 if (dofast) 407 doitfast(); 408 409 if (pledge("stdio rpath wpath cpath tty id proc exec", NULL) == -1) 410 err(1, "pledge"); 411 412 #ifdef DEBUG 413 if (doreboot) 414 (void)printf("reboot"); 415 else if (dopower) 416 (void)printf("power-down"); 417 else if (dohalt) 418 (void)printf("halt"); 419 if (nosync) 420 (void)printf(" no sync"); 421 if (dofast) 422 (void)printf(" no fsck"); 423 if (dodump) 424 (void)printf(" with dump"); 425 (void)printf("\nkill -HUP 1\n"); 426 #else 427 if (dohalt || dopower || doreboot) { 428 char *args[10]; 429 char **arg, *path; 430 431 if (pledge("stdio exec", NULL) == -1) 432 err(1, "pledge"); 433 434 arg = &args[0]; 435 if (doreboot) { 436 path = _PATH_REBOOT; 437 *arg++ = "reboot"; 438 } else { 439 path = _PATH_HALT; 440 *arg++ = "halt"; 441 } 442 *arg++ = "-l"; 443 if (dopower) 444 *arg++ = "-p"; 445 if (nosync) 446 *arg++ = "-n"; 447 if (dodump) 448 *arg++ = "-d"; 449 *arg++ = NULL; 450 execve(path, args, NULL); 451 syslog(LOG_ERR, "shutdown: can't exec %s: %m.", path); 452 warn("%s", path); 453 } 454 if (access(_PATH_RC, R_OK) != -1) { 455 pid_t pid; 456 struct termios t; 457 int fd; 458 459 switch ((pid = fork())) { 460 case -1: 461 break; 462 case 0: 463 if (revoke(_PATH_CONSOLE) == -1) 464 perror("revoke"); 465 if (setsid() == -1) 466 perror("setsid"); 467 fd = open(_PATH_CONSOLE, O_RDWR); 468 if (fd == -1) 469 perror("open"); 470 dup2(fd, 0); 471 dup2(fd, 1); 472 dup2(fd, 2); 473 if (fd > 2) 474 close(fd); 475 476 /* At a minimum... */ 477 tcgetattr(0, &t); 478 t.c_oflag |= (ONLCR | OPOST); 479 tcsetattr(0, TCSANOW, &t); 480 481 execl(_PATH_BSHELL, "sh", _PATH_RC, "shutdown", (char *)NULL); 482 _exit(1); 483 default: 484 waitpid(pid, NULL, 0); 485 } 486 } 487 (void)kill(1, SIGTERM); /* to single user */ 488 #endif 489 finish(0); 490 } 491 492 #define ATOI2(p) (p[0] - '0') * 10 + (p[1] - '0'); p += 2; 493 494 void 495 getoffset(char *timearg) 496 { 497 char when[64]; 498 const char *errstr; 499 struct tm *lt; 500 int this_year; 501 time_t minutes, now; 502 char *p; 503 504 if (!strcasecmp(timearg, "now")) { /* now */ 505 offset = 0; 506 return; 507 } 508 509 if (timearg[0] == '+') { /* +minutes */ 510 minutes = strtonum(timearg, 0, INT_MAX, &errstr); 511 if (errstr) 512 errx(1, "relative offset is %s: %s", errstr, timearg); 513 offset = minutes * 60; 514 return; 515 } 516 517 /* handle hh:mm by getting rid of the colon */ 518 for (p = timearg; *p; ++p) { 519 if (!isascii((unsigned char)*p) || !isdigit((unsigned char)*p)) { 520 if (*p == ':' && strlen(p) == 3) { 521 p[0] = p[1]; 522 p[1] = p[2]; 523 p[2] = '\0'; 524 } else 525 badtime(); 526 } 527 } 528 529 unsetenv("TZ"); /* OUR timezone */ 530 time(&now); 531 lt = localtime(&now); /* current time val */ 532 533 if (lt == NULL) 534 badtime(); 535 536 switch (strlen(timearg)) { 537 case 10: 538 this_year = lt->tm_year; 539 lt->tm_year = ATOI2(timearg); 540 /* 541 * check if the specified year is in the next century. 542 * allow for one year of user error as many people will 543 * enter n - 1 at the start of year n. 544 */ 545 if (lt->tm_year < (this_year % 100) - 1) 546 lt->tm_year += 100; 547 /* adjust for the year 2000 and beyond */ 548 lt->tm_year += (this_year - (this_year % 100)); 549 /* FALLTHROUGH */ 550 case 8: 551 lt->tm_mon = ATOI2(timearg); 552 if (--lt->tm_mon < 0 || lt->tm_mon > 11) 553 badtime(); 554 /* FALLTHROUGH */ 555 case 6: 556 lt->tm_mday = ATOI2(timearg); 557 if (lt->tm_mday < 1 || lt->tm_mday > 31) 558 badtime(); 559 /* FALLTHROUGH */ 560 case 4: 561 lt->tm_hour = ATOI2(timearg); 562 if (lt->tm_hour < 0 || lt->tm_hour > 23) 563 badtime(); 564 lt->tm_min = ATOI2(timearg); 565 if (lt->tm_min < 0 || lt->tm_min > 59) 566 badtime(); 567 lt->tm_sec = 0; 568 if ((shuttime = mktime(lt)) == -1) 569 badtime(); 570 if ((offset = shuttime - now) < 0) { 571 strftime(when, sizeof(when), "%a %b %e %T %Z %Y", lt); 572 errx(1, "time is already past: %s", when); 573 } 574 break; 575 default: 576 badtime(); 577 } 578 } 579 580 void 581 doitfast(void) 582 { 583 int fastfd; 584 585 if ((fastfd = open(_PATH_FASTBOOT, O_WRONLY|O_CREAT|O_TRUNC, 586 0664)) >= 0) { 587 dprintf(fastfd, "fastboot file for fsck\n"); 588 close(fastfd); 589 } 590 } 591 592 void 593 nolog(time_t timeleft) 594 { 595 char when[64]; 596 struct tm *tm; 597 int logfd; 598 599 (void)unlink(_PATH_NOLOGIN); /* in case linked to another file */ 600 (void)signal(SIGINT, finish); 601 (void)signal(SIGHUP, finish); 602 (void)signal(SIGQUIT, finish); 603 (void)signal(SIGTERM, finish); 604 shuttime = time(NULL) + timeleft; 605 tm = localtime(&shuttime); 606 if ((logfd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT|O_TRUNC, 607 0664)) >= 0) { 608 if (tm) { 609 strftime(when, sizeof(when), "at %H:%M %Z", tm); 610 dprintf(logfd, 611 "\n\nNO LOGINS: System going down %s\n\n", when); 612 } else 613 dprintf(logfd, 614 "\n\nNO LOGINS: System going in %lld minute%s\n\n", 615 (long long)(timeleft / 60), 616 (timeleft > 60) ? "s" : ""); 617 close(logfd); 618 } 619 } 620 621 void 622 finish(int signo) 623 { 624 if (!killflg) 625 (void)unlink(_PATH_NOLOGIN); 626 if (signo == 0) 627 exit(0); 628 else 629 _exit(0); 630 } 631 632 void 633 badtime(void) 634 { 635 errx(1, "bad time format."); 636 } 637 638 void 639 usage(void) 640 { 641 fprintf(stderr, 642 "usage: shutdown [-] [-dfhknpr] time [warning-message ...]\n"); 643 exit(1); 644 } 645