newsyslog.c revision 111773
1/*
2 * This file contains changes from the Open Software Foundation.
3 */
4
5/*
6 * Copyright 1988, 1989 by the Massachusetts Institute of Technology
7 *
8 * Permission to use, copy, modify, and distribute this software and its
9 * documentation for any purpose and without fee is hereby granted, provided
10 * that the above copyright notice appear in all copies and that both that
11 * copyright notice and this permission notice appear in supporting
12 * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
13 * used in advertising or publicity pertaining to distribution of the
14 * software without specific, written prior permission. M.I.T. and the M.I.T.
15 * S.I.P.B. make no representations about the suitability of this software
16 * for any purpose.  It is provided "as is" without express or implied
17 * warranty.
18 *
19 */
20
21/*
22 * newsyslog - roll over selected logs at the appropriate time, keeping the a
23 * specified number of backup files around.
24 */
25
26#ifndef lint
27static const char rcsid[] =
28"$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 111773 2003-03-03 00:01:51Z gad $";
29#endif	/* not lint */
30
31#define OSF
32#ifndef COMPRESS_POSTFIX
33#define COMPRESS_POSTFIX ".gz"
34#endif
35#ifndef	BZCOMPRESS_POSTFIX
36#define	BZCOMPRESS_POSTFIX ".bz2"
37#endif
38
39#include <sys/param.h>
40#include <sys/stat.h>
41#include <sys/wait.h>
42
43#include <ctype.h>
44#include <err.h>
45#include <errno.h>
46#include <fcntl.h>
47#include <fnmatch.h>
48#include <glob.h>
49#include <grp.h>
50#include <paths.h>
51#include <pwd.h>
52#include <signal.h>
53#include <stdio.h>
54#include <stdlib.h>
55#include <string.h>
56#include <time.h>
57#include <unistd.h>
58
59#include "pathnames.h"
60
61#define kbytes(size)  (((size) + 1023) >> 10)
62
63#ifdef _IBMR2
64/* Calculates (db * DEV_BSIZE) */
65#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
66#endif
67
68/*
69 * Bit-values for the 'flags' parsed from a config-file entry.
70 */
71#define CE_COMPACT	0x0001	/* Compact the achived log files with gzip. */
72#define CE_BZCOMPACT	0x0002	/* Compact the achived log files with bzip2. */
73#define	CE_COMPACTWAIT	0x0004	/* wait until compressing one file finishes */
74				/*    before starting the next step. */
75#define CE_BINARY	0x0008	/* Logfile is in binary, do not add status */
76				/*    messages to logfile(s) when rotating. */
77#define	CE_NOSIGNAL	0x0010	/* There is no process to signal when */
78				/*    trimming this file. */
79#define	CE_TRIMAT	0x0020	/* trim file at a specific time. */
80#define	CE_GLOB		0x0040	/* name of the log is file name pattern. */
81
82#define NONE -1
83
84struct conf_entry {
85	char *log;		/* Name of the log */
86	char *pid_file;		/* PID file */
87	char *r_reason;		/* The reason this file is being rotated */
88	int rotate;		/* Non-zero if this file should be rotated */
89	int uid;		/* Owner of log */
90	int gid;		/* Group of log */
91	int numlogs;		/* Number of logs to keep */
92	int size;		/* Size cutoff to trigger trimming the log */
93	int hours;		/* Hours between log trimming */
94	time_t trim_at;		/* Specific time to do trimming */
95	int permissions;	/* File permissions on the log */
96	int flags;		/* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */
97	int sig;		/* Signal to send */
98	int def_cfg;		/* Using the <default> rule for this file */
99	struct conf_entry *next;/* Linked list pointer */
100};
101
102#define DEFAULT_MARKER "<default>"
103
104int archtodir = 0;		/* Archive old logfiles to other directory */
105int verbose = 0;		/* Print out what's going on */
106int needroot = 1;		/* Root privs are necessary */
107int noaction = 0;		/* Don't do anything, just show it */
108int nosignal;			/* Do not send any signals */
109int force = 0;			/* Force the trim no matter what */
110int rotatereq = 0;		/* -R = Always rotate the file(s) as given */
111				/*    on the command (this also requires   */
112				/*    that a list of files *are* given on  */
113				/*    the run command). */
114char *requestor;		/* The name given on a -R request */
115char *archdirname;		/* Directory path to old logfiles archive */
116const char *conf;		/* Configuration file to use */
117time_t timenow;
118
119#define MIN_PID         5
120#define MAX_PID		99999	/* was lower, see /usr/include/sys/proc.h */
121char hostname[MAXHOSTNAMELEN];	/* hostname */
122char daytime[16];		/* timenow in human readable form */
123
124static struct conf_entry *get_worklist(char **files);
125static void parse_file(FILE *cf, const char *cfname, struct conf_entry **work_p,
126		struct conf_entry **defconf_p);
127static char *sob(char *p);
128static char *son(char *p);
129static char *missing_field(char *p, char *errline);
130static void do_entry(struct conf_entry * ent);
131static void free_entry(struct conf_entry *ent);
132static struct conf_entry *init_entry(const char *fname,
133		struct conf_entry *src_entry);
134static void PRS(int argc, char **argv);
135static void usage(void);
136static void dotrim(const struct conf_entry *trim_ent, char *log,
137		int numdays, int flags, int perm, int owner_uid,
138		int group_gid, int sig);
139static int log_trim(const char *log, const struct conf_entry *log_ent);
140static void compress_log(char *log, int dowait);
141static void bzcompress_log(char *log, int dowait);
142static int sizefile(char *file);
143static int age_old_log(char *file);
144static pid_t get_pid(const char *pid_file);
145static time_t parse8601(char *s, char *errline);
146static void movefile(char *from, char *to, int perm, int owner_uid,
147		int group_gid);
148static void createdir(char *dirpart);
149static time_t parseDWM(char *s, char *errline);
150
151/*
152 * All the following are defined to work on an 'int', in the
153 * range 0 to 255, plus EOF.  Define wrappers which can take
154 * values of type 'char', either signed or unsigned.
155 */
156#define isprintch(Anychar)    isprint(((int) Anychar) & 255)
157#define isspacech(Anychar)    isspace(((int) Anychar) & 255)
158#define tolowerch(Anychar)    tolower(((int) Anychar) & 255)
159
160int
161main(int argc, char **argv)
162{
163	struct conf_entry *p, *q;
164	char *savglob;
165	glob_t pglob;
166	int i;
167
168	PRS(argc, argv);
169	argc -= optind;
170	argv += optind;
171
172	if (needroot && getuid() && geteuid())
173		errx(1, "must have root privs");
174	p = q = get_worklist(argv);
175
176	while (p) {
177		if ((p->flags & CE_GLOB) == 0) {
178			do_entry(p);
179		} else {
180			if (verbose > 2)
181				printf("\t+ Processing pattern %s\n", p->log);
182			if (glob(p->log, GLOB_NOCHECK, NULL, &pglob) != 0) {
183				warn("can't expand pattern: %s", p->log);
184			} else {
185				savglob = p->log;
186				for (i = 0; i < pglob.gl_matchc; i++) {
187					p->log = pglob.gl_pathv[i];
188					do_entry(p);
189				}
190				globfree(&pglob);
191				p->log = savglob;
192				if (verbose > 2)
193					printf("\t+ Done with pattern\n");
194			}
195		}
196		p = p->next;
197		free_entry(q);
198		q = p;
199	}
200	while (wait(NULL) > 0 || errno == EINTR)
201		;
202	return (0);
203}
204
205static struct conf_entry *
206init_entry(const char *fname, struct conf_entry *src_entry)
207{
208	struct conf_entry *tempwork;
209
210	if (verbose > 4)
211		printf("\t--> [creating entry for %s]\n", fname);
212
213	tempwork = malloc(sizeof(struct conf_entry));
214	if (tempwork == NULL)
215		err(1, "malloc of conf_entry for %s", fname);
216
217	tempwork->log = strdup(fname);
218	if (tempwork->log == NULL)
219		err(1, "strdup for %s", fname);
220
221	if (src_entry != NULL) {
222		tempwork->pid_file = NULL;
223		if (src_entry->pid_file)
224			tempwork->pid_file = strdup(src_entry->pid_file);
225		tempwork->r_reason = NULL;
226		tempwork->rotate = 0;
227		tempwork->uid = src_entry->uid;
228		tempwork->gid = src_entry->gid;
229		tempwork->numlogs = src_entry->numlogs;
230		tempwork->size = src_entry->size;
231		tempwork->hours = src_entry->hours;
232		tempwork->trim_at = src_entry->trim_at;
233		tempwork->permissions = src_entry->permissions;
234		tempwork->flags = src_entry->flags;
235		tempwork->sig = src_entry->sig;
236		tempwork->def_cfg = src_entry->def_cfg;
237	} else {
238		/* Initialize as a "do-nothing" entry */
239		tempwork->pid_file = NULL;
240		tempwork->r_reason = NULL;
241		tempwork->rotate = 0;
242		tempwork->uid = NONE;
243		tempwork->gid = NONE;
244		tempwork->numlogs = 1;
245		tempwork->size = -1;
246		tempwork->hours = -1;
247		tempwork->trim_at = (time_t)0;
248		tempwork->permissions = 0;
249		tempwork->flags = 0;
250		tempwork->sig = SIGHUP;
251		tempwork->def_cfg = 0;
252	}
253	tempwork->next = NULL;
254
255	return (tempwork);
256}
257
258static void
259free_entry(struct conf_entry *ent)
260{
261
262	if (ent == NULL)
263		return;
264
265	if (ent->log != NULL) {
266		if (verbose > 4)
267			printf("\t--> [freeing entry for %s]\n", ent->log);
268		free(ent->log);
269		ent->log = NULL;
270	}
271
272	if (ent->pid_file != NULL) {
273		free(ent->pid_file);
274		ent->pid_file = NULL;
275	}
276
277	if (ent->r_reason != NULL) {
278		free(ent->r_reason);
279		ent->r_reason = NULL;
280	}
281
282	free(ent);
283}
284
285static void
286do_entry(struct conf_entry * ent)
287{
288#define REASON_MAX	80
289	int size, modtime;
290	char temp_reason[REASON_MAX];
291
292	if (verbose) {
293		if (ent->flags & CE_COMPACT)
294			printf("%s <%dZ>: ", ent->log, ent->numlogs);
295		else if (ent->flags & CE_BZCOMPACT)
296			printf("%s <%dJ>: ", ent->log, ent->numlogs);
297		else
298			printf("%s <%d>: ", ent->log, ent->numlogs);
299	}
300	size = sizefile(ent->log);
301	modtime = age_old_log(ent->log);
302	ent->rotate = 0;
303	if (size < 0) {
304		if (verbose)
305			printf("does not exist.\n");
306	} else {
307		if (ent->flags & CE_TRIMAT && !force && !rotatereq) {
308			if (timenow < ent->trim_at
309			    || difftime(timenow, ent->trim_at) >= 60 * 60) {
310				if (verbose)
311					printf("--> will trim at %s",
312					    ctime(&ent->trim_at));
313				return;
314			} else if (verbose && ent->hours <= 0) {
315				printf("--> time is up\n");
316			}
317		}
318		if (verbose && (ent->size > 0))
319			printf("size (Kb): %d [%d] ", size, ent->size);
320		if (verbose && (ent->hours > 0))
321			printf(" age (hr): %d [%d] ", modtime, ent->hours);
322
323		/*
324		 * Figure out if this logfile needs to be rotated.
325		 */
326		temp_reason[0] = '\0';
327		if (rotatereq) {
328			ent->rotate = 1;
329			snprintf(temp_reason, REASON_MAX, " due to -R from %s",
330			    requestor);
331		} else if (force) {
332			ent->rotate = 1;
333			snprintf(temp_reason, REASON_MAX, " due to -F request");
334		} else if ((ent->size > 0) && (size >= ent->size)) {
335			ent->rotate = 1;
336			snprintf(temp_reason, REASON_MAX, " due to size>%dK",
337			    ent->size);
338		} else if (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) {
339			ent->rotate = 1;
340		} else if ((ent->hours > 0) && ((modtime >= ent->hours) ||
341		    (modtime < 0))) {
342			ent->rotate = 1;
343		}
344
345		/*
346		 * If the file needs to be rotated, then rotate it.
347		 */
348		if (ent->rotate) {
349			if (temp_reason[0] != '\0')
350				ent->r_reason = strdup(temp_reason);
351			if (verbose)
352				printf("--> trimming log....\n");
353			if (noaction && !verbose) {
354				if (ent->flags & CE_COMPACT)
355					printf("%s <%dZ>: trimming\n",
356					    ent->log, ent->numlogs);
357				else if (ent->flags & CE_BZCOMPACT)
358					printf("%s <%dJ>: trimming\n",
359					    ent->log, ent->numlogs);
360				else
361					printf("%s <%d>: trimming\n",
362					    ent->log, ent->numlogs);
363			}
364			dotrim(ent, ent->log, ent->numlogs, ent->flags,
365			    ent->permissions, ent->uid, ent->gid, ent->sig);
366		} else {
367			if (verbose)
368				printf("--> skipping\n");
369		}
370	}
371#undef REASON_MAX
372}
373
374static void
375PRS(int argc, char **argv)
376{
377	int c;
378	char *p;
379
380	timenow = time((time_t *) 0);
381	(void)strncpy(daytime, ctime(&timenow) + 4, 15);
382	daytime[15] = '\0';
383
384	/* Let's get our hostname */
385	(void) gethostname(hostname, sizeof(hostname));
386
387	/* Truncate domain */
388	if ((p = strchr(hostname, '.'))) {
389		*p = '\0';
390	}
391
392	/* Parse command line options. */
393	while ((c = getopt(argc, argv, "a:f:nrsvFR:")) != -1)
394		switch (c) {
395		case 'a':
396			archtodir++;
397			archdirname = optarg;
398			break;
399		case 'f':
400			conf = optarg;
401			break;
402		case 'n':
403			noaction++;
404			break;
405		case 'r':
406			needroot = 0;
407			break;
408		case 's':
409			nosignal = 1;
410			break;
411		case 'v':
412			verbose++;
413			break;
414		case 'F':
415			force++;
416			break;
417		case 'R':
418			rotatereq++;
419			requestor = strdup(optarg);
420			break;
421		default:
422			usage();
423			/* NOTREACHED */
424		}
425
426	if (rotatereq) {
427		if (optind == argc) {
428			warnx("At least one filename must be given when -R is specified.");
429			usage();
430			/* NOTREACHED */
431		}
432		/* Make sure "requestor" value is safe for a syslog message. */
433		for (p = requestor; *p != '\0'; p++) {
434			if (!isprintch(*p) && (*p != '\t'))
435				*p = '.';
436		}
437	}
438}
439
440static void
441usage(void)
442{
443
444	fprintf(stderr,
445	    "usage: newsyslog [-Fnrsv] [-a directory] [-f config-file]\n"
446	    "                 [ [-R requestor] filename ... ]\n");
447	exit(1);
448}
449
450/*
451 * Parse a configuration file and return a linked list of all the logs
452 * which should be processed.
453 */
454static struct conf_entry *
455get_worklist(char **files)
456{
457	FILE *f;
458	const char *fname;
459	char **given;
460	struct conf_entry *defconf, *dupent, *ent, *firstnew;
461	struct conf_entry *newlist, *worklist;
462	int gmatch;
463
464	defconf = worklist = NULL;
465
466	fname = conf;
467	if (fname == NULL)
468		fname = _PATH_CONF;
469
470	if (strcmp(fname, "-") != 0)
471		f = fopen(fname, "r");
472	else {
473		f = stdin;
474		fname = "<stdin>";
475	}
476	if (!f)
477		err(1, "%s", conf);
478
479	parse_file(f, fname, &worklist, &defconf);
480	(void) fclose(f);
481
482	/*
483	 * All config-file information has been read in and turned into
484	 * a worklist.  If there were no specific files given on the run
485	 * command, then the work of this routine is done.
486	 */
487	if (*files == NULL) {
488		if (defconf != NULL)
489			free_entry(defconf);
490		return (worklist);
491		/* NOTREACHED */
492	}
493
494	/*
495	 * If newsyslog was given a specific list of files to process,
496	 * it may be that some of those files were not listed in any
497	 * config file.  Those unlisted files should get the default
498	 * rotation action.  First, create the default-rotation action
499	 * if none was found in a system config file.
500	 */
501	if (defconf == NULL) {
502		defconf = init_entry(DEFAULT_MARKER, NULL);
503		defconf->numlogs = 3;
504		defconf->size = 50;
505		defconf->permissions = S_IRUSR|S_IWUSR;
506	}
507
508	/*
509	 * If newsyslog was run with a list of specific filenames,
510	 * then create a new worklist which has only those files in
511	 * it, picking up the rotation-rules for those files from
512	 * the original worklist.
513	 *
514	 * XXX - Note that this will copy multiple rules for a single
515	 *	logfile, if multiple entries are an exact match for
516	 *	that file.  That matches the historic behavior, but do
517	 *	we want to continue to allow it?  If so, it should
518	 *	probably be handled more intelligently.
519	 */
520	firstnew = newlist = NULL;
521	for (given = files; *given; ++given) {
522		gmatch = 0;
523		/*
524		 * First try to find exact-matches for this given file.
525		 */
526		for (ent = worklist; ent; ent = ent->next) {
527			if ((ent->flags & CE_GLOB) != 0)
528				continue;
529			if (strcmp(ent->log, *given) == 0) {
530				gmatch++;
531				dupent = init_entry(*given, ent);
532				if (!firstnew)
533					firstnew = dupent;
534				else
535					newlist->next = dupent;
536				newlist = dupent;
537			}
538		}
539		if (gmatch) {
540			if (verbose > 2)
541				printf("\t+ Matched entry %s\n", *given);
542			continue;
543		}
544
545		/*
546		 * There was no exact-match for this given file, so look
547		 * for a "glob" entry which does match.
548		 */
549		for (ent = worklist; ent; ent = ent->next) {
550			if ((ent->flags & CE_GLOB) == 0)
551				continue;
552			if (fnmatch(ent->log, *given, FNM_PATHNAME) == 0) {
553				gmatch++;
554				dupent = init_entry(*given, ent);
555				if (!firstnew)
556					firstnew = dupent;
557				else
558					newlist->next = dupent;
559				newlist = dupent;
560				/* This work entry is *not* a glob! */
561				dupent->flags &= ~CE_GLOB;
562				/* Only allow a match to one glob-entry */
563				break;
564			}
565		}
566		if (gmatch) {
567			if (verbose > 2)
568				printf("\t+ Matched %s via %s\n", *given,
569				    ent->log);
570			continue;
571		}
572
573		/*
574		 * This given file was not found in any config file, so
575		 * add a worklist item based on the default entry.
576		 */
577		if (verbose > 2)
578			printf("\t+ No entry matched %s  (will use %s)\n",
579			    *given, DEFAULT_MARKER);
580		dupent = init_entry(*given, defconf);
581		if (!firstnew)
582			firstnew = dupent;
583		else
584			newlist->next = dupent;
585		/* Mark that it was *not* found in a config file */
586		dupent->def_cfg = 1;
587		newlist = dupent;
588	}
589
590	/*
591	 * Free all the entries in the original work list, and then
592	 * return the new work list.
593	 */
594	while (worklist) {
595		ent = worklist->next;
596		free_entry(worklist);
597		worklist = ent;
598	}
599
600	free_entry(defconf);
601	return (newlist);
602}
603
604/*
605 * Parse a configuration file and update a linked list of all the logs to
606 * process.
607 */
608static void
609parse_file(FILE *cf, const char *cfname, struct conf_entry **work_p,
610    struct conf_entry **defconf_p)
611{
612	char line[BUFSIZ], *parse, *q;
613	char *cp, *errline, *group;
614	struct conf_entry *working, *worklist;
615	struct passwd *pass;
616	struct group *grp;
617	int eol;
618
619	/*
620	 * XXX - for now, assume that only one config file will be read,
621	 *	ie, this routine is only called one time.
622	 */
623	worklist = NULL;
624
625	while (fgets(line, BUFSIZ, cf)) {
626		if ((line[0] == '\n') || (line[0] == '#') ||
627		    (strlen(line) == 0))
628			continue;
629		errline = strdup(line);
630		for (cp = line + 1; *cp != '\0'; cp++) {
631			if (*cp != '#')
632				continue;
633			if (*(cp - 1) == '\\') {
634				strcpy(cp - 1, cp);
635				cp--;
636				continue;
637			}
638			*cp = '\0';
639			break;
640		}
641
642		q = parse = missing_field(sob(line), errline);
643		parse = son(line);
644		if (!*parse)
645			errx(1, "malformed line (missing fields):\n%s",
646			    errline);
647		*parse = '\0';
648
649		working = init_entry(q, NULL);
650		if (strcasecmp(DEFAULT_MARKER, q) == 0) {
651			if (defconf_p == NULL) {
652				warnx("Ignoring entry for %s in %s!", q,
653				    cfname);
654				free_entry(working);
655				continue;
656			} else if (*defconf_p != NULL) {
657				warnx("Ignoring duplicate entry for %s!", q);
658				free_entry(working);
659				continue;
660			}
661			*defconf_p = working;
662		} else {
663			if (!*work_p)
664				*work_p = working;
665			else
666				worklist->next = working;
667			worklist = working;
668		}
669
670		q = parse = missing_field(sob(++parse), errline);
671		parse = son(parse);
672		if (!*parse)
673			errx(1, "malformed line (missing fields):\n%s",
674			    errline);
675		*parse = '\0';
676		if ((group = strchr(q, ':')) != NULL ||
677		    (group = strrchr(q, '.')) != NULL) {
678			*group++ = '\0';
679			if (*q) {
680				if (!(isnumber(*q))) {
681					if ((pass = getpwnam(q)) == NULL)
682						errx(1,
683				     "error in config file; unknown user:\n%s",
684						    errline);
685					working->uid = pass->pw_uid;
686				} else
687					working->uid = atoi(q);
688			} else
689				working->uid = NONE;
690
691			q = group;
692			if (*q) {
693				if (!(isnumber(*q))) {
694					if ((grp = getgrnam(q)) == NULL)
695						errx(1,
696				    "error in config file; unknown group:\n%s",
697						    errline);
698					working->gid = grp->gr_gid;
699				} else
700					working->gid = atoi(q);
701			} else
702				working->gid = NONE;
703
704			q = parse = missing_field(sob(++parse), errline);
705			parse = son(parse);
706			if (!*parse)
707				errx(1, "malformed line (missing fields):\n%s",
708				    errline);
709			*parse = '\0';
710		} else
711			working->uid = working->gid = NONE;
712
713		if (!sscanf(q, "%o", &working->permissions))
714			errx(1, "error in config file; bad permissions:\n%s",
715			    errline);
716
717		q = parse = missing_field(sob(++parse), errline);
718		parse = son(parse);
719		if (!*parse)
720			errx(1, "malformed line (missing fields):\n%s",
721			    errline);
722		*parse = '\0';
723		if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0)
724			errx(1, "error in config file; bad value for count of logs to save:\n%s",
725			    errline);
726
727		q = parse = missing_field(sob(++parse), errline);
728		parse = son(parse);
729		if (!*parse)
730			errx(1, "malformed line (missing fields):\n%s",
731			    errline);
732		*parse = '\0';
733		if (isdigit(*q))
734			working->size = atoi(q);
735		else
736			working->size = -1;
737
738		working->flags = 0;
739		q = parse = missing_field(sob(++parse), errline);
740		parse = son(parse);
741		eol = !*parse;
742		*parse = '\0';
743		{
744			char *ep;
745			u_long ul;
746
747			ul = strtoul(q, &ep, 10);
748			if (ep == q)
749				working->hours = 0;
750			else if (*ep == '*')
751				working->hours = -1;
752			else if (ul > INT_MAX)
753				errx(1, "interval is too large:\n%s", errline);
754			else
755				working->hours = ul;
756
757			if (*ep != '\0' && *ep != '@' && *ep != '*' &&
758			    *ep != '$')
759				errx(1, "malformed interval/at:\n%s", errline);
760			if (*ep == '@') {
761				if ((working->trim_at = parse8601(ep + 1, errline))
762				    == (time_t) - 1)
763					errx(1, "malformed at:\n%s", errline);
764				working->flags |= CE_TRIMAT;
765			} else if (*ep == '$') {
766				if ((working->trim_at = parseDWM(ep + 1, errline))
767				    == (time_t) - 1)
768					errx(1, "malformed at:\n%s", errline);
769				working->flags |= CE_TRIMAT;
770			}
771		}
772
773		if (eol)
774			q = NULL;
775		else {
776			q = parse = sob(++parse);	/* Optional field */
777			parse = son(parse);
778			if (!*parse)
779				eol = 1;
780			*parse = '\0';
781		}
782
783		for (; q && *q && !isspacech(*q); q++) {
784			switch (tolowerch(*q)) {
785			case 'b':
786				working->flags |= CE_BINARY;
787				break;
788			case 'c':
789				/*
790				 * netbsd uses 'c' for "create".  We will
791				 * temporarily accept it for 'g', because
792				 * earlier freebsd versions had a typo
793				 * of ('G' || 'c')...
794				 */
795				warnx("Assuming 'g' for 'c' in flags for line:\n%s",
796				    errline);
797				/* FALLTHROUGH */
798			case 'g':
799				working->flags |= CE_GLOB;
800				break;
801			case 'j':
802				working->flags |= CE_BZCOMPACT;
803				break;
804			case 'n':
805				working->flags |= CE_NOSIGNAL;
806				break;
807			case 'w':
808				working->flags |= CE_COMPACTWAIT;
809				break;
810			case 'z':
811				working->flags |= CE_COMPACT;
812				break;
813			case '-':
814				break;
815			default:
816				errx(1, "illegal flag in config file -- %c",
817				    *q);
818			}
819		}
820
821		if (eol)
822			q = NULL;
823		else {
824			q = parse = sob(++parse);	/* Optional field */
825			parse = son(parse);
826			if (!*parse)
827				eol = 1;
828			*parse = '\0';
829		}
830
831		working->pid_file = NULL;
832		if (q && *q) {
833			if (*q == '/')
834				working->pid_file = strdup(q);
835			else if (isdigit(*q))
836				goto got_sig;
837			else
838				errx(1,
839			"illegal pid file or signal number in config file:\n%s",
840				    errline);
841		}
842		if (eol)
843			q = NULL;
844		else {
845			q = parse = sob(++parse);	/* Optional field */
846			*(parse = son(parse)) = '\0';
847		}
848
849		working->sig = SIGHUP;
850		if (q && *q) {
851			if (isdigit(*q)) {
852		got_sig:
853				working->sig = atoi(q);
854			} else {
855		err_sig:
856				errx(1,
857				    "illegal signal number in config file:\n%s",
858				    errline);
859			}
860			if (working->sig < 1 || working->sig >= NSIG)
861				goto err_sig;
862		}
863
864		/*
865		 * Finish figuring out what pid-file to use (if any) in
866		 * later processing if this logfile needs to be rotated.
867		 */
868		if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) {
869			/*
870			 * This config-entry specified 'n' for nosignal,
871			 * see if it also specified an explicit pid_file.
872			 * This would be a pretty pointless combination.
873			 */
874			if (working->pid_file != NULL) {
875				warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s",
876				    working->pid_file, errline);
877				free(working->pid_file);
878				working->pid_file = NULL;
879			}
880		} else if (nosignal) {
881			/*
882			 * While this entry might usually signal some
883			 * process via the pid-file, newsyslog was run
884			 * with '-s', so quietly ignore the pid-file.
885			 */
886			if (working->pid_file != NULL) {
887				free(working->pid_file);
888				working->pid_file = NULL;
889			}
890		} else if (working->pid_file == NULL) {
891			/*
892			 * This entry did not specify the 'n' flag, which
893			 * means it should signal syslogd unless it had
894			 * specified some other pid-file.  But we only
895			 * try to notify syslog if we are root
896			 */
897			if (needroot)
898				working->pid_file = strdup(_PATH_SYSLOGPID);
899		}
900
901		free(errline);
902		errline = NULL;
903	}
904}
905
906static char *
907missing_field(char *p, char *errline)
908{
909
910	if (!p || !*p)
911		errx(1, "missing field in config file:\n%s", errline);
912	return (p);
913}
914
915static void
916dotrim(const struct conf_entry *trim_ent, char *log, int numdays, int flags,
917    int perm, int owner_uid, int group_gid, int sig)
918{
919	char dirpart[MAXPATHLEN], namepart[MAXPATHLEN];
920	char file1[MAXPATHLEN], file2[MAXPATHLEN];
921	char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
922	char jfile1[MAXPATHLEN];
923	char tfile[MAXPATHLEN];
924	int notified, need_notification, fd, _numdays;
925	struct stat st;
926	pid_t pid;
927
928#ifdef _IBMR2
929	/*
930	 * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will
931	 * actually change it to be owned by uid -1, instead of leaving it
932	 * as is, as it is supposed to.
933	 */
934	if (owner_uid == -1)
935		owner_uid = geteuid();
936#endif
937
938	if (archtodir) {
939		char *p;
940
941		/* build complete name of archive directory into dirpart */
942		if (*archdirname == '/') {	/* absolute */
943			strlcpy(dirpart, archdirname, sizeof(dirpart));
944		} else {	/* relative */
945			/* get directory part of logfile */
946			strlcpy(dirpart, log, sizeof(dirpart));
947			if ((p = rindex(dirpart, '/')) == NULL)
948				dirpart[0] = '\0';
949			else
950				*(p + 1) = '\0';
951			strlcat(dirpart, archdirname, sizeof(dirpart));
952		}
953
954		/* check if archive directory exists, if not, create it */
955		if (lstat(dirpart, &st))
956			createdir(dirpart);
957
958		/* get filename part of logfile */
959		if ((p = rindex(log, '/')) == NULL)
960			strlcpy(namepart, log, sizeof(namepart));
961		else
962			strlcpy(namepart, p + 1, sizeof(namepart));
963
964		/* name of oldest log */
965		(void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart,
966		    namepart, numdays);
967		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
968		    COMPRESS_POSTFIX);
969		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
970		    BZCOMPRESS_POSTFIX);
971	} else {
972		/* name of oldest log */
973		(void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays);
974		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
975		    COMPRESS_POSTFIX);
976		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
977		    BZCOMPRESS_POSTFIX);
978	}
979
980	if (noaction) {
981		printf("rm -f %s\n", file1);
982		printf("rm -f %s\n", zfile1);
983		printf("rm -f %s\n", jfile1);
984	} else {
985		(void) unlink(file1);
986		(void) unlink(zfile1);
987		(void) unlink(jfile1);
988	}
989
990	/* Move down log files */
991	_numdays = numdays;	/* preserve */
992	while (numdays--) {
993
994		(void) strlcpy(file2, file1, sizeof(file2));
995
996		if (archtodir)
997			(void) snprintf(file1, sizeof(file1), "%s/%s.%d",
998			    dirpart, namepart, numdays);
999		else
1000			(void) snprintf(file1, sizeof(file1), "%s.%d", log,
1001			    numdays);
1002
1003		(void) strlcpy(zfile1, file1, sizeof(zfile1));
1004		(void) strlcpy(zfile2, file2, sizeof(zfile2));
1005		if (lstat(file1, &st)) {
1006			(void) strlcat(zfile1, COMPRESS_POSTFIX,
1007			    sizeof(zfile1));
1008			(void) strlcat(zfile2, COMPRESS_POSTFIX,
1009			    sizeof(zfile2));
1010			if (lstat(zfile1, &st)) {
1011				strlcpy(zfile1, file1, sizeof(zfile1));
1012				strlcpy(zfile2, file2, sizeof(zfile2));
1013				strlcat(zfile1, BZCOMPRESS_POSTFIX,
1014				    sizeof(zfile1));
1015				strlcat(zfile2, BZCOMPRESS_POSTFIX,
1016				    sizeof(zfile2));
1017				if (lstat(zfile1, &st))
1018					continue;
1019			}
1020		}
1021		if (noaction) {
1022			printf("mv %s %s\n", zfile1, zfile2);
1023			printf("chmod %o %s\n", perm, zfile2);
1024			printf("chown %d:%d %s\n",
1025			    owner_uid, group_gid, zfile2);
1026		} else {
1027			(void) rename(zfile1, zfile2);
1028			(void) chmod(zfile2, perm);
1029			(void) chown(zfile2, owner_uid, group_gid);
1030		}
1031	}
1032	if (!noaction && !(flags & CE_BINARY)) {
1033		/* Report the trimming to the old log */
1034		(void) log_trim(log, trim_ent);
1035	}
1036
1037	if (!_numdays) {
1038		if (noaction)
1039			printf("rm %s\n", log);
1040		else
1041			(void) unlink(log);
1042	} else {
1043		if (noaction)
1044			printf("mv %s to %s\n", log, file1);
1045		else {
1046			if (archtodir)
1047				movefile(log, file1, perm, owner_uid,
1048				    group_gid);
1049			else
1050				(void) rename(log, file1);
1051		}
1052	}
1053
1054	if (noaction)
1055		printf("Start new log...");
1056	else {
1057		strlcpy(tfile, log, sizeof(tfile));
1058		strlcat(tfile, ".XXXXXX", sizeof(tfile));
1059		mkstemp(tfile);
1060		fd = creat(tfile, perm);
1061		if (fd < 0)
1062			err(1, "can't start new log");
1063		if (fchown(fd, owner_uid, group_gid))
1064			err(1, "can't chmod new log file");
1065		(void) close(fd);
1066		if (!(flags & CE_BINARY)) {
1067			/* Add status message to new log file */
1068			if (log_trim(tfile, trim_ent))
1069				err(1, "can't add status message to log");
1070		}
1071	}
1072	if (noaction)
1073		printf("chmod %o %s...\n", perm, log);
1074	else {
1075		(void) chmod(tfile, perm);
1076		if (rename(tfile, log) < 0) {
1077			err(1, "can't start new log");
1078			(void) unlink(tfile);
1079		}
1080	}
1081
1082	pid = 0;
1083	need_notification = notified = 0;
1084	if (trim_ent->pid_file != NULL) {
1085		need_notification = 1;
1086		pid = get_pid(trim_ent->pid_file);
1087	}
1088	if (pid) {
1089		if (noaction) {
1090			notified = 1;
1091			printf("kill -%d %d\n", sig, (int) pid);
1092		} else if (kill(pid, sig))
1093			warn("can't notify daemon, pid %d", (int) pid);
1094		else {
1095			notified = 1;
1096			if (verbose)
1097				printf("daemon pid %d notified\n", (int) pid);
1098		}
1099	}
1100	if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) {
1101		if (need_notification && !notified)
1102			warnx(
1103			    "log %s not compressed because daemon not notified",
1104			    log);
1105		else if (noaction)
1106			printf("Compress %s.0\n", log);
1107		else {
1108			if (notified) {
1109				if (verbose)
1110					printf("small pause to allow daemon to close log\n");
1111				sleep(10);
1112			}
1113			if (archtodir) {
1114				(void) snprintf(file1, sizeof(file1), "%s/%s",
1115				    dirpart, namepart);
1116				if (flags & CE_COMPACT)
1117					compress_log(file1,
1118					    flags & CE_COMPACTWAIT);
1119				else if (flags & CE_BZCOMPACT)
1120					bzcompress_log(file1,
1121					    flags & CE_COMPACTWAIT);
1122			} else {
1123				if (flags & CE_COMPACT)
1124					compress_log(log,
1125					    flags & CE_COMPACTWAIT);
1126				else if (flags & CE_BZCOMPACT)
1127					bzcompress_log(log,
1128					    flags & CE_COMPACTWAIT);
1129			}
1130		}
1131	}
1132}
1133
1134/* Log the fact that the logs were turned over */
1135static int
1136log_trim(const char *log, const struct conf_entry *log_ent)
1137{
1138	FILE *f;
1139	const char *xtra;
1140
1141	if ((f = fopen(log, "a")) == NULL)
1142		return (-1);
1143	xtra = "";
1144	if (log_ent->def_cfg)
1145		xtra = " using <default> rule";
1146	if (log_ent->r_reason != NULL)
1147		fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s%s\n",
1148		    daytime, hostname, (int) getpid(), log_ent->r_reason, xtra);
1149	else
1150		fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n",
1151		    daytime, hostname, (int) getpid(), xtra);
1152	if (fclose(f) == EOF)
1153		err(1, "log_trim: fclose:");
1154	return (0);
1155}
1156
1157/* Fork of gzip to compress the old log file */
1158static void
1159compress_log(char *log, int dowait)
1160{
1161	pid_t pid;
1162	char tmp[MAXPATHLEN];
1163
1164	while (dowait && (wait(NULL) > 0 || errno == EINTR))
1165		;
1166	(void) snprintf(tmp, sizeof(tmp), "%s.0", log);
1167	pid = fork();
1168	if (pid < 0)
1169		err(1, "gzip fork");
1170	else if (!pid) {
1171		(void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0);
1172		err(1, _PATH_GZIP);
1173	}
1174}
1175
1176/* Fork of bzip2 to compress the old log file */
1177static void
1178bzcompress_log(char *log, int dowait)
1179{
1180	pid_t pid;
1181	char tmp[MAXPATHLEN];
1182
1183	while (dowait && (wait(NULL) > 0 || errno == EINTR))
1184		;
1185	snprintf(tmp, sizeof(tmp), "%s.0", log);
1186	pid = fork();
1187	if (pid < 0)
1188		err(1, "bzip2 fork");
1189	else if (!pid) {
1190		execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0);
1191		err(1, _PATH_BZIP2);
1192	}
1193}
1194
1195/* Return size in kilobytes of a file */
1196static int
1197sizefile(char *file)
1198{
1199	struct stat sb;
1200
1201	if (stat(file, &sb) < 0)
1202		return (-1);
1203	return (kbytes(dbtob(sb.st_blocks)));
1204}
1205
1206/* Return the age of old log file (file.0) */
1207static int
1208age_old_log(char *file)
1209{
1210	struct stat sb;
1211	char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1];
1212
1213	if (archtodir) {
1214		char *p;
1215
1216		/* build name of archive directory into tmp */
1217		if (*archdirname == '/') {	/* absolute */
1218			strlcpy(tmp, archdirname, sizeof(tmp));
1219		} else {	/* relative */
1220			/* get directory part of logfile */
1221			strlcpy(tmp, file, sizeof(tmp));
1222			if ((p = rindex(tmp, '/')) == NULL)
1223				tmp[0] = '\0';
1224			else
1225				*(p + 1) = '\0';
1226			strlcat(tmp, archdirname, sizeof(tmp));
1227		}
1228
1229		strlcat(tmp, "/", sizeof(tmp));
1230
1231		/* get filename part of logfile */
1232		if ((p = rindex(file, '/')) == NULL)
1233			strlcat(tmp, file, sizeof(tmp));
1234		else
1235			strlcat(tmp, p + 1, sizeof(tmp));
1236	} else {
1237		(void) strlcpy(tmp, file, sizeof(tmp));
1238	}
1239
1240	if (stat(strcat(tmp, ".0"), &sb) < 0)
1241		if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0)
1242			return (-1);
1243	return ((int) (timenow - sb.st_mtime + 1800) / 3600);
1244}
1245
1246static pid_t
1247get_pid(const char *pid_file)
1248{
1249	FILE *f;
1250	char line[BUFSIZ];
1251	pid_t pid = 0;
1252
1253	if ((f = fopen(pid_file, "r")) == NULL)
1254		warn("can't open %s pid file to restart a daemon",
1255		    pid_file);
1256	else {
1257		if (fgets(line, BUFSIZ, f)) {
1258			pid = atol(line);
1259			if (pid < MIN_PID || pid > MAX_PID) {
1260				warnx("preposterous process number: %d",
1261				   (int)pid);
1262				pid = 0;
1263			}
1264		} else
1265			warn("can't read %s pid file to restart a daemon",
1266			    pid_file);
1267		(void) fclose(f);
1268	}
1269	return (pid);
1270}
1271
1272/* Skip Over Blanks */
1273char *
1274sob(char *p)
1275{
1276	while (p && *p && isspace(*p))
1277		p++;
1278	return (p);
1279}
1280
1281/* Skip Over Non-Blanks */
1282char *
1283son(char *p)
1284{
1285	while (p && *p && !isspace(*p))
1286		p++;
1287	return (p);
1288}
1289
1290/*
1291 * Parse a limited subset of ISO 8601. The specific format is as follows:
1292 *
1293 * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
1294 *
1295 * We don't accept a timezone specification; missing fields (including timezone)
1296 * are defaulted to the current date but time zero.
1297 */
1298static time_t
1299parse8601(char *s, char *errline)
1300{
1301	char *t;
1302	time_t tsecs;
1303	struct tm tm, *tmp;
1304	u_long ul;
1305
1306	tmp = localtime(&timenow);
1307	tm = *tmp;
1308
1309	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1310
1311	ul = strtoul(s, &t, 10);
1312	if (*t != '\0' && *t != 'T')
1313		return (-1);
1314
1315	/*
1316	 * Now t points either to the end of the string (if no time was
1317	 * provided) or to the letter `T' which separates date and time in
1318	 * ISO 8601.  The pointer arithmetic is the same for either case.
1319	 */
1320	switch (t - s) {
1321	case 8:
1322		tm.tm_year = ((ul / 1000000) - 19) * 100;
1323		ul = ul % 1000000;
1324	case 6:
1325		tm.tm_year -= tm.tm_year % 100;
1326		tm.tm_year += ul / 10000;
1327		ul = ul % 10000;
1328	case 4:
1329		tm.tm_mon = (ul / 100) - 1;
1330		ul = ul % 100;
1331	case 2:
1332		tm.tm_mday = ul;
1333	case 0:
1334		break;
1335	default:
1336		return (-1);
1337	}
1338
1339	/* sanity check */
1340	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
1341	    || tm.tm_mday < 1 || tm.tm_mday > 31)
1342		return (-1);
1343
1344	if (*t != '\0') {
1345		s = ++t;
1346		ul = strtoul(s, &t, 10);
1347		if (*t != '\0' && !isspace(*t))
1348			return (-1);
1349
1350		switch (t - s) {
1351		case 6:
1352			tm.tm_sec = ul % 100;
1353			ul /= 100;
1354		case 4:
1355			tm.tm_min = ul % 100;
1356			ul /= 100;
1357		case 2:
1358			tm.tm_hour = ul;
1359		case 0:
1360			break;
1361		default:
1362			return (-1);
1363		}
1364
1365		/* sanity check */
1366		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
1367		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
1368			return (-1);
1369	}
1370	if ((tsecs = mktime(&tm)) == -1)
1371		errx(1, "nonexistent time:\n%s", errline);
1372	return (tsecs);
1373}
1374
1375/* physically move file */
1376static void
1377movefile(char *from, char *to, int perm, int owner_uid, int group_gid)
1378{
1379	FILE *src, *dst;
1380	int c;
1381
1382	if ((src = fopen(from, "r")) == NULL)
1383		err(1, "can't fopen %s for reading", from);
1384	if ((dst = fopen(to, "w")) == NULL)
1385		err(1, "can't fopen %s for writing", to);
1386	if (fchown(fileno(dst), owner_uid, group_gid))
1387		err(1, "can't fchown %s", to);
1388	if (fchmod(fileno(dst), perm))
1389		err(1, "can't fchmod %s", to);
1390
1391	while ((c = getc(src)) != EOF) {
1392		if ((putc(c, dst)) == EOF)
1393			err(1, "error writing to %s", to);
1394	}
1395
1396	if (ferror(src))
1397		err(1, "error reading from %s", from);
1398	if ((fclose(src)) != 0)
1399		err(1, "can't fclose %s", to);
1400	if ((fclose(dst)) != 0)
1401		err(1, "can't fclose %s", from);
1402	if ((unlink(from)) != 0)
1403		err(1, "can't unlink %s", from);
1404}
1405
1406/* create one or more directory components of a path */
1407static void
1408createdir(char *dirpart)
1409{
1410	int res;
1411	char *s, *d;
1412	char mkdirpath[MAXPATHLEN];
1413	struct stat st;
1414
1415	s = dirpart;
1416	d = mkdirpath;
1417
1418	for (;;) {
1419		*d++ = *s++;
1420		if (*s != '/' && *s != '\0')
1421			continue;
1422		*d = '\0';
1423		res = lstat(mkdirpath, &st);
1424		if (res != 0) {
1425			if (noaction) {
1426				printf("mkdir %s\n", mkdirpath);
1427			} else {
1428				res = mkdir(mkdirpath, 0755);
1429				if (res != 0)
1430					err(1, "Error on mkdir(\"%s\") for -a",
1431					    mkdirpath);
1432			}
1433		}
1434		if (*s == '\0')
1435			break;
1436	}
1437	if (verbose)
1438		printf("created directory '%s' for -a\n", dirpart);
1439}
1440
1441/*-
1442 * Parse a cyclic time specification, the format is as follows:
1443 *
1444 *	[Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
1445 *
1446 * to rotate a logfile cyclic at
1447 *
1448 *	- every day (D) within a specific hour (hh)	(hh = 0...23)
1449 *	- once a week (W) at a specific day (d)     OR	(d = 0..6, 0 = Sunday)
1450 *	- once a month (M) at a specific day (d)	(d = 1..31,l|L)
1451 *
1452 * We don't accept a timezone specification; missing fields
1453 * are defaulted to the current date but time zero.
1454 */
1455static time_t
1456parseDWM(char *s, char *errline)
1457{
1458	char *t;
1459	time_t tsecs;
1460	struct tm tm, *tmp;
1461	long l;
1462	int nd;
1463	static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1464	int WMseen = 0;
1465	int Dseen = 0;
1466
1467	tmp = localtime(&timenow);
1468	tm = *tmp;
1469
1470	/* set no. of days per month */
1471
1472	nd = mtab[tm.tm_mon];
1473
1474	if (tm.tm_mon == 1) {
1475		if (((tm.tm_year + 1900) % 4 == 0) &&
1476		    ((tm.tm_year + 1900) % 100 != 0) &&
1477		    ((tm.tm_year + 1900) % 400 == 0)) {
1478			nd++;	/* leap year, 29 days in february */
1479		}
1480	}
1481	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1482
1483	for (;;) {
1484		switch (*s) {
1485		case 'D':
1486			if (Dseen)
1487				return (-1);
1488			Dseen++;
1489			s++;
1490			l = strtol(s, &t, 10);
1491			if (l < 0 || l > 23)
1492				return (-1);
1493			tm.tm_hour = l;
1494			break;
1495
1496		case 'W':
1497			if (WMseen)
1498				return (-1);
1499			WMseen++;
1500			s++;
1501			l = strtol(s, &t, 10);
1502			if (l < 0 || l > 6)
1503				return (-1);
1504			if (l != tm.tm_wday) {
1505				int save;
1506
1507				if (l < tm.tm_wday) {
1508					save = 6 - tm.tm_wday;
1509					save += (l + 1);
1510				} else {
1511					save = l - tm.tm_wday;
1512				}
1513
1514				tm.tm_mday += save;
1515
1516				if (tm.tm_mday > nd) {
1517					tm.tm_mon++;
1518					tm.tm_mday = tm.tm_mday - nd;
1519				}
1520			}
1521			break;
1522
1523		case 'M':
1524			if (WMseen)
1525				return (-1);
1526			WMseen++;
1527			s++;
1528			if (tolower(*s) == 'l') {
1529				tm.tm_mday = nd;
1530				s++;
1531				t = s;
1532			} else {
1533				l = strtol(s, &t, 10);
1534				if (l < 1 || l > 31)
1535					return (-1);
1536
1537				if (l > nd)
1538					return (-1);
1539				tm.tm_mday = l;
1540			}
1541			break;
1542
1543		default:
1544			return (-1);
1545			break;
1546		}
1547
1548		if (*t == '\0' || isspace(*t))
1549			break;
1550		else
1551			s = t;
1552	}
1553	if ((tsecs = mktime(&tm)) == -1)
1554		errx(1, "nonexistent time:\n%s", errline);
1555	return (tsecs);
1556}
1557