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
main(int argc,char * argv[])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
loop(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
timewarn(time_t timeleft)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
timeout(int signo)387 timeout(int signo)
388 {
389 timed_out = 1;
390 }
391
392 void
die_you_gravy_sucking_pig_dog(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
getoffset(char * timearg)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
doitfast(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
nolog(time_t timeleft)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
finish(int signo)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
badtime(void)633 badtime(void)
634 {
635 errx(1, "bad time format.");
636 }
637
638 void
usage(void)639 usage(void)
640 {
641 fprintf(stderr,
642 "usage: shutdown [-] [-dfhknpr] time [warning-message ...]\n");
643 exit(1);
644 }
645