1 /* Copyright (C) 2000-2016 Boris Wesslowski */
2 /* $Id: modes.c 741 2016-02-19 14:35:50Z bw $ */
3 
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <unistd.h>
7 #include <string.h>
8 #include <sys/time.h>
9 #include <errno.h>
10 #include <pwd.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <fcntl.h>
14 #include <syslog.h>
15 #include <signal.h>
16 
17 #ifdef HAVE_ZLIB
18 #include <zlib.h>
19 #endif
20 
21 #ifdef HAVE_ADNS
22 #include <adns.h>
23 #endif
24 
25 #include "parser.h"
26 #include "output.h"
27 #include "compare.h"
28 #include "response.h"
29 #include "utils.h"
30 #include "net.h"
31 #include "whois.h"
32 #include "rcfile.h"
33 
34 #ifdef HAVE_ADNS
35 #include "resolve.h"
36 #endif
37 
38 extern struct options opt;
39 extern struct conn_data *first;
40 extern struct input_file *first_file;
41 
42 #ifdef HAVE_ADNS
43 extern adns_state adns;
44 #endif
45 
common_input_loop(int * linenum,int * hitnum,int * errnum,int * oldnum,int * exnum)46 void common_input_loop(int *linenum, int *hitnum, int *errnum, int *oldnum, int *exnum)
47 {
48   char buf[BUFSIZE];
49   int retval, hit = 0;
50 
51 #ifdef HAVE_ZLIB
52   if ((opt.std_in) || (opt.mode == REALTIME_RESPONSE)) {
53 #endif
54     retval = (fgets(buf, BUFSIZE, opt.inputfd) != NULL);
55 #ifdef HAVE_ZLIB
56   } else {
57     retval = (gzgets(opt.gzinputfd, buf, BUFSIZE) != Z_NULL);
58   }
59 #endif
60 
61   while (retval) {
62     *linenum += 1;
63     hit = parse_line(buf, *linenum);
64     opt.repeated = 0;
65     switch (hit) {
66     case PARSE_OK:
67       *hitnum += 1;
68       opt.repeated = 1;
69       break;
70     case PARSE_WRONG_FORMAT:
71       *errnum += 1;
72       break;
73     case PARSE_TOO_OLD:
74       *oldnum += 1;
75       break;
76     case PARSE_EXCLUDED:
77       *hitnum += 1;
78       *exnum += 1;
79     }
80 
81 #ifdef HAVE_ZLIB
82     if ((opt.std_in) || (opt.mode == REALTIME_RESPONSE)) {
83 #endif
84       retval = (fgets(buf, BUFSIZE, opt.inputfd) != NULL);
85 #ifdef HAVE_ZLIB
86     } else {
87       retval = (gzgets(opt.gzinputfd, buf, BUFSIZE) != Z_NULL);
88     }
89 #endif
90   }
91 }
92 
mode_summary()93 void mode_summary()
94 {
95   char nows[TIMESIZE], first_entry[TIMESIZE], last_entry[TIMESIZE], *input = NULL, last_file = 0;
96   FILE *output = NULL;
97   int retval, linenum = 0, hitnum = 0, errnum = 0, old_errnum = 0, oldnum = 0, exnum = 0;
98   time_t now;
99   struct passwd *gen_user;
100   struct input_file *file;
101 
102   opt.line = xmalloc(sizeof(struct log_line));
103 
104   file = first_file;
105   while (last_file == 0) {
106     if (opt.std_in) {
107       if (opt.verbose)
108 	fprintf(stderr, _("Using stdin as input\n"));
109 
110       opt.inputfd = stdin;
111     } else {
112       input = file->name;
113       if (opt.verbose)
114 	fprintf(stderr, _("Opening input file '%s'\n"), input);
115 
116 #ifdef HAVE_ZLIB
117       opt.gzinputfd = gzopen(input, "rb");
118       if (opt.gzinputfd == NULL) {
119 	fprintf(stderr, "gzopen %s: %s\n", input, strerror(errno));
120 #else
121       opt.inputfd = fopen(input, "r");
122       if (opt.inputfd == NULL) {
123 	fprintf(stderr, "fopen %s: %s\n", input, strerror(errno));
124 #endif
125 	exit(EXIT_FAILURE);
126       }
127     }
128 
129     if (opt.verbose)
130       fprintf(stderr, _("Processing\n"));
131 
132     common_input_loop(&linenum, &hitnum, &errnum, &oldnum, &exnum);
133 
134     if (opt.verbose == 2)
135       fprintf(stderr, "\n");
136     if (opt.verbose && (errnum > old_errnum)) {
137       fprintf(stderr, _("Unrecognized entries or tokens can be submitted at\n"));
138       fprintf(stderr, "http://fwlogwatch.inside-security.de/unrecognized.php\n");
139       old_errnum = errnum;
140     }
141 
142     if (opt.std_in) {
143       last_file++;
144     } else {
145       if (opt.verbose)
146 	fprintf(stderr, _("Closing '%s'\n"), input);
147 
148 #ifndef HAVE_ZLIB
149       retval = fclose(opt.inputfd);
150       if (retval == EOF) {
151 	perror("fclose");
152 #else
153       retval = gzclose(opt.gzinputfd);
154       if (retval != 0) {
155 	if (retval != Z_ERRNO) {
156 	  fprintf(stderr, "gzclose %s: %s\n", input, gzerror(opt.gzinputfd, &retval));
157 	} else {
158 	  perror("gzclose");
159 	}
160 #endif
161 	exit(EXIT_FAILURE);
162       }
163 
164       if (file->next != NULL) {
165 	file = file->next;
166       } else {
167 	last_file++;
168       }
169     }
170   }
171 
172   free(opt.line);
173 
174   if (opt.verbose)
175     fprintf(stderr, _("Sorting data\n"));
176 
177   if (first != NULL) {
178     time_t last_time;
179     struct conn_data *p;
180 
181     opt.sortfield = SORT_END_TIME;
182     opt.sortmode = ORDER_DESCENDING;
183     first = fwlw_pc_mergesort(first);
184     if (opt.verbose == 2)
185       fprintf(stderr, ".");
186     last_time = first->end_time;
187 
188     opt.sortfield = SORT_START_TIME;
189     opt.sortmode = ORDER_ASCENDING;
190     first = fwlw_pc_mergesort(first);
191     if (opt.verbose == 2)
192       fprintf(stderr, ".");
193     strftime(first_entry, TIMESIZE, _("%b %d %H:%M:%S"), localtime(&first->start_time));
194 
195     p = first;
196     while (p->next != NULL)
197       p = p->next;
198     if (p->start_time > last_time)
199       last_time = p->start_time;
200     strftime(last_entry, TIMESIZE, _("%b %d %H:%M:%S"), localtime(&last_time));
201   } else {
202     first_entry[0] = '\0';
203   }
204 
205   sort_data(SORT_PC);
206 
207   if (opt.verbose == 2)
208     fprintf(stderr, "\n");
209 
210   if (opt.use_out) {
211     if (opt.verbose)
212       fprintf(stderr, _("Opening output file '%s'\n"), opt.outputfile);
213 
214     output = freopen(opt.outputfile, "w", stdout);
215     if (output == NULL) {
216       fprintf(stderr, "freopen %s: %s\n", opt.outputfile, strerror(errno));
217       exit(EXIT_FAILURE);
218     }
219   } else if (opt.recipient[0] != '\0') {
220     char buf[BUFSIZE];
221 
222     if (opt.verbose)
223       fprintf(stderr, _("Sending\n"));
224 
225     snprintf(buf, BUFSIZE, "%s -t", P_SENDMAIL);
226     output = popen(buf, "w");
227     if (output == NULL) {
228       perror("popen");
229       exit(EXIT_FAILURE);
230     }
231 
232     generate_email_header(output);
233     fflush(output);
234   } else {
235     output = stdout;
236   }
237 
238   if (opt.html) {
239     output_html_header(fileno(output));
240     fprintf(output, "<p>\n");
241   } else {
242     fprintf(output, "%s\n", opt.title);
243   }
244 
245   now = time(NULL);
246   strftime(nows, TIMESIZE, _("%A %B %d %H:%M:%S %Z %Y"), localtime(&now));
247   fprintf(output, _("Generated %s by "), nows);
248 
249   gen_user = getpwuid(getuid());
250   if (gen_user != NULL) {
251     if (gen_user->pw_gecos[0] != '\0') {
252       fprintf(output, "%s.\n", gen_user->pw_gecos);
253     } else {
254       fprintf(output, "%s.\n", gen_user->pw_name);
255     }
256   } else {
257     fprintf(output, _("an unknown user.\n"));
258   }
259 
260   if (opt.html)
261     fprintf(output, "<br />\n");
262 
263   fprintf(output, "%d ", hitnum);
264   if (oldnum > 0) {
265     fprintf(output, _("(and %d older than %d seconds) "), oldnum, opt.recent);
266   }
267   if (errnum > 0) {
268     fprintf(output, _("(and %d malformed) "), errnum);
269   }
270   if (opt.filecount == 1) {
271     fprintf(output, _("of %d entries in the file \"%s\" are packet logs, "), linenum, input);
272   } else if (opt.filecount == 0) {
273     fprintf(output, _("of %d entries in standard input are packet logs, "), linenum);
274   } else {
275     fprintf(output, _("of %d entries in %d input files are packet logs, "), linenum, opt.filecount);
276   }
277   retval = list_stats();
278   if (retval == 1) {
279     fprintf(output, _("one has unique characteristics.\n"));
280   } else {
281     fprintf(output, _("%d have unique characteristics.\n"), retval);
282   }
283 
284   if (exnum != 0) {
285     if (opt.html)
286       fprintf(output, "<br />\n");
287 
288     if (exnum == 1) {
289       fprintf(output, _("One entry was excluded by configuration.\n"));
290     } else {
291       fprintf(output, _("%d entries were excluded by configuration.\n"), exnum);
292     }
293   }
294 
295   if (opt.html)
296     fprintf(output, "<br />\n");
297 
298   if (first_entry[0] != '\0') {
299     fprintf(output, _("First packet log entry: %s, last: %s.\n"), first_entry, last_entry);
300   } else {
301     fprintf(output, _("No valid time entries found.\n"));
302   }
303 
304   if (!opt.loghost) {
305     if (opt.html)
306       fprintf(output, "<br />\n");
307 
308     fprintf(output, _("All entries were logged by the same host: \"%s\".\n"), opt.hostname);
309   }
310 
311   if (!opt.chains) {
312     if (opt.html)
313       fprintf(output, "<br />\n");
314 
315     fprintf(output, _("All entries are from the same chain: \"%s\".\n"), opt.chainlabel);
316   }
317 
318   if (!opt.branches) {
319     if (opt.html)
320       fprintf(output, "<br />\n");
321 
322     fprintf(output, _("All entries have the same target: \"%s\".\n"), opt.branchname);
323   }
324 
325   if (!opt.ifs) {
326     if (opt.html)
327       fprintf(output, "<br />\n");
328 
329     fprintf(output, _("All entries are from the same interface: \"%s\".\n"), opt.interface);
330   }
331 
332   if (opt.least > 1) {
333     if (opt.html)
334       fprintf(output, "<br />\n");
335 
336     fprintf(output, _("Only entries with a count of at least %d are shown.\n"), opt.least);
337   }
338 
339   if (opt.max) {
340     if (opt.html)
341       fprintf(output, "<br />\n");
342 
343     fprintf(output, _("Only the top %d entries are shown.\n"), opt.max);
344   }
345 
346   if (opt.html)
347     output_html_table(output);
348   else
349     fprintf(output, "\n");
350 
351 #ifdef HAVE_ADNS
352   if (opt.resolve) {
353     if (opt.verbose)
354       fprintf(stderr, _("Resolving\n"));
355 
356     retval = adns_init(&adns, adns_if_none, 0);
357     if (retval) {
358       perror("adns_init");
359       exit(EXIT_FAILURE);
360     }
361     adns_preresolve(RES_ADNS_PC);
362   }
363 #endif
364 
365   if (opt.whois_lookup)
366     whois_connect(RADB);
367 
368   show_list(output);
369   fflush(output);
370 
371   if (opt.whois_lookup)
372     whois_close();
373 
374 #ifdef HAVE_ADNS
375   if (opt.resolve)
376     adns_finish(adns);
377 #endif
378 
379   if (opt.html) {
380     fprintf(output, "</table>\n");
381     fflush(output);
382     output_html_footer(fileno(output));
383   }
384 
385   free_conn_data();
386   free_dns_cache();
387   free_whois();
388   free_exclude_data();
389   free_input_file();
390 
391   if (opt.use_out) {
392     if (opt.verbose)
393       fprintf(stderr, _("Closing '%s'\n"), opt.outputfile);
394 
395     retval = fclose(output);
396     if (retval == EOF) {
397       perror("fclose");
398     }
399   } else if (opt.recipient[0] != '\0') {
400     retval = pclose(output);
401     if (retval == -1) {
402       perror("pclose");
403     }
404   }
405 }
406 
407 void check_pidfile()
408 {
409   struct stat *sbuf;
410 
411   sbuf = xmalloc(sizeof(struct stat));
412   if (stat(opt.pidfile, sbuf) != -1) {
413     fprintf(stderr, _("Warning: pidfile exists, another fwlogwatch might be running.\n"));
414   } else {
415     if ((errno != ENOENT) && (errno != EACCES)) {
416       fprintf(stderr, "stat %s: %d, %s\n", opt.pidfile, errno, strerror(errno));
417       exit(EXIT_FAILURE);
418     }
419   }
420   free(sbuf);
421 }
422 
423 void mode_rt_response_reread_conf()
424 {
425   free_exclude_data();
426   if (read_rcfile(opt.rcfile, MAY_NOT_EXIST, RCFILE_CF) == EXIT_SUCCESS) {
427     syslog(LOG_NOTICE, _("SIGHUP caught, reread configuration file %s"), opt.rcfile);
428   } else {
429     syslog(LOG_NOTICE, _("SIGHUP caught, unable to reread configuration file %s"), opt.rcfile);
430   }
431   if (opt.rcfile_dns[0] != '\0') {
432     if (read_rcfile(opt.rcfile_dns, MAY_NOT_EXIST, RCFILE_DNS) == EXIT_SUCCESS) {
433       syslog(LOG_NOTICE, _("Reread DNS cache file %s"), opt.rcfile_dns);
434     } else {
435       syslog(LOG_NOTICE, _("Unable to reread DNS cache file %s"), opt.rcfile_dns);
436     }
437   }
438   signal(SIGHUP, mode_rt_response_reread_conf);
439 }
440 
441 void mode_rt_response_open()
442 {
443   if (opt.std_in) {
444     opt.inputfd = stdin;
445   } else {
446     opt.inputfd = fopen(first_file->name, "r");
447     if (opt.inputfd == NULL) {
448       syslog(LOG_NOTICE, "fopen %s: %s", first_file->name, strerror(errno));
449       log_exit(EXIT_FAILURE);
450     }
451   }
452 }
453 
454 void mode_rt_response_reopen_log()
455 {
456   int retval;
457 
458   if (opt.std_in) {
459     syslog(LOG_NOTICE, _("SIGUSR1 caught, reading input from stdin, no need to reopen log file"));
460   } else {
461     syslog(LOG_NOTICE, _("SIGUSR1 caught, reopening log file %s"), first_file->name);
462 
463     retval = fclose(opt.inputfd);
464     if (retval == EOF)
465       syslog(LOG_NOTICE, "fclose %s: %s", first_file->name, strerror(errno));
466 
467     mode_rt_response_open();
468     signal(SIGUSR1, mode_rt_response_reopen_log);
469   }
470 }
471 
472 void mode_rt_response_core()
473 {
474   int retval, linenum = 0, hitnum = 0, ignored = 0;
475   struct stat info;
476   off_t size = 0;
477   fd_set rfds;
478   struct timeval tv;
479 
480   if ((!opt.std_in) && (!opt.stateful_start)) {
481     retval = fstat(fileno(opt.inputfd), &info);
482     if (retval == -1) {
483       syslog(LOG_NOTICE, "fstat %s: %s", first_file->name, strerror(errno));
484       log_exit(EXIT_FAILURE);
485     }
486     size = info.st_size;
487   }
488 
489   opt.line = xmalloc(sizeof(struct log_line));
490 
491   while (1) {
492     if (opt.status) {
493       FD_ZERO(&rfds);
494       FD_SET(opt.sock, &rfds);
495       tv.tv_sec = 1;
496       tv.tv_usec = 0;
497       retval = select(opt.sock + 1, &rfds, NULL, NULL, &tv);
498       if (retval == -1) {
499 	if (errno != EINTR) {
500 	  syslog(LOG_NOTICE, "select: %s", strerror(errno));
501 	  exit(EXIT_FAILURE);
502 	}
503       }
504       if (retval > 0) {
505 	handshake(linenum, hitnum, ignored);
506       }
507     } else {
508       sleep(1);
509     }
510 
511     remove_old(RESP_REMOVE_OPC | RESP_REMOVE_OHS);
512     if (opt.std_in) {
513       common_input_loop(&linenum, &hitnum, &ignored, &ignored, &ignored);
514       look_for_alert();
515     } else {
516       retval = fstat(fileno(opt.inputfd), &info);
517       if (retval == -1) {
518 	syslog(LOG_NOTICE, "fstat %s: %s", first_file->name, strerror(errno));
519 	log_exit(EXIT_FAILURE);
520       }
521       if (size != info.st_size) {
522 	size = info.st_size;
523 	clearerr(opt.inputfd);
524 	common_input_loop(&linenum, &hitnum, &ignored, &ignored, &ignored);
525 	look_for_alert();
526       }
527     }
528   }
529 }
530 
531 void mode_rt_response_terminate()
532 {
533   syslog(LOG_NOTICE, _("SIGTERM caught, cleaning up"));
534   free_hosts();
535   if (opt.response & OPT_RESPOND)
536     modify_firewall(FW_STOP);
537   log_exit(EXIT_SUCCESS);
538 }
539 
540 void mode_rt_response()
541 {
542   int retval;
543   FILE *pidfile;
544 #ifndef RR_DEBUG
545   pid_t pid;
546 
547   if (opt.pidfile[0] != '\0')
548     check_pidfile();
549 
550   pid = fork();
551   if (pid == -1) {
552     perror("fork");
553     exit(EXIT_FAILURE);
554   }
555   if (pid != 0) {
556     _exit(EXIT_SUCCESS);
557   }
558   pid = setsid();
559   if (pid == -1) {
560     perror("setsid");
561     exit(EXIT_FAILURE);
562   }
563   pid = fork();
564   if (pid == -1) {
565     perror("fork");
566     exit(EXIT_FAILURE);
567   }
568   if (pid != 0) {
569     _exit(EXIT_SUCCESS);
570   }
571   retval = chdir("/");
572   if (retval == -1) {
573     perror("chdir");
574     exit(EXIT_FAILURE);
575   }
576   /* umask() */
577   retval = close(2);
578   if (retval == -1) {
579     perror("close");
580     exit(EXIT_FAILURE);
581   }
582   retval = close(1);
583   if (retval == -1) {
584     perror("close");
585     exit(EXIT_FAILURE);
586   }
587   if (!opt.std_in) {
588     retval = close(0);
589     if (retval == -1) {
590       perror("close");
591       exit(EXIT_FAILURE);
592     }
593   }
594   retval = open("/dev/null", O_RDWR);
595   if (retval == -1) {
596     perror("open");
597     exit(EXIT_FAILURE);
598   }
599   retval = dup(0);
600   if (retval == -1) {
601     perror("dup");
602     exit(EXIT_FAILURE);
603   }
604   if (!opt.std_in) {
605     retval = dup(0);
606     if (retval == -1) {
607       perror("dup");
608       exit(EXIT_FAILURE);
609     }
610   }
611   openlog("fwlogwatch", LOG_CONS, LOG_DAEMON);
612 #else
613   openlog("fwlogwatch", LOG_CONS | LOG_PERROR, LOG_DAEMON);
614 #endif
615   syslog(LOG_NOTICE, _("Starting (pid %d)"), getpid());
616 
617   signal(SIGTERM, mode_rt_response_terminate);
618 
619   if (opt.pidfile[0] != '\0') {
620     pidfile = fopen(opt.pidfile, "w");
621     if (pidfile == NULL) {
622       syslog(LOG_NOTICE, "fopen %s: %s\n", opt.pidfile, strerror(errno));
623     } else {
624       fprintf(pidfile, "%d\n", (int) getpid());
625       retval = fclose(pidfile);
626       if (retval == EOF) {
627 	syslog(LOG_NOTICE, "fclose %s: %s\n", opt.pidfile, strerror(errno));
628       }
629     }
630   }
631 
632   if (opt.status) {
633     prepare_socket();
634 #ifdef HAVE_ADNS
635     if (opt.resolve) {
636       retval = adns_init(&adns, adns_if_none, 0);
637       if (retval) {
638 	syslog(LOG_NOTICE, "adns_init: %s", strerror(errno));
639 	log_exit(EXIT_FAILURE);
640       }
641     }
642 #endif
643   }
644 
645   if ((opt.ipchains_check == 1) && ((opt.format & PARSER_IPCHAINS) != 0))
646     check_for_ipchains();
647 
648   if ((opt.response & OPT_NOTIFY) != 0)
649     check_script_perms(opt.notify_script);
650 
651   if ((opt.response & OPT_RESPOND) != 0) {
652     check_script_perms(opt.respond_script);
653     modify_firewall(FW_START);
654   }
655 
656   mode_rt_response_open();
657 
658   if (opt.run_as[0] != '\0') {
659     uid_t olduid;
660     gid_t oldgid;
661     struct passwd *pwe;
662 
663     pwe = getpwnam(opt.run_as);
664     if (pwe == NULL) {
665       syslog(LOG_NOTICE, _("User to run as was not found"));
666       log_exit(EXIT_FAILURE);
667     }
668     olduid = getuid();
669     oldgid = getgid();
670     retval = setgid(pwe->pw_gid);
671     if (retval == -1) {
672       syslog(LOG_NOTICE, "setgid: %s", strerror(errno));
673       log_exit(EXIT_FAILURE);
674     }
675     retval = setuid(pwe->pw_uid);
676     if (retval == -1) {
677       syslog(LOG_NOTICE, "setuid: %s", strerror(errno));
678       log_exit(EXIT_FAILURE);
679     }
680     syslog(LOG_NOTICE, _("Changed uid from %d to %d, gid from %d to %d"), olduid, getuid(), oldgid, getgid());
681   } else {
682     syslog(LOG_NOTICE, _("Running with uid %d, gid %d"), getuid(), getgid());
683   }
684 
685   if (opt.threshold == 1) {
686     syslog(LOG_NOTICE, _("Alert threshold is one attempt"));
687   } else {
688     syslog(LOG_NOTICE, _("Alert threshold is %d attempts"), opt.threshold);
689   }
690 
691   if (opt.recent < 3600) {
692     syslog(LOG_NOTICE, _("Events older than %d second(s) are discarded"), opt.recent);
693   } else {
694     syslog(LOG_NOTICE, _("Events older than %d hour(s) are discarded"), opt.recent / 3600);
695   }
696 
697   syslog(LOG_NOTICE, _("Response mode: Log%s%s"), (opt.response & OPT_NOTIFY) ? _(", notify") : "", (opt.response & OPT_RESPOND) ? _(", respond") : "");
698 
699   if ((!opt.std_in) && (!opt.stateful_start)) {
700     retval = fseek(opt.inputfd, 0, SEEK_END);
701     if (retval == -1) {
702       syslog(LOG_NOTICE, "fseek %s: %s", first_file->name, strerror(errno));
703       log_exit(EXIT_FAILURE);
704     }
705   }
706 
707   signal(SIGHUP, mode_rt_response_reread_conf);
708   signal(SIGUSR1, mode_rt_response_reopen_log);
709   mode_rt_response_core();
710 }
711 
712 void mode_show_log_times()
713 {
714   char buf[BUFSIZE], stime[TIMESIZE], month[4], *input = NULL, last_file = 0;
715   int retval = 0, loop, hour, minute, second, linenum = 0;
716   unsigned int day;
717   struct input_file *file;
718   time_t first = 0, last = 0;
719 
720   opt.line = xmalloc(sizeof(struct log_line));
721 
722   file = first_file;
723   while (last_file == 0) {
724     if (opt.std_in) {
725       opt.inputfd = stdin;
726 
727       if (opt.verbose)
728 	fprintf(stderr, _("Reading standard input\n"));
729     } else {
730       input = file->name;
731 #ifdef HAVE_ZLIB
732       opt.gzinputfd = gzopen(input, "rb");
733       if (opt.gzinputfd == NULL) {
734 	fprintf(stderr, "gzopen %s: %s\n", input, strerror(errno));
735 #else
736       opt.inputfd = fopen(input, "r");
737       if (opt.inputfd == NULL) {
738 	fprintf(stderr, "fopen %s: %s\n", input, strerror(errno));
739 #endif
740 	exit(EXIT_FAILURE);
741       }
742 
743       if (opt.verbose)
744 	fprintf(stderr, _("Reading '%s'\n"), input);
745     }
746 
747 #ifdef HAVE_ZLIB
748     if (opt.std_in) {
749 #endif
750       loop = (fgets(buf, BUFSIZE, opt.inputfd) != NULL);
751 #ifdef HAVE_ZLIB
752     } else {
753       loop = (gzgets(opt.gzinputfd, buf, BUFSIZE) != Z_NULL);
754     }
755 #endif
756 
757     while (loop) {
758       linenum++;
759       retval = sscanf(buf, "%3s %u %2d:%2d:%2d ", month, &day, &hour, &minute, &second);
760       if (retval == 5) {
761 	build_time(month, day, hour, minute, second);
762 	if (first == 0)
763 	  first = last = opt.line->time;
764 	if (opt.line->time < first)
765 	  first = opt.line->time;
766 	if (opt.line->time > last)
767 	  last = opt.line->time;
768       }
769 #ifdef HAVE_ZLIB
770       if (opt.std_in) {
771 #endif
772 	loop = (fgets(buf, BUFSIZE, opt.inputfd) != NULL);
773 #ifdef HAVE_ZLIB
774       } else {
775 	loop = (gzgets(opt.gzinputfd, buf, BUFSIZE) != Z_NULL);
776       }
777 #endif
778     }
779 
780     if (opt.std_in) {
781       last_file++;
782     } else {
783       if (opt.verbose)
784 	fprintf(stderr, _("Closing '%s'\n"), input);
785 
786 #ifndef HAVE_ZLIB
787       retval = fclose(opt.inputfd);
788       if (retval == EOF) {
789 	perror("fclose");
790 #else
791       retval = gzclose(opt.gzinputfd);
792       if (retval != 0) {
793 	if (retval != Z_ERRNO) {
794 	  fprintf(stderr, "gzclose %s: %s\n", input, gzerror(opt.gzinputfd, &retval));
795 	} else {
796 	  perror("gzclose");
797 	}
798 #endif
799 	exit(EXIT_FAILURE);
800       }
801 
802       if (file->next != NULL) {
803 	file = file->next;
804       } else {
805 	last_file++;
806       }
807     }
808   }
809 
810   printf(_("Number of files: %d\n"), opt.filecount);
811   printf(_("Number of lines: %d\n"), linenum);
812   if (first == 0) {
813     printf(_("No valid time entries found.\n"));
814   } else {
815     strftime(stime, TIMESIZE, _("%b %d %H:%M:%S"), localtime(&first));
816     printf(_("First entry: %s\n"), stime);
817     strftime(stime, TIMESIZE, _("%b %d %H:%M:%S"), localtime(&last));
818     printf(_("Last entry : %s\n"), stime);
819     output_timediff(first, last, stime);
820     printf(_("Difference : %s\n"), stime);
821   }
822 
823   free(opt.line);
824 }
825