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/syslog.h>
36#include <sys/time.h>
37
38#include <ctype.h>
39#include <err.h>
40#include <errno.h>
41#include <fcntl.h>
42#include <paths.h>
43#include <pwd.h>
44#include <setjmp.h>
45#include <signal.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <unistd.h>
50
51#ifdef DEBUG
52#undef _PATH_NOLOGIN
53#define	_PATH_NOLOGIN	"./nologin"
54#endif
55
56#define	H		*60*60
57#define	M		*60
58#define	S		*1
59#define	NOLOG_TIME	5*60
60static struct interval {
61	int timeleft, timetowait;
62} tlist[] = {
63	{ 10 H,  5 H },
64	{  5 H,  3 H },
65	{  2 H,  1 H },
66	{  1 H, 30 M },
67	{ 30 M, 10 M },
68	{ 20 M, 10 M },
69	{ 10 M,  5 M },
70	{  5 M,  3 M },
71	{  2 M,  1 M },
72	{  1 M, 30 S },
73	{ 30 S, 30 S },
74	{  0  ,  0   }
75};
76#undef H
77#undef M
78#undef S
79
80static time_t offset, shuttime;
81static int docycle, dohalt, dopower, doreboot, killflg, mbuflen, oflag;
82static char mbuf[BUFSIZ];
83static const char *nosync, *whom;
84
85static void badtime(void);
86static void die_you_gravy_sucking_pig_dog(void);
87static void finish(int);
88static void getoffset(char *);
89static void loop(void);
90static void nolog(void);
91static void timeout(int);
92static void timewarn(int);
93static void usage(const char *);
94
95extern const char **environ;
96
97int
98main(int argc, char **argv)
99{
100	char *p, *endp;
101	struct passwd *pw;
102	int arglen, ch, len, readstdin;
103
104#ifndef DEBUG
105	if (geteuid())
106		errx(1, "NOT super-user");
107#endif
108
109	nosync = NULL;
110	readstdin = 0;
111
112	/*
113	 * Test for the special case where the utility is called as
114	 * "poweroff", for which it runs 'shutdown -p now'.
115	 */
116	if ((p = strrchr(argv[0], '/')) == NULL)
117		p = argv[0];
118	else
119		++p;
120	if (strcmp(p, "poweroff") == 0) {
121		if (getopt(argc, argv, "") != -1)
122			usage((char *)NULL);
123		argc -= optind;
124		argv += optind;
125		if (argc != 0)
126			usage((char *)NULL);
127		dopower = 1;
128		offset = 0;
129		(void)time(&shuttime);
130		goto poweroff;
131	}
132
133	while ((ch = getopt(argc, argv, "-chknopr")) != -1)
134		switch (ch) {
135		case '-':
136			readstdin = 1;
137			break;
138		case 'c':
139			docycle = 1;
140			break;
141		case 'h':
142			dohalt = 1;
143			break;
144		case 'k':
145			killflg = 1;
146			break;
147		case 'n':
148			nosync = "-n";
149			break;
150		case 'o':
151			oflag = 1;
152			break;
153		case 'p':
154			dopower = 1;
155			break;
156		case 'r':
157			doreboot = 1;
158			break;
159		case '?':
160		default:
161			usage((char *)NULL);
162		}
163	argc -= optind;
164	argv += optind;
165
166	if (argc < 1)
167		usage((char *)NULL);
168
169	if (killflg + doreboot + dohalt + dopower + docycle > 1)
170		usage("incompatible switches -c, -h, -k, -p and -r");
171
172	if (oflag && !(dohalt || dopower || doreboot || docycle))
173		usage("-o requires -c, -h, -p or -r");
174
175	if (nosync != NULL && !oflag)
176		usage("-n requires -o");
177
178	getoffset(*argv++);
179
180poweroff:
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			memmove(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		BOOTTRACE("Shutdown at %s", ctime(&shuttime));
213		(void)printf("Shutdown at %.24s.\n", ctime(&shuttime));
214	} else {
215		BOOTTRACE("Shutdown NOW!");
216		(void)printf("Shutdown NOW!\n");
217	}
218
219	if (!(whom = getlogin()))
220		whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???";
221
222#ifdef DEBUG
223	(void)putc('\n', stdout);
224#else
225	(void)setpriority(PRIO_PROCESS, 0, PRIO_MIN);
226	{
227		int forkpid;
228
229		forkpid = fork();
230		if (forkpid == -1)
231			err(1, "fork");
232		if (forkpid)
233			errx(0, "[pid %d]", forkpid);
234	}
235	setsid();
236#endif
237	openlog("shutdown", LOG_CONS, LOG_AUTH);
238	loop();
239	return(0);
240}
241
242static void
243loop(void)
244{
245	struct interval *tp;
246	u_int sltime;
247	int logged;
248
249	if (offset <= NOLOG_TIME) {
250		logged = 1;
251		nolog();
252	}
253	else
254		logged = 0;
255	tp = tlist;
256	if (tp->timeleft < offset)
257		(void)sleep((u_int)(offset - tp->timeleft));
258	else {
259		while (tp->timeleft && offset < tp->timeleft)
260			++tp;
261		/*
262		 * Warn now, if going to sleep more than a fifth of
263		 * the next wait time.
264		 */
265		if ((sltime = offset - tp->timeleft)) {
266			if (sltime > (u_int)(tp->timetowait / 5))
267				timewarn(offset);
268			(void)sleep(sltime);
269		}
270	}
271	for (;; ++tp) {
272		timewarn(tp->timeleft);
273		if (!logged && tp->timeleft <= NOLOG_TIME) {
274			logged = 1;
275			nolog();
276		}
277		(void)sleep((u_int)tp->timetowait);
278		if (!tp->timeleft)
279			break;
280	}
281	die_you_gravy_sucking_pig_dog();
282}
283
284static jmp_buf alarmbuf;
285
286static const char *restricted_environ[] = {
287	"PATH=" _PATH_STDPATH,
288	NULL
289};
290
291static void
292timewarn(int timeleft)
293{
294	static int first;
295	static char hostname[MAXHOSTNAMELEN + 1];
296	FILE *pf;
297	char wcmd[MAXPATHLEN + 4];
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 %s30 seconds\n\n",
322		    (offset > 0 && offset < 30 ? "less than " : ""));
323	else
324		(void)fprintf(pf, "System going down IMMEDIATELY\n\n");
325
326	if (mbuflen)
327		(void)fwrite(mbuf, sizeof(*mbuf), mbuflen, pf);
328
329	/*
330	 * play some games, just in case wall doesn't come back
331	 * probably unnecessary, given that wall is careful.
332	 */
333	if (!setjmp(alarmbuf)) {
334		(void)signal(SIGALRM, timeout);
335		(void)alarm((u_int)30);
336		(void)pclose(pf);
337		(void)alarm((u_int)0);
338		(void)signal(SIGALRM, SIG_DFL);
339	}
340}
341
342static void
343timeout(int signo __unused)
344{
345	longjmp(alarmbuf, 1);
346}
347
348static void
349die_you_gravy_sucking_pig_dog(void)
350{
351	char *empty_environ[] = { NULL };
352
353	BOOTTRACE("%s by %s",
354	    doreboot ? "reboot" : dohalt ? "halt" : dopower ? "power-down" :
355	    docycle ? "power-cycle" : "shutdown", whom);
356	syslog(LOG_NOTICE, "%s by %s: %s",
357	    doreboot ? "reboot" : dohalt ? "halt" : dopower ? "power-down" :
358	    docycle ? "power-cycle" : "shutdown", whom, mbuf);
359
360	(void)printf("\r\nSystem shutdown time has arrived\007\007\r\n");
361	if (killflg) {
362		BOOTTRACE("fake shutdown...");
363		(void)printf("\rbut you'll have to do it yourself\r\n");
364		exit(0);
365	}
366#ifdef DEBUG
367	if (doreboot)
368		(void)printf("reboot");
369	else if (docycle)
370		(void)printf("power-cycle");
371	else if (dohalt)
372		(void)printf("halt");
373	else if (dopower)
374		(void)printf("power-down");
375	if (nosync != NULL)
376		(void)printf(" no sync");
377	(void)printf("\nkill -HUP 1\n");
378#else
379	if (!oflag) {
380		BOOTTRACE("signal to init(8)...");
381		(void)kill(1, doreboot ? SIGINT :	/* reboot */
382			      dohalt ? SIGUSR1 :	/* halt */
383			      dopower ? SIGUSR2 :	/* power-down */
384			      docycle ? SIGWINCH :	/* power-cycle */
385			      SIGTERM);			/* single-user */
386	} else {
387		if (doreboot) {
388			BOOTTRACE("exec reboot(8) -l...");
389			execle(_PATH_REBOOT, "reboot", "-l", nosync,
390				(char *)NULL, empty_environ);
391			syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
392				_PATH_REBOOT);
393			warn(_PATH_REBOOT);
394		}
395		else if (dohalt) {
396			BOOTTRACE("exec halt(8) -l...");
397			execle(_PATH_HALT, "halt", "-l", nosync,
398				(char *)NULL, empty_environ);
399			syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
400				_PATH_HALT);
401			warn(_PATH_HALT);
402		}
403		else if (dopower) {
404			BOOTTRACE("exec halt(8) -l -p...");
405			execle(_PATH_HALT, "halt", "-l", "-p", nosync,
406				(char *)NULL, empty_environ);
407			syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
408				_PATH_HALT);
409			warn(_PATH_HALT);
410		}
411		else if (docycle) {
412			execle(_PATH_HALT, "halt", "-l", "-c", nosync,
413				(char *)NULL, empty_environ);
414			syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
415				_PATH_HALT);
416			warn(_PATH_HALT);
417		}
418		BOOTTRACE("SIGTERM to init(8)...");
419		(void)kill(1, SIGTERM);		/* to single-user */
420	}
421#endif
422	finish(0);
423}
424
425#define	ATOI2(p)	(p[0] - '0') * 10 + (p[1] - '0'); p += 2;
426
427static void
428getoffset(char *timearg)
429{
430	struct tm *lt;
431	char *p;
432	time_t now;
433	int maybe_today, this_year;
434	char *timeunit;
435
436	(void)time(&now);
437
438	if (!strcasecmp(timearg, "now")) {		/* now */
439		offset = 0;
440		shuttime = now;
441		return;
442	}
443
444	if (*timearg == '+') {				/* +minutes */
445		if (!isdigit(*++timearg))
446			badtime();
447		errno = 0;
448		offset = strtol(timearg, &timeunit, 10);
449		if (offset < 0 || offset == LONG_MAX || errno != 0)
450			badtime();
451		if (timeunit[0] == '\0' || strcasecmp(timeunit, "m") == 0 ||
452		    strcasecmp(timeunit, "min") == 0 ||
453		    strcasecmp(timeunit, "mins") == 0) {
454			offset *= 60;
455		} else if (strcasecmp(timeunit, "h") == 0 ||
456		    strcasecmp(timeunit, "hour") == 0 ||
457		    strcasecmp(timeunit, "hours") == 0) {
458			offset *= 60 * 60;
459		} else if (strcasecmp(timeunit, "s") == 0 ||
460		    strcasecmp(timeunit, "sec") == 0 ||
461		    strcasecmp(timeunit, "secs") == 0) {
462			offset *= 1;
463		} else {
464			badtime();
465		}
466		shuttime = now + offset;
467		return;
468	}
469
470	/* handle hh:mm by getting rid of the colon */
471	for (p = timearg; *p; ++p)
472		if (!isascii(*p) || !isdigit(*p)) {
473			if (*p == ':' && strlen(p) == 3) {
474				p[0] = p[1];
475				p[1] = p[2];
476				p[2] = '\0';
477			}
478			else
479				badtime();
480		}
481
482	unsetenv("TZ");					/* OUR timezone */
483	lt = localtime(&now);				/* current time val */
484	maybe_today = 1;
485
486	switch(strlen(timearg)) {
487	case 10:
488		this_year = lt->tm_year;
489		lt->tm_year = ATOI2(timearg);
490		/*
491		 * check if the specified year is in the next century.
492		 * allow for one year of user error as many people will
493		 * enter n - 1 at the start of year n.
494		 */
495		if (lt->tm_year < (this_year % 100) - 1)
496			lt->tm_year += 100;
497		/* adjust for the year 2000 and beyond */
498		lt->tm_year += (this_year - (this_year % 100));
499		/* FALLTHROUGH */
500	case 8:
501		lt->tm_mon = ATOI2(timearg);
502		if (--lt->tm_mon < 0 || lt->tm_mon > 11)
503			badtime();
504		/* FALLTHROUGH */
505	case 6:
506		maybe_today = 0;
507		lt->tm_mday = ATOI2(timearg);
508		if (lt->tm_mday < 1 || lt->tm_mday > 31)
509			badtime();
510		/* FALLTHROUGH */
511	case 4:
512		lt->tm_hour = ATOI2(timearg);
513		if (lt->tm_hour < 0 || lt->tm_hour > 23)
514			badtime();
515		lt->tm_min = ATOI2(timearg);
516		if (lt->tm_min < 0 || lt->tm_min > 59)
517			badtime();
518		lt->tm_sec = 0;
519		if ((shuttime = mktime(lt)) == -1)
520			badtime();
521
522		if ((offset = shuttime - now) < 0) {
523			if (!maybe_today)
524				errx(1, "that time is already past.");
525
526			/*
527			 * If the user only gave a time, assume that
528			 * any time earlier than the current time
529			 * was intended to be that time tomorrow.
530			 */
531			lt->tm_mday++;
532			if ((shuttime = mktime(lt)) == -1)
533				badtime();
534			if ((offset = shuttime - now) < 0) {
535				errx(1, "tomorrow is before today?");
536			}
537		}
538		break;
539	default:
540		badtime();
541	}
542}
543
544#define	NOMSG	"\n\nNO LOGINS: System going down at "
545static void
546nolog(void)
547{
548	int logfd;
549	char *ct;
550
551	(void)unlink(_PATH_NOLOGIN);	/* in case linked to another file */
552	(void)signal(SIGINT, finish);
553	(void)signal(SIGHUP, finish);
554	(void)signal(SIGQUIT, finish);
555	(void)signal(SIGTERM, finish);
556	if ((logfd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT|O_TRUNC,
557	    0664)) >= 0) {
558		(void)write(logfd, NOMSG, sizeof(NOMSG) - 1);
559		ct = ctime(&shuttime);
560		(void)write(logfd, ct + 11, 5);
561		(void)write(logfd, "\n\n", 2);
562		(void)write(logfd, mbuf, strlen(mbuf));
563		(void)close(logfd);
564	}
565}
566
567static void
568finish(int signo __unused)
569{
570	if (!killflg)
571		(void)unlink(_PATH_NOLOGIN);
572	exit(0);
573}
574
575static void
576badtime(void)
577{
578	errx(1, "bad time format");
579}
580
581static void
582usage(const char *cp)
583{
584	if (cp != NULL)
585		warnx("%s", cp);
586	(void)fprintf(stderr,
587	    "usage: shutdown [-] [-c | -h | -p | -r | -k] [-o [-n]] time [warning-message ...]\n"
588	    "       poweroff\n");
589	exit(1);
590}
591