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