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