1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 1988, 1990, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/param.h> 33 #include <sys/boottrace.h> 34 #include <sys/resource.h> 35 #include <sys/stat.h> 36 #include <sys/syslog.h> 37 #include <sys/time.h> 38 39 #include <ctype.h> 40 #include <err.h> 41 #include <errno.h> 42 #include <fcntl.h> 43 #include <paths.h> 44 #include <pwd.h> 45 #include <setjmp.h> 46 #include <signal.h> 47 #include <stdbool.h> 48 #include <stdio.h> 49 #include <stdlib.h> 50 #include <string.h> 51 #include <unistd.h> 52 53 #ifdef DEBUG 54 #undef _PATH_NOLOGIN 55 #define _PATH_NOLOGIN "./nologin" 56 #endif 57 58 #define H *60*60 59 #define M *60 60 #define S *1 61 #define NOLOG_TIME 5*60 62 static struct interval { 63 int timeleft, timetowait; 64 } tlist[] = { 65 { 10 H, 5 H }, 66 { 5 H, 3 H }, 67 { 2 H, 1 H }, 68 { 1 H, 30 M }, 69 { 30 M, 10 M }, 70 { 20 M, 10 M }, 71 { 10 M, 5 M }, 72 { 5 M, 3 M }, 73 { 2 M, 1 M }, 74 { 1 M, 30 S }, 75 { 30 S, 30 S }, 76 { 0 , 0 } 77 }; 78 #undef H 79 #undef M 80 #undef S 81 82 static time_t offset, shuttime; 83 static int docycle, dohalt, dopower, doreboot, ign_noshutdown, 84 killflg, mbuflen, oflag; 85 static char mbuf[BUFSIZ]; 86 static const char *nosync, *whom; 87 88 static void badtime(void); 89 static void die_you_gravy_sucking_pig_dog(void); 90 static void finish(int); 91 static void getoffset(char *); 92 static void loop(bool); 93 static void nolog(void); 94 static void timeout(int); 95 static void timewarn(int); 96 static void usage(const char *); 97 98 extern const char **environ; 99 100 int 101 main(int argc, char **argv) 102 { 103 char *p, *endp; 104 struct passwd *pw; 105 struct stat st; 106 int arglen, ch, len, readstdin; 107 bool dowarn; 108 109 #ifndef DEBUG 110 if (geteuid()) 111 errx(1, "NOT super-user"); 112 #endif 113 114 dowarn = true; 115 nosync = NULL; 116 readstdin = 0; 117 118 /* 119 * Test for the special case where the utility is called as 120 * "poweroff", for which it runs 'shutdown -p now'. 121 */ 122 if ((p = strrchr(argv[0], '/')) == NULL) 123 p = argv[0]; 124 else 125 ++p; 126 if (strcmp(p, "poweroff") == 0) { 127 if (getopt(argc, argv, "") != -1) 128 usage((char *)NULL); 129 argc -= optind; 130 argv += optind; 131 if (argc != 0) 132 usage((char *)NULL); 133 dopower = 1; 134 offset = 0; 135 (void)time(&shuttime); 136 goto poweroff; 137 } 138 139 while ((ch = getopt(argc, argv, "-cfhknopqr")) != -1) 140 switch (ch) { 141 case '-': 142 readstdin = 1; 143 break; 144 case 'c': 145 docycle = 1; 146 break; 147 case 'f': 148 ign_noshutdown = 1; 149 break; 150 case 'h': 151 dohalt = 1; 152 break; 153 case 'k': 154 killflg = 1; 155 break; 156 case 'n': 157 nosync = "-n"; 158 break; 159 case 'o': 160 oflag = 1; 161 break; 162 case 'p': 163 dopower = 1; 164 break; 165 case 'q': 166 dowarn = false; 167 break; 168 case 'r': 169 doreboot = 1; 170 break; 171 case '?': 172 default: 173 usage((char *)NULL); 174 } 175 argc -= optind; 176 argv += optind; 177 178 if (argc < 1) 179 usage((char *)NULL); 180 181 if (killflg + doreboot + dohalt + dopower + docycle > 1) 182 usage("incompatible switches -c, -h, -k, -p and -r"); 183 184 if (oflag && !(dohalt || dopower || doreboot || docycle)) 185 usage("-o requires -c, -h, -p or -r"); 186 187 if (nosync != NULL && !oflag) 188 usage("-n requires -o"); 189 190 getoffset(*argv++); 191 192 poweroff: 193 if (!dowarn && *argv != NULL) 194 usage("warning-message supplied but suppressed with -q"); 195 if (*argv) { 196 for (p = mbuf, len = sizeof(mbuf); *argv; ++argv) { 197 arglen = strlen(*argv); 198 if ((len -= arglen) <= 2) 199 break; 200 if (p != mbuf) 201 *p++ = ' '; 202 memmove(p, *argv, arglen); 203 p += arglen; 204 } 205 *p = '\n'; 206 *++p = '\0'; 207 } 208 209 if (readstdin) { 210 p = mbuf; 211 endp = mbuf + sizeof(mbuf) - 2; 212 for (;;) { 213 if (!fgets(p, endp - p + 1, stdin)) 214 break; 215 for (; *p && p < endp; ++p); 216 if (p == endp) { 217 *p = '\n'; 218 *++p = '\0'; 219 break; 220 } 221 } 222 } 223 mbuflen = strlen(mbuf); 224 225 if (!ign_noshutdown && stat(_PATH_NOSHUTDOWN, &st) == 0) { 226 (void)printf("Shutdown cannot be done, " _PATH_NOSHUTDOWN 227 " is present\n"); 228 exit(2); 229 } 230 231 if (offset) { 232 BOOTTRACE("Shutdown at %s", ctime(&shuttime)); 233 (void)printf("Shutdown at %.24s.\n", ctime(&shuttime)); 234 } else { 235 BOOTTRACE("Shutdown NOW!"); 236 (void)printf("Shutdown NOW!\n"); 237 } 238 239 if (!(whom = getlogin())) 240 whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???"; 241 242 #ifdef DEBUG 243 (void)putc('\n', stdout); 244 #else 245 (void)setpriority(PRIO_PROCESS, 0, PRIO_MIN); 246 { 247 int forkpid; 248 249 forkpid = fork(); 250 if (forkpid == -1) 251 err(1, "fork"); 252 if (forkpid) 253 errx(0, "[pid %d]", forkpid); 254 } 255 setsid(); 256 #endif 257 openlog("shutdown", LOG_CONS, LOG_AUTH); 258 loop(dowarn); 259 return(0); 260 } 261 262 static void 263 loop(bool dowarn) 264 { 265 struct interval *tp; 266 u_int sltime; 267 int logged; 268 269 if (offset <= NOLOG_TIME) { 270 logged = 1; 271 nolog(); 272 } 273 else 274 logged = 0; 275 tp = tlist; 276 if (tp->timeleft < offset) 277 (void)sleep((u_int)(offset - tp->timeleft)); 278 else { 279 while (tp->timeleft && offset < tp->timeleft) 280 ++tp; 281 /* 282 * Warn now, if going to sleep more than a fifth of 283 * the next wait time. 284 */ 285 if ((sltime = offset - tp->timeleft)) { 286 if (dowarn && sltime > (u_int)(tp->timetowait / 5)) 287 timewarn(offset); 288 (void)sleep(sltime); 289 } 290 } 291 for (;; ++tp) { 292 if (dowarn) 293 timewarn(tp->timeleft); 294 if (!logged && tp->timeleft <= NOLOG_TIME) { 295 logged = 1; 296 nolog(); 297 } 298 (void)sleep((u_int)tp->timetowait); 299 if (!tp->timeleft) 300 break; 301 } 302 die_you_gravy_sucking_pig_dog(); 303 } 304 305 static jmp_buf alarmbuf; 306 307 static const char *restricted_environ[] = { 308 "PATH=" _PATH_STDPATH, 309 NULL 310 }; 311 312 static void 313 timewarn(int timeleft) 314 { 315 static int first; 316 static char hostname[MAXHOSTNAMELEN + 1]; 317 FILE *pf; 318 char wcmd[MAXPATHLEN + 4]; 319 320 if (!first++) 321 (void)gethostname(hostname, sizeof(hostname)); 322 323 /* undoc -n option to wall suppresses normal wall banner */ 324 (void)snprintf(wcmd, sizeof(wcmd), "%s -n", _PATH_WALL); 325 environ = restricted_environ; 326 if (!(pf = popen(wcmd, "w"))) { 327 syslog(LOG_ERR, "shutdown: can't find %s: %m", _PATH_WALL); 328 return; 329 } 330 331 (void)fprintf(pf, 332 "\007*** %sSystem shutdown message from %s@%s ***\007\n", 333 timeleft ? "": "FINAL ", whom, hostname); 334 335 if (timeleft > 10*60) 336 (void)fprintf(pf, "System going down at %5.5s\n\n", 337 ctime(&shuttime) + 11); 338 else if (timeleft > 59) 339 (void)fprintf(pf, "System going down in %d minute%s\n\n", 340 timeleft / 60, (timeleft > 60) ? "s" : ""); 341 else if (timeleft) 342 (void)fprintf(pf, "System going down in %s30 seconds\n\n", 343 (offset > 0 && offset < 30 ? "less than " : "")); 344 else 345 (void)fprintf(pf, "System going down IMMEDIATELY\n\n"); 346 347 if (mbuflen) 348 (void)fwrite(mbuf, sizeof(*mbuf), mbuflen, pf); 349 350 /* 351 * play some games, just in case wall doesn't come back 352 * probably unnecessary, given that wall is careful. 353 */ 354 if (!setjmp(alarmbuf)) { 355 (void)signal(SIGALRM, timeout); 356 (void)alarm((u_int)30); 357 (void)pclose(pf); 358 (void)alarm((u_int)0); 359 (void)signal(SIGALRM, SIG_DFL); 360 } 361 } 362 363 static void 364 timeout(int signo __unused) 365 { 366 longjmp(alarmbuf, 1); 367 } 368 369 static void 370 die_you_gravy_sucking_pig_dog(void) 371 { 372 char *empty_environ[] = { NULL }; 373 374 BOOTTRACE("%s by %s", 375 doreboot ? "reboot" : dohalt ? "halt" : dopower ? "power-down" : 376 docycle ? "power-cycle" : "shutdown", whom); 377 syslog(LOG_NOTICE, "%s by %s: %s", 378 doreboot ? "reboot" : dohalt ? "halt" : dopower ? "power-down" : 379 docycle ? "power-cycle" : "shutdown", whom, mbuf); 380 381 (void)printf("\r\nSystem shutdown time has arrived\007\007\r\n"); 382 if (killflg) { 383 BOOTTRACE("fake shutdown..."); 384 (void)printf("\rbut you'll have to do it yourself\r\n"); 385 exit(0); 386 } 387 #ifdef DEBUG 388 if (doreboot) 389 (void)printf("reboot"); 390 else if (docycle) 391 (void)printf("power-cycle"); 392 else if (dohalt) 393 (void)printf("halt"); 394 else if (dopower) 395 (void)printf("power-down"); 396 if (nosync != NULL) 397 (void)printf(" no sync"); 398 (void)printf("\nkill -HUP 1\n"); 399 #else 400 if (!oflag) { 401 BOOTTRACE("signal to init(8)..."); 402 (void)kill(1, doreboot ? SIGINT : /* reboot */ 403 dohalt ? SIGUSR1 : /* halt */ 404 dopower ? SIGUSR2 : /* power-down */ 405 docycle ? SIGWINCH : /* power-cycle */ 406 SIGTERM); /* single-user */ 407 } else { 408 if (doreboot) { 409 BOOTTRACE("exec reboot(8) -l..."); 410 execle(_PATH_REBOOT, "reboot", "-l", nosync, 411 (char *)NULL, empty_environ); 412 syslog(LOG_ERR, "shutdown: can't exec %s: %m.", 413 _PATH_REBOOT); 414 warn(_PATH_REBOOT); 415 } 416 else if (dohalt) { 417 BOOTTRACE("exec halt(8) -l..."); 418 execle(_PATH_HALT, "halt", "-l", nosync, 419 (char *)NULL, empty_environ); 420 syslog(LOG_ERR, "shutdown: can't exec %s: %m.", 421 _PATH_HALT); 422 warn(_PATH_HALT); 423 } 424 else if (dopower) { 425 BOOTTRACE("exec halt(8) -l -p..."); 426 execle(_PATH_HALT, "halt", "-l", "-p", nosync, 427 (char *)NULL, empty_environ); 428 syslog(LOG_ERR, "shutdown: can't exec %s: %m.", 429 _PATH_HALT); 430 warn(_PATH_HALT); 431 } 432 else if (docycle) { 433 execle(_PATH_HALT, "halt", "-l", "-c", nosync, 434 (char *)NULL, empty_environ); 435 syslog(LOG_ERR, "shutdown: can't exec %s: %m.", 436 _PATH_HALT); 437 warn(_PATH_HALT); 438 } 439 BOOTTRACE("SIGTERM to init(8)..."); 440 (void)kill(1, SIGTERM); /* to single-user */ 441 } 442 #endif 443 finish(0); 444 } 445 446 #define ATOI2(p) (p[0] - '0') * 10 + (p[1] - '0'); p += 2; 447 448 static void 449 getoffset(char *timearg) 450 { 451 struct tm *lt; 452 char *p; 453 time_t now; 454 int maybe_today, this_year; 455 char *timeunit; 456 457 (void)time(&now); 458 459 if (!strcasecmp(timearg, "now")) { /* now */ 460 offset = 0; 461 shuttime = now; 462 return; 463 } 464 465 if (*timearg == '+') { /* +minutes */ 466 if (!isdigit(*++timearg)) 467 badtime(); 468 errno = 0; 469 offset = strtol(timearg, &timeunit, 10); 470 if (offset < 0 || offset == LONG_MAX || errno != 0) 471 badtime(); 472 if (timeunit[0] == '\0' || strcasecmp(timeunit, "m") == 0 || 473 strcasecmp(timeunit, "min") == 0 || 474 strcasecmp(timeunit, "mins") == 0) { 475 offset *= 60; 476 } else if (strcasecmp(timeunit, "h") == 0 || 477 strcasecmp(timeunit, "hour") == 0 || 478 strcasecmp(timeunit, "hours") == 0) { 479 offset *= 60 * 60; 480 } else if (strcasecmp(timeunit, "s") == 0 || 481 strcasecmp(timeunit, "sec") == 0 || 482 strcasecmp(timeunit, "secs") == 0) { 483 offset *= 1; 484 } else { 485 badtime(); 486 } 487 shuttime = now + offset; 488 return; 489 } 490 491 /* handle hh:mm by getting rid of the colon */ 492 for (p = timearg; *p; ++p) 493 if (!isascii(*p) || !isdigit(*p)) { 494 if (*p == ':' && strlen(p) == 3) { 495 p[0] = p[1]; 496 p[1] = p[2]; 497 p[2] = '\0'; 498 } 499 else 500 badtime(); 501 } 502 503 unsetenv("TZ"); /* OUR timezone */ 504 lt = localtime(&now); /* current time val */ 505 maybe_today = 1; 506 507 switch(strlen(timearg)) { 508 case 10: 509 this_year = lt->tm_year; 510 lt->tm_year = ATOI2(timearg); 511 /* 512 * check if the specified year is in the next century. 513 * allow for one year of user error as many people will 514 * enter n - 1 at the start of year n. 515 */ 516 if (lt->tm_year < (this_year % 100) - 1) 517 lt->tm_year += 100; 518 /* adjust for the year 2000 and beyond */ 519 lt->tm_year += (this_year - (this_year % 100)); 520 /* FALLTHROUGH */ 521 case 8: 522 lt->tm_mon = ATOI2(timearg); 523 if (--lt->tm_mon < 0 || lt->tm_mon > 11) 524 badtime(); 525 /* FALLTHROUGH */ 526 case 6: 527 maybe_today = 0; 528 lt->tm_mday = ATOI2(timearg); 529 if (lt->tm_mday < 1 || lt->tm_mday > 31) 530 badtime(); 531 /* FALLTHROUGH */ 532 case 4: 533 lt->tm_hour = ATOI2(timearg); 534 if (lt->tm_hour < 0 || lt->tm_hour > 23) 535 badtime(); 536 lt->tm_min = ATOI2(timearg); 537 if (lt->tm_min < 0 || lt->tm_min > 59) 538 badtime(); 539 lt->tm_sec = 0; 540 if ((shuttime = mktime(lt)) == -1) 541 badtime(); 542 543 if ((offset = shuttime - now) < 0) { 544 if (!maybe_today) 545 errx(1, "that time is already past."); 546 547 /* 548 * If the user only gave a time, assume that 549 * any time earlier than the current time 550 * was intended to be that time tomorrow. 551 */ 552 lt->tm_mday++; 553 if ((shuttime = mktime(lt)) == -1) 554 badtime(); 555 if ((offset = shuttime - now) < 0) { 556 errx(1, "tomorrow is before today?"); 557 } 558 } 559 break; 560 default: 561 badtime(); 562 } 563 } 564 565 #define NOMSG "\n\nNO LOGINS: System going down at " 566 static void 567 nolog(void) 568 { 569 int logfd; 570 char *ct; 571 572 (void)unlink(_PATH_NOLOGIN); /* in case linked to another file */ 573 (void)signal(SIGINT, finish); 574 (void)signal(SIGHUP, finish); 575 (void)signal(SIGQUIT, finish); 576 (void)signal(SIGTERM, finish); 577 if ((logfd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT|O_TRUNC, 578 0664)) >= 0) { 579 (void)write(logfd, NOMSG, sizeof(NOMSG) - 1); 580 ct = ctime(&shuttime); 581 (void)write(logfd, ct + 11, 5); 582 (void)write(logfd, "\n\n", 2); 583 (void)write(logfd, mbuf, strlen(mbuf)); 584 (void)close(logfd); 585 } 586 } 587 588 static void 589 finish(int signo __unused) 590 { 591 if (!killflg) 592 (void)unlink(_PATH_NOLOGIN); 593 exit(0); 594 } 595 596 static void 597 badtime(void) 598 { 599 errx(1, "bad time format"); 600 } 601 602 static void 603 usage(const char *cp) 604 { 605 if (cp != NULL) 606 warnx("%s", cp); 607 (void)fprintf(stderr, 608 "usage: shutdown [-] [-c | -f | -h | -p | -r | -k] [-o [-n]] [-q] time [warning-message ...]\n" 609 " poweroff\n"); 610 exit(1); 611 } 612