1 /* $OpenBSD: shutdown.c,v 1.30 2003/07/30 20:56:19 avsm 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 #ifndef lint 34 static char copyright[] = 35 "@(#) Copyright (c) 1988, 1990, 1993\n\ 36 The Regents of the University of California. All rights reserved.\n"; 37 #endif /* not lint */ 38 39 #ifndef lint 40 #if 0 41 static char sccsid[] = "@(#)shutdown.c 8.2 (Berkeley) 2/16/94"; 42 #else 43 static char rcsid[] = "$OpenBSD: shutdown.c,v 1.30 2003/07/30 20:56:19 avsm Exp $"; 44 #endif 45 #endif /* not lint */ 46 47 #include <sys/param.h> 48 #include <sys/resource.h> 49 #include <sys/syslog.h> 50 #include <sys/types.h> 51 #include <sys/wait.h> 52 53 #include <ctype.h> 54 #include <fcntl.h> 55 #include <sys/termios.h> 56 #include <pwd.h> 57 #include <setjmp.h> 58 #include <signal.h> 59 #include <stdio.h> 60 #include <stdlib.h> 61 #include <string.h> 62 #include <time.h> 63 #include <tzfile.h> 64 #include <unistd.h> 65 #include <errno.h> 66 #include <err.h> 67 68 #include "pathnames.h" 69 70 #ifdef DEBUG 71 #undef _PATH_NOLOGIN 72 #define _PATH_NOLOGIN "./nologin" 73 #undef _PATH_FASTBOOT 74 #define _PATH_FASTBOOT "./fastboot" 75 #endif 76 77 #define H *60*60 78 #define M *60 79 #define S *1 80 #define NOLOG_TIME 5*60 81 struct interval { 82 int timeleft, timetowait; 83 } tlist[] = { 84 { 10 H, 5 H }, 85 { 5 H, 3 H }, 86 { 2 H, 1 H }, 87 { 1 H, 30 M }, 88 { 30 M, 10 M }, 89 { 20 M, 10 M }, 90 { 10 M, 5 M }, 91 { 5 M, 3 M }, 92 { 2 M, 1 M }, 93 { 1 M, 30 S }, 94 { 30 S, 30 S }, 95 { 0, 0 } 96 }; 97 #undef H 98 #undef M 99 #undef S 100 101 static time_t offset, shuttime; 102 static int dofast, dohalt, doreboot, dopower, dodump, killflg, mbuflen, nosync; 103 static char *whom, mbuf[BUFSIZ]; 104 105 void badtime(void); 106 void __attribute ((noreturn)) die_you_gravy_sucking_pig_dog(void); 107 void doitfast(void); 108 void __attribute ((noreturn)) finish(int); 109 void getoffset(char *); 110 void __attribute ((noreturn)) loop(void); 111 void nolog(void); 112 void timeout(int); 113 void timewarn(int); 114 void usage(void); 115 116 int 117 main(int argc, char *argv[]) 118 { 119 char *p, *endp; 120 struct passwd *pw; 121 int arglen, ch, len, readstdin; 122 123 #ifndef DEBUG 124 if (geteuid()) 125 errx(1, "NOT super-user"); 126 #endif 127 readstdin = 0; 128 while ((ch = getopt(argc, argv, "dfhknpr-")) != -1) 129 switch (ch) { 130 case '-': 131 readstdin = 1; 132 break; 133 case 'd': 134 dodump = 1; 135 break; 136 case 'f': 137 dofast = 1; 138 break; 139 case 'h': 140 dohalt = 1; 141 break; 142 case 'k': 143 killflg = 1; 144 break; 145 case 'n': 146 nosync = 1; 147 break; 148 case 'p': 149 dopower = 1; 150 break; 151 case 'r': 152 doreboot = 1; 153 break; 154 case '?': 155 default: 156 usage(); 157 } 158 argc -= optind; 159 argv += optind; 160 161 if (argc < 1) 162 usage(); 163 164 if (dofast && nosync) { 165 (void)fprintf(stderr, 166 "shutdown: incompatible switches -f and -n.\n"); 167 usage(); 168 } 169 if (doreboot && dohalt) { 170 (void)fprintf(stderr, 171 "shutdown: incompatible switches -h and -r.\n"); 172 usage(); 173 } 174 if (dopower && !dohalt) { 175 (void)fprintf(stderr, 176 "shutdown: switch -p must be used with -h.\n"); 177 usage(); 178 } 179 getoffset(*argv++); 180 181 if (*argv) { 182 for (p = mbuf, len = sizeof(mbuf); *argv; ++argv) { 183 arglen = strlen(*argv); 184 if ((len -= arglen) <= 2) 185 break; 186 if (p != mbuf) 187 *p++ = ' '; 188 memcpy(p, *argv, arglen); 189 p += arglen; 190 } 191 *p = '\n'; 192 *++p = '\0'; 193 } 194 195 if (readstdin) { 196 p = mbuf; 197 endp = mbuf + sizeof(mbuf) - 2; 198 for (;;) { 199 if (!fgets(p, endp - p + 1, stdin)) 200 break; 201 for (; *p && p < endp; ++p); 202 if (p == endp) { 203 *p = '\n'; 204 *++p = '\0'; 205 break; 206 } 207 } 208 } 209 mbuflen = strlen(mbuf); 210 211 if (offset) 212 (void)printf("Shutdown at %.24s.\n", ctime(&shuttime)); 213 else 214 (void)printf("Shutdown NOW!\n"); 215 216 if (!(whom = getlogin())) 217 whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???"; 218 219 #ifdef DEBUG 220 (void)putc('\n', stdout); 221 #else 222 (void)setpriority(PRIO_PROCESS, 0, PRIO_MIN); 223 { 224 pid_t forkpid; 225 226 forkpid = fork(); 227 if (forkpid == -1) 228 err(1, "fork"); 229 if (forkpid) { 230 (void)printf("shutdown: [pid %ld]\n", (long)forkpid); 231 exit(0); 232 } 233 } 234 setsid(); 235 #endif 236 openlog("shutdown", LOG_CONS, LOG_AUTH); 237 loop(); 238 /* NOTREACHED */ 239 } 240 241 void 242 loop(void) 243 { 244 struct interval *tp; 245 u_int sltime; 246 int logged; 247 248 if (offset <= NOLOG_TIME) { 249 logged = 1; 250 nolog(); 251 } 252 else 253 logged = 0; 254 tp = tlist; 255 if (tp->timeleft < offset) 256 (void)sleep((u_int)(offset - tp->timeleft)); 257 else { 258 while (offset < tp->timeleft) 259 ++tp; 260 /* 261 * Warn now, if going to sleep more than a fifth of 262 * the next wait time. 263 */ 264 if ((sltime = offset - tp->timeleft)) { 265 if (sltime > tp->timetowait / 5) 266 timewarn(offset); 267 (void)sleep(sltime); 268 } 269 } 270 for (;; ++tp) { 271 timewarn(tp->timeleft); 272 if (!logged && tp->timeleft <= NOLOG_TIME) { 273 logged = 1; 274 nolog(); 275 } 276 (void)sleep((u_int)tp->timetowait); 277 if (!tp->timeleft) 278 break; 279 } 280 die_you_gravy_sucking_pig_dog(); 281 } 282 283 static jmp_buf alarmbuf; 284 285 static char *restricted_environ[] = { 286 "PATH=" _PATH_STDPATH, 287 NULL 288 }; 289 290 void 291 timewarn(int timeleft) 292 { 293 static int first; 294 static char hostname[MAXHOSTNAMELEN]; 295 FILE *pf; 296 char wcmd[MAXPATHLEN + 4]; 297 extern char **environ; 298 299 if (!first++) 300 (void)gethostname(hostname, sizeof(hostname)); 301 302 /* undoc -n option to wall suppresses normal wall banner */ 303 (void)snprintf(wcmd, sizeof(wcmd), "%s -n", _PATH_WALL); 304 environ = restricted_environ; 305 if (!(pf = popen(wcmd, "w"))) { 306 syslog(LOG_ERR, "shutdown: can't find %s: %m", _PATH_WALL); 307 return; 308 } 309 310 (void)fprintf(pf, 311 "\007*** %sSystem shutdown message from %s@%s ***\007\n", 312 timeleft ? "": "FINAL ", whom, hostname); 313 314 if (timeleft > 10*60) 315 (void)fprintf(pf, "System going down at %5.5s\n\n", 316 ctime(&shuttime) + 11); 317 else if (timeleft > 59) 318 (void)fprintf(pf, "System going down in %d minute%s\n\n", 319 timeleft / 60, (timeleft > 60) ? "s" : ""); 320 else if (timeleft) 321 (void)fprintf(pf, "System going down in 30 seconds\n\n"); 322 else 323 (void)fprintf(pf, "System going down IMMEDIATELY\n\n"); 324 325 if (mbuflen) 326 (void)fwrite(mbuf, sizeof(*mbuf), mbuflen, pf); 327 328 /* 329 * play some games, just in case wall doesn't come back 330 * probably unecessary, given that wall is careful. 331 */ 332 if (!setjmp(alarmbuf)) { 333 (void)signal(SIGALRM, timeout); 334 (void)alarm((u_int)30); 335 (void)pclose(pf); 336 (void)alarm((u_int)0); 337 (void)signal(SIGALRM, SIG_DFL); 338 } 339 } 340 341 void 342 timeout(int signo) 343 { 344 longjmp(alarmbuf, 1); /* XXX signal/longjmp resource leaks */ 345 } 346 347 void 348 die_you_gravy_sucking_pig_dog(void) 349 { 350 351 syslog(LOG_NOTICE, "%s by %s: %s", 352 doreboot ? "reboot" : dohalt ? "halt" : "shutdown", whom, mbuf); 353 (void)sleep(2); 354 355 (void)printf("\r\nSystem shutdown time has arrived\007\007\r\n"); 356 if (killflg) { 357 (void)printf("\rbut you'll have to do it yourself\r\n"); 358 finish(0); 359 } 360 if (dofast) 361 doitfast(); 362 #ifdef DEBUG 363 if (doreboot) 364 (void)printf("reboot"); 365 else if (dohalt) 366 (void)printf("halt"); 367 if (nosync) 368 (void)printf(" no sync"); 369 if (dofast) 370 (void)printf(" no fsck"); 371 if (dodump) 372 (void)printf(" with dump"); 373 (void)printf("\nkill -HUP 1\n"); 374 #else 375 if (doreboot) { 376 execle(_PATH_REBOOT, "reboot", "-l", 377 (nosync ? "-n" : (dodump ? "-d" : NULL)), 378 (dodump ? "-d" : NULL), (char *)NULL, (char *)NULL); 379 syslog(LOG_ERR, "shutdown: can't exec %s: %m.", _PATH_REBOOT); 380 warn(_PATH_REBOOT); 381 } 382 else if (dohalt) { 383 execle(_PATH_HALT, "halt", "-l", 384 (dopower ? "-p" : (nosync ? "-n" : (dodump ? "-d" : NULL))), 385 (nosync ? "-n" : (dodump ? "-d" : NULL)), 386 (dodump ? "-d" : NULL), (char *)NULL, (char *)NULL); 387 syslog(LOG_ERR, "shutdown: can't exec %s: %m.", _PATH_HALT); 388 warn(_PATH_HALT); 389 } 390 if (access(_PATH_RC, R_OK) != -1) { 391 pid_t pid; 392 struct termios t; 393 int fd; 394 395 switch ((pid = fork())) { 396 case -1: 397 break; 398 case 0: 399 if (revoke(_PATH_CONSOLE) == -1) 400 perror("revoke"); 401 if (setsid() == -1) 402 perror("setsid"); 403 fd = open(_PATH_CONSOLE, O_RDWR); 404 if (fd == -1) 405 perror("open"); 406 dup2(fd, 0); 407 dup2(fd, 1); 408 dup2(fd, 2); 409 if (fd > 2) 410 close(fd); 411 412 /* At a minimum... */ 413 tcgetattr(0, &t); 414 t.c_oflag |= (ONLCR | OPOST); 415 tcsetattr(0, TCSANOW, &t); 416 417 execl(_PATH_BSHELL, "sh", _PATH_RC, "shutdown", (char *)NULL); 418 _exit(1); 419 default: 420 waitpid(pid, NULL, 0); 421 } 422 } 423 (void)kill(1, SIGTERM); /* to single user */ 424 #endif 425 finish(0); 426 } 427 428 #define ATOI2(p) (p[0] - '0') * 10 + (p[1] - '0'); p += 2; 429 430 void 431 getoffset(char *timearg) 432 { 433 struct tm *lt; 434 char *p; 435 time_t now; 436 int this_year; 437 438 if (!strcasecmp(timearg, "now")) { /* now */ 439 offset = 0; 440 return; 441 } 442 443 (void)time(&now); 444 if (*timearg == '+') { /* +minutes */ 445 if (!isdigit(*++timearg)) 446 badtime(); 447 offset = atoi(timearg) * 60; 448 shuttime = now + offset; 449 return; 450 } 451 452 /* handle hh:mm by getting rid of the colon */ 453 for (p = timearg; *p; ++p) { 454 if (!isascii(*p) || !isdigit(*p)) { 455 if (*p == ':' && strlen(p) == 3) { 456 p[0] = p[1]; 457 p[1] = p[2]; 458 p[2] = '\0'; 459 } else 460 badtime(); 461 } 462 } 463 464 unsetenv("TZ"); /* OUR timezone */ 465 lt = localtime(&now); /* current time val */ 466 467 switch(strlen(timearg)) { 468 case 10: 469 this_year = lt->tm_year; 470 lt->tm_year = ATOI2(timearg); 471 /* 472 * check if the specified year is in the next century. 473 * allow for one year of user error as many people will 474 * enter n - 1 at the start of year n. 475 */ 476 if (lt->tm_year < (this_year % 100) - 1) 477 lt->tm_year += 100; 478 /* adjust for the year 2000 and beyond */ 479 lt->tm_year += (this_year - (this_year % 100)); 480 /* FALLTHROUGH */ 481 case 8: 482 lt->tm_mon = ATOI2(timearg); 483 if (--lt->tm_mon < 0 || lt->tm_mon > 11) 484 badtime(); 485 /* FALLTHROUGH */ 486 case 6: 487 lt->tm_mday = ATOI2(timearg); 488 if (lt->tm_mday < 1 || lt->tm_mday > 31) 489 badtime(); 490 /* FALLTHROUGH */ 491 case 4: 492 lt->tm_hour = ATOI2(timearg); 493 if (lt->tm_hour < 0 || lt->tm_hour > 23) 494 badtime(); 495 lt->tm_min = ATOI2(timearg); 496 if (lt->tm_min < 0 || lt->tm_min > 59) 497 badtime(); 498 lt->tm_sec = 0; 499 if ((shuttime = mktime(lt)) == -1) 500 badtime(); 501 if ((offset = shuttime - now) < 0) 502 errx(1, "that time is already past."); 503 break; 504 default: 505 badtime(); 506 } 507 } 508 509 #define FSMSG "fastboot file for fsck\n" 510 void 511 doitfast(void) 512 { 513 int fastfd; 514 515 if ((fastfd = open(_PATH_FASTBOOT, O_WRONLY|O_CREAT|O_TRUNC, 516 0664)) >= 0) { 517 (void)write(fastfd, FSMSG, sizeof(FSMSG) - 1); 518 (void)close(fastfd); 519 } 520 } 521 522 #define NOMSG "\n\nNO LOGINS: System going down at " 523 void 524 nolog(void) 525 { 526 int logfd; 527 char *ct; 528 529 (void)unlink(_PATH_NOLOGIN); /* in case linked to another file */ 530 (void)signal(SIGINT, finish); 531 (void)signal(SIGHUP, finish); 532 (void)signal(SIGQUIT, finish); 533 (void)signal(SIGTERM, finish); 534 if ((logfd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT|O_TRUNC, 535 0664)) >= 0) { 536 (void)write(logfd, NOMSG, sizeof(NOMSG) - 1); 537 ct = ctime(&shuttime); 538 (void)write(logfd, ct + 11, 5); 539 (void)write(logfd, "\n\n", 2); 540 (void)write(logfd, mbuf, strlen(mbuf)); 541 (void)close(logfd); 542 } 543 } 544 545 void 546 finish(int signo) 547 { 548 if (!killflg) 549 (void)unlink(_PATH_NOLOGIN); 550 if (signo == 0) 551 exit(0); 552 else 553 _exit(0); 554 } 555 556 void 557 badtime(void) 558 { 559 errx(1, "bad time format."); 560 } 561 562 void 563 usage(void) 564 { 565 fprintf(stderr, "usage: shutdown [-dfhknpr] shutdowntime [ message ]\n"); 566 exit(1); 567 } 568