xref: /freebsd/usr.sbin/newsyslog/newsyslog.c (revision 4cf49a43559ed9fdad601bdcccd2c55963008675)
1 /*
2  * This file contains changes from the Open Software Foundation.
3  */
4 
5 /*
6 
7 Copyright 1988, 1989 by the Massachusetts Institute of Technology
8 
9 Permission to use, copy, modify, and distribute this software
10 and its documentation for any purpose and without fee is
11 hereby granted, provided that the above copyright notice
12 appear in all copies and that both that copyright notice and
13 this permission notice appear in supporting documentation,
14 and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
15 used in advertising or publicity pertaining to distribution
16 of the software without specific, written prior permission.
17 M.I.T. and the M.I.T. S.I.P.B. make no representations about
18 the suitability of this software for any purpose.  It is
19 provided "as is" without express or implied warranty.
20 
21 */
22 
23 /*
24  *      newsyslog - roll over selected logs at the appropriate time,
25  *              keeping the a specified number of backup files around.
26  */
27 
28 #ifndef lint
29 static const char rcsid[] =
30   "$FreeBSD$";
31 #endif /* not lint */
32 
33 #define OSF
34 #ifndef COMPRESS_POSTFIX
35 #define COMPRESS_POSTFIX ".gz"
36 #endif
37 
38 #include <ctype.h>
39 #include <err.h>
40 #include <fcntl.h>
41 #include <grp.h>
42 #include <paths.h>
43 #include <pwd.h>
44 #include <signal.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <time.h>
49 #include <unistd.h>
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <sys/param.h>
53 #include <sys/wait.h>
54 
55 #include "pathnames.h"
56 
57 #define kbytes(size)  (((size) + 1023) >> 10)
58 #ifdef _IBMR2
59 /* Calculates (db * DEV_BSIZE) */
60 #define dbtob(db)  ((unsigned)(db) << UBSHIFT)
61 #endif
62 
63 #define CE_COMPACT 1            /* Compact the achived log files */
64 #define CE_BINARY  2            /* Logfile is in binary, don't add */
65                                 /* status messages */
66 #define	CE_TRIMAT  4		/* trim at a specific time */
67 
68 #define NONE -1
69 
70 struct conf_entry {
71         char    *log;           /* Name of the log */
72 	char	*pid_file;	/* PID file */
73         int     uid;            /* Owner of log */
74         int     gid;            /* Group of log */
75         int     numlogs;        /* Number of logs to keep */
76         int     size;           /* Size cutoff to trigger trimming the log */
77         int     hours;          /* Hours between log trimming */
78 	time_t	trim_at;	/* Specific time to do trimming */
79         int     permissions;    /* File permissions on the log */
80         int     flags;          /* Flags (CE_COMPACT & CE_BINARY)  */
81 	int     sig;            /* Signal to send */
82         struct conf_entry       *next; /* Linked list pointer */
83 };
84 
85 int     verbose = 0;            /* Print out what's going on */
86 int     needroot = 1;           /* Root privs are necessary */
87 int     noaction = 0;           /* Don't do anything, just show it */
88 int     force = 0;		/* Force the trim no matter what*/
89 char    *conf = _PATH_CONF;	/* Configuration file to use */
90 time_t  timenow;
91 #define MIN_PID         5
92 #define MAX_PID		99999   /* was lower, see /usr/include/sys/proc.h */
93 char    hostname[MAXHOSTNAMELEN+1]; /* hostname */
94 char    *daytime;               /* timenow in human readable form */
95 
96 static struct conf_entry *parse_file();
97 static char *sob(char *p);
98 static char *son(char *p);
99 static char *missing_field(char *p,char *errline);
100 static void do_entry(struct conf_entry *ent);
101 static void PRS(int argc,char **argv);
102 static void usage();
103 static void dotrim(char *log,char *pid_file,int numdays,int falgs,int perm,int owner_uid,int group_gid,int sig);
104 static int log_trim(char *log);
105 static void compress_log(char *log);
106 static int sizefile(char *file);
107 static int age_old_log(char *file);
108 static pid_t get_pid(char *pid_file);
109 static	time_t parse8601(const char *s);
110 
111 int main(argc,argv)
112         int argc;
113         char **argv;
114 {
115         struct conf_entry *p, *q;
116 
117         PRS(argc,argv);
118         if (needroot && getuid() && geteuid())
119                 errx(1, "must have root privs");
120         p = q = parse_file();
121 
122         while (p) {
123                 do_entry(p);
124                 p=p->next;
125                 free((char *) q);
126                 q=p;
127         }
128         return(0);
129 }
130 
131 static void do_entry(ent)
132         struct conf_entry       *ent;
133 
134 {
135         int     size, modtime;
136 	char 	*pid_file;
137 
138         if (verbose) {
139                 if (ent->flags & CE_COMPACT)
140                         printf("%s <%dZ>: ",ent->log,ent->numlogs);
141                 else
142                         printf("%s <%d>: ",ent->log,ent->numlogs);
143         }
144         size = sizefile(ent->log);
145         modtime = age_old_log(ent->log);
146         if (size < 0) {
147                 if (verbose)
148                         printf("does not exist.\n");
149         } else {
150 		if (ent->flags & CE_TRIMAT) {
151 			if (timenow < ent->trim_at
152 			    || difftime(timenow, ent->trim_at) >= 60*60) {
153 				if (verbose)
154 					printf("--> will trim at %s",
155 					       ctime(&ent->trim_at));
156 				return;
157 			} else if (verbose && ent->hours <= 0) {
158 				printf("--> time is up\n");
159 			}
160 		}
161                 if (verbose && (ent->size > 0))
162                         printf("size (Kb): %d [%d] ", size, ent->size);
163                 if (verbose && (ent->hours > 0))
164                         printf(" age (hr): %d [%d] ", modtime, ent->hours);
165                 if (force || ((ent->size > 0) && (size >= ent->size)) ||
166 		    (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) ||
167                     ((ent->hours > 0) && ((modtime >= ent->hours)
168                                         || (modtime < 0)))) {
169                         if (verbose)
170                                 printf("--> trimming log....\n");
171                         if (noaction && !verbose) {
172                                 if (ent->flags & CE_COMPACT)
173                                         printf("%s <%dZ>: trimming\n",
174                                                ent->log,ent->numlogs);
175                                 else
176                                         printf("%s <%d>: trimming\n",
177                                                ent->log,ent->numlogs);
178                         }
179 			if (ent->pid_file) {
180 				pid_file = ent->pid_file;
181 			} else {
182 				/* Only try to notify syslog if we are root */
183 				if (needroot)
184 					pid_file = _PATH_SYSLOGPID;
185 				else
186 					pid_file = NULL;
187 			}
188                         dotrim(ent->log, pid_file, ent->numlogs,
189 			    ent->flags, ent->permissions, ent->uid, ent->gid, ent->sig);
190                 } else {
191                         if (verbose)
192                                 printf("--> skipping\n");
193                 }
194         }
195 }
196 
197 static void PRS(argc,argv)
198         int argc;
199         char **argv;
200 {
201         int     c;
202 	char	*p;
203 
204         timenow = time((time_t *) 0);
205         daytime = ctime(&timenow) + 4;
206         daytime[15] = '\0';
207 
208         /* Let's get our hostname */
209         (void) gethostname(hostname, sizeof(hostname));
210 
211 	/* Truncate domain */
212 	if ((p = strchr(hostname, '.'))) {
213 		*p = '\0';
214 	}
215 
216         optind = 1;             /* Start options parsing */
217         while ((c=getopt(argc,argv,"nrvFf:t:")) != -1)
218                 switch (c) {
219                 case 'n':
220                         noaction++;
221 			break;
222                 case 'r':
223                         needroot = 0;
224                         break;
225                 case 'v':
226                         verbose++;
227                         break;
228                 case 'f':
229                         conf = optarg;
230                         break;
231 		case 'F':
232 			force++;
233 			break;
234                 default:
235                         usage();
236                 }
237 }
238 
239 static void usage()
240 {
241         fprintf(stderr, "usage: newsyslog [-Fnrv] [-f config-file]\n");
242         exit(1);
243 }
244 
245 /* Parse a configuration file and return a linked list of all the logs
246  * to process
247  */
248 static struct conf_entry *parse_file()
249 {
250         FILE    *f;
251         char    line[BUFSIZ], *parse, *q;
252         char    *errline, *group;
253         struct conf_entry *first = NULL;
254         struct conf_entry *working = NULL;
255         struct passwd *pass;
256         struct group *grp;
257 	int eol;
258 
259         if (strcmp(conf,"-"))
260                 f = fopen(conf,"r");
261         else
262                 f = stdin;
263         if (!f)
264                 err(1, "%s", conf);
265         while (fgets(line,BUFSIZ,f)) {
266                 if ((line[0]== '\n') || (line[0] == '#'))
267                         continue;
268                 errline = strdup(line);
269                 if (!first) {
270                         working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
271                         first = working;
272                 } else {
273                         working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
274                         working = working->next;
275                 }
276 
277                 q = parse = missing_field(sob(line),errline);
278                 parse = son(line);
279 		if (!*parse)
280                   errx(1, "malformed line (missing fields):\n%s", errline);
281                 *parse = '\0';
282                 working->log = strdup(q);
283 
284                 q = parse = missing_field(sob(++parse),errline);
285                 parse = son(parse);
286 		if (!*parse)
287                   errx(1, "malformed line (missing fields):\n%s", errline);
288                 *parse = '\0';
289                 if ((group = strchr(q, ':')) != NULL ||
290                     (group = strrchr(q, '.')) != NULL) {
291                     *group++ = '\0';
292                     if (*q) {
293                         if (!(isnumber(*q))) {
294                             if ((pass = getpwnam(q)) == NULL)
295                                 errx(1,
296                                   "error in config file; unknown user:\n%s",
297                                   errline);
298                             working->uid = pass->pw_uid;
299                         } else
300                             working->uid = atoi(q);
301                     } else
302                         working->uid = NONE;
303 
304                     q = group;
305                     if (*q) {
306                         if (!(isnumber(*q))) {
307                             if ((grp = getgrnam(q)) == NULL)
308                                 errx(1,
309                                   "error in config file; unknown group:\n%s",
310                                   errline);
311                             working->gid = grp->gr_gid;
312                         } else
313                             working->gid = atoi(q);
314                     } else
315                         working->gid = NONE;
316 
317                     q = parse = missing_field(sob(++parse),errline);
318                     parse = son(parse);
319 		    if (!*parse)
320                       errx(1, "malformed line (missing fields):\n%s", errline);
321                     *parse = '\0';
322                 }
323                 else
324                     working->uid = working->gid = NONE;
325 
326                 if (!sscanf(q,"%o",&working->permissions))
327                         errx(1, "error in config file; bad permissions:\n%s",
328                           errline);
329 
330                 q = parse = missing_field(sob(++parse),errline);
331                 parse = son(parse);
332 		if (!*parse)
333                   errx(1, "malformed line (missing fields):\n%s", errline);
334                 *parse = '\0';
335                 if (!sscanf(q,"%d",&working->numlogs))
336                         errx(1, "error in config file; bad number:\n%s",
337                           errline);
338 
339                 q = parse = missing_field(sob(++parse),errline);
340                 parse = son(parse);
341 		if (!*parse)
342                   errx(1, "malformed line (missing fields):\n%s", errline);
343                 *parse = '\0';
344                 if (isdigit(*q))
345                         working->size = atoi(q);
346                 else
347                         working->size = -1;
348 
349                 working->flags = 0;
350                 q = parse = missing_field(sob(++parse),errline);
351                 parse = son(parse);
352 		eol = !*parse;
353                 *parse = '\0';
354 		{
355 			char	*ep;
356 			u_long	ul;
357 
358 			ul = strtoul(q, &ep, 10);
359 			if (ep == q)
360 				working->hours = 0;
361 			else if (*ep == '*')
362 				working->hours = -1;
363 			else if (ul > INT_MAX)
364 				errx(1, "interval is too large:\n%s", errline);
365 			else
366 				working->hours = ul;
367 
368 			if (*ep != '\0' && *ep != '@' && *ep != '*')
369 				errx(1, "malformed interval/at:\n%s", errline);
370 			if (*ep == '@') {
371 				if ((working->trim_at = parse8601(ep + 1))
372 				    == (time_t)-1)
373 					errx(1, "malformed at:\n%s", errline);
374 				working->flags |= CE_TRIMAT;
375 			}
376 		}
377 
378 		if (eol)
379 		  q = NULL;
380 		else {
381                   q = parse = sob(++parse); /* Optional field */
382                   parse = son(parse);
383 		  if (!*parse)
384                     eol = 1;
385                   *parse = '\0';
386 		}
387 
388                 while (q && *q && !isspace(*q)) {
389                         if ((*q == 'Z') || (*q == 'z'))
390                                 working->flags |= CE_COMPACT;
391                         else if ((*q == 'B') || (*q == 'b'))
392                                 working->flags |= CE_BINARY;
393                         else if (*q != '-')
394                            errx(1, "illegal flag in config file -- %c", *q);
395                         q++;
396                 }
397 
398 		if (eol)
399 		  q = NULL;
400 		else {
401 		  q = parse = sob(++parse); /* Optional field */
402                   parse = son(parse);
403 		  if (!*parse)
404                     eol = 1;
405                   *parse = '\0';
406 		}
407 
408 		working->pid_file = NULL;
409 		if (q && *q) {
410 			if (*q == '/')
411 				working->pid_file = strdup(q);
412 			else if (isdigit(*q))
413 				goto got_sig;
414                         else
415 			   errx(1, "illegal pid file or signal number in config file:\n%s", errline);
416 		}
417 
418 		if (eol)
419 		  q = NULL;
420 		else {
421 		  q = parse = sob(++parse); /* Optional field */
422 		  *(parse = son(parse)) = '\0';
423 		}
424 
425 		working->sig = SIGHUP;
426 		if (q && *q) {
427 			if (isdigit(*q)) {
428 			got_sig:
429 				working->sig = atoi(q);
430 			} else {
431 			err_sig:
432 			   errx(1, "illegal signal number in config file:\n%s", errline);
433 			}
434 			if (working->sig < 1 || working->sig >= NSIG)
435 				goto err_sig;
436 		}
437 
438                 free(errline);
439         }
440         if (working)
441                 working->next = (struct conf_entry *) NULL;
442         (void) fclose(f);
443         return(first);
444 }
445 
446 static char *missing_field(p,errline)
447         char    *p,*errline;
448 {
449         if (!p || !*p)
450             errx(1, "missing field in config file:\n%s", errline);
451         return(p);
452 }
453 
454 static void dotrim(log,pid_file,numdays,flags,perm,owner_uid,group_gid,sig)
455         char    *log;
456 	char    *pid_file;
457         int     numdays;
458         int     flags;
459         int     perm;
460         int     owner_uid;
461         int     group_gid;
462 	int     sig;
463 {
464         char    file1 [MAXPATHLEN+1], file2 [MAXPATHLEN+1];
465         char    zfile1[MAXPATHLEN+1], zfile2[MAXPATHLEN+1];
466 	int     notified, need_notification, fd, _numdays;
467         struct  stat st;
468 	pid_t   pid;
469 
470 #ifdef _IBMR2
471 /* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
472 /* change it to be owned by uid -1, instead of leaving it as is, as it is */
473 /* supposed to. */
474                 if (owner_uid == -1)
475                   owner_uid = geteuid();
476 #endif
477 
478         /* Remove oldest log */
479         (void) sprintf(file1,"%s.%d",log,numdays);
480         (void) strcpy(zfile1, file1);
481         (void) strcat(zfile1, COMPRESS_POSTFIX);
482 
483         if (noaction) {
484                 printf("rm -f %s\n", file1);
485                 printf("rm -f %s\n", zfile1);
486         } else {
487                 (void) unlink(file1);
488                 (void) unlink(zfile1);
489         }
490 
491         /* Move down log files */
492 	_numdays = numdays;	/* preserve */
493         while (numdays--) {
494                 (void) strcpy(file2,file1);
495                 (void) sprintf(file1,"%s.%d",log,numdays);
496                 (void) strcpy(zfile1, file1);
497                 (void) strcpy(zfile2, file2);
498                 if (lstat(file1, &st)) {
499                         (void) strcat(zfile1, COMPRESS_POSTFIX);
500                         (void) strcat(zfile2, COMPRESS_POSTFIX);
501 			if (lstat(zfile1, &st)) continue;
502                 }
503                 if (noaction) {
504                         printf("mv %s %s\n",zfile1,zfile2);
505                         printf("chmod %o %s\n", perm, zfile2);
506                         printf("chown %d.%d %s\n",
507                                owner_uid, group_gid, zfile2);
508                 } else {
509                         (void) rename(zfile1, zfile2);
510                         (void) chmod(zfile2, perm);
511                         (void) chown(zfile2, owner_uid, group_gid);
512                 }
513         }
514         if (!noaction && !(flags & CE_BINARY))
515                 (void) log_trim(log);  /* Report the trimming to the old log */
516 
517 	if (!_numdays) {
518 		if (noaction)
519 			printf("rm %s\n",log);
520 		else
521 			(void)unlink(log);
522 	}
523 	else {
524 		if (noaction)
525 			printf("mv %s to %s\n",log,file1);
526 		else
527 			(void)rename(log, file1);
528 	}
529 
530         if (noaction)
531                 printf("Start new log...");
532         else {
533                 fd = creat(log,perm);
534                 if (fd < 0)
535                         err(1, "can't start new log");
536                 if (fchown(fd, owner_uid, group_gid))
537                         err(1, "can't chmod new log file");
538                 (void) close(fd);
539                 if (!(flags & CE_BINARY))
540                         if (log_trim(log))    /* Add status message */
541                              err(1, "can't add status message to log");
542         }
543         if (noaction)
544                 printf("chmod %o %s...\n", perm, log);
545         else
546                 (void) chmod(log,perm);
547 
548 	pid = 0;
549 	need_notification = notified = 0;
550 	if (pid_file != NULL) {
551 		need_notification = 1;
552 		pid = get_pid(pid_file);
553 	}
554 
555 	if (pid) {
556 		if (noaction) {
557 			notified = 1;
558 			printf("kill -%d %d\n", sig, (int)pid);
559 		} else if (kill(pid,sig))
560 			warn("can't notify daemon, pid %d", (int)pid);
561 		else {
562 			notified = 1;
563 			if (verbose)
564 				printf("daemon pid %d notified\n", (int)pid);
565 		}
566 	}
567 
568 	if ((flags & CE_COMPACT)) {
569 		if (need_notification && !notified)
570 			warnx("log not compressed because daemon not notified");
571 		else if (noaction)
572                         printf("Compress %s.0\n",log);
573 		else {
574 			if (notified) {
575 				if (verbose)
576 					printf("small pause to allow daemon to close log\n");
577 				sleep(10);
578 			}
579                         compress_log(log);
580 		}
581         }
582 }
583 
584 /* Log the fact that the logs were turned over */
585 static int log_trim(log)
586         char    *log;
587 {
588         FILE    *f;
589         if ((f = fopen(log,"a")) == NULL)
590                 return(-1);
591         fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
592                 daytime, hostname, (int)getpid());
593         if (fclose(f) == EOF)
594                 err(1, "log_trim: fclose:");
595         return(0);
596 }
597 
598 /* Fork of /usr/ucb/compress to compress the old log file */
599 static void compress_log(log)
600         char    *log;
601 {
602 	pid_t   pid;
603 	char    tmp[MAXPATHLEN+1];
604 
605         (void) sprintf(tmp,"%s.0",log);
606 	pid = fork();
607         if (pid < 0)
608                 err(1, "fork");
609         else if (!pid) {
610 		(void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, 0);
611 		err(1, _PATH_GZIP);
612         }
613 }
614 
615 /* Return size in kilobytes of a file */
616 static int sizefile(file)
617         char    *file;
618 {
619         struct stat sb;
620 
621         if (stat(file,&sb) < 0)
622                 return(-1);
623         return(kbytes(dbtob(sb.st_blocks)));
624 }
625 
626 /* Return the age of old log file (file.0) */
627 static int age_old_log(file)
628         char    *file;
629 {
630         struct stat sb;
631         char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1];
632 
633         (void) strcpy(tmp,file);
634         if (stat(strcat(tmp,".0"),&sb) < 0)
635             if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
636                 return(-1);
637         return( (int) (timenow - sb.st_mtime + 1800) / 3600);
638 }
639 
640 static pid_t get_pid(pid_file)
641 	char *pid_file;
642 {
643 	FILE *f;
644 	char  line[BUFSIZ];
645 	pid_t pid = 0;
646 
647 	if ((f = fopen(pid_file,"r")) == NULL)
648 		warn("can't open %s pid file to restart a daemon",
649 			pid_file);
650 	else {
651 		if (fgets(line,BUFSIZ,f)) {
652 			pid = atol(line);
653 			if (pid < MIN_PID || pid > MAX_PID) {
654 				warnx("preposterous process number: %d", (int)pid);
655 				pid = 0;
656 			}
657 		} else
658 			warn("can't read %s pid file to restart a daemon",
659 				pid_file);
660 		(void)fclose(f);
661 	}
662 	return pid;
663 }
664 
665 /* Skip Over Blanks */
666 char *sob(p)
667 	register char   *p;
668 {
669         while (p && *p && isspace(*p))
670                 p++;
671         return(p);
672 }
673 
674 /* Skip Over Non-Blanks */
675 char *son(p)
676 	register char   *p;
677 {
678         while (p && *p && !isspace(*p))
679                 p++;
680         return(p);
681 }
682 
683 /*
684  * Parse a limited subset of ISO 8601.
685  * The specific format is as follows:
686  *
687  *	[CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
688  *
689  * We don't accept a timezone specification; missing fields (including
690  * timezone) are defaulted to the current date but time zero.
691  */
692 static time_t
693 parse8601(const char *s)
694 {
695 	char		*t;
696 	struct tm	tm, *tmp;
697 	u_long		ul;
698 
699 	tmp = localtime(&timenow);
700 	tm = *tmp;
701 
702 	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
703 
704 	ul = strtoul(s, &t, 10);
705 	if (*t != '\0' && *t != 'T')
706 		return -1;
707 
708 	/*
709 	 * Now t points either to the end of the string (if no time
710 	 * was provided) or to the letter `T' which separates date
711 	 * and time in ISO 8601.  The pointer arithmetic is the same for
712 	 * either case.
713 	 */
714 	switch (t - s) {
715 	case 8:
716 		tm.tm_year = ((ul / 1000000) - 19) * 100;
717 		ul = ul % 1000000;
718 	case 6:
719 		tm.tm_year = tm.tm_year - (tm.tm_year % 100);
720 		tm.tm_year += ul / 10000;
721 		ul = ul % 10000;
722 	case 4:
723 		tm.tm_mon = (ul / 100) - 1;
724 		ul = ul % 100;
725 	case 2:
726 		tm.tm_mday = ul;
727 	case 0:
728 		break;
729 	default:
730 		return -1;
731 	}
732 
733 	/* sanity check */
734 	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
735 	    || tm.tm_mday < 1 || tm.tm_mday > 31)
736 		return -1;
737 
738 	if (*t != '\0') {
739 		s = ++t;
740 		ul = strtoul(s, &t, 10);
741 		if (*t != '\0' && !isspace(*t))
742 			return -1;
743 
744 		switch (t - s) {
745 		case 6:
746 			tm.tm_sec = ul % 100;
747 			ul /= 100;
748 		case 4:
749 			tm.tm_min = ul % 100;
750 			ul /= 100;
751 		case 2:
752 			tm.tm_hour = ul;
753 		case 0:
754 			break;
755 		default:
756 			return -1;
757 		}
758 
759 		/* sanity check */
760 		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
761 		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
762 			return -1;
763 	}
764 
765 	return mktime(&tm);
766 }
767 
768 
769