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
main(int argc,char ** argv)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
loop(bool dowarn)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
timewarn(int timeleft)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
timeout(int signo __unused)364 timeout(int signo __unused)
365 {
366 longjmp(alarmbuf, 1);
367 }
368
369 static void
die_you_gravy_sucking_pig_dog(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
getoffset(char * timearg)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
nolog(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
finish(int signo __unused)589 finish(int signo __unused)
590 {
591 if (!killflg)
592 (void)unlink(_PATH_NOLOGIN);
593 exit(0);
594 }
595
596 static void
badtime(void)597 badtime(void)
598 {
599 errx(1, "bad time format");
600 }
601
602 static void
usage(const char * cp)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