1 /* $OpenBSD: pflogd.c,v 1.21 2003/08/22 21:50:34 david Exp $ */ 2 3 /* 4 * Copyright (c) 2001 Theo de Raadt 5 * Copyright (c) 2001 Can Erkin Acar 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * - Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * - Redistributions in binary form must reproduce the above 15 * copyright notice, this list of conditions and the following 16 * disclaimer in the documentation and/or other materials provided 17 * with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 25 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 29 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 #include <sys/cdefs.h> 34 __FBSDID("$FreeBSD$"); 35 36 #include <sys/types.h> 37 #include <sys/file.h> 38 #include <sys/stat.h> 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <unistd.h> 43 #include <pcap-int.h> 44 #include <pcap.h> 45 #include <syslog.h> 46 #include <signal.h> 47 #include <errno.h> 48 #include <stdarg.h> 49 #include <fcntl.h> 50 #ifdef __FreeBSD__ 51 #include "pidfile.h" 52 #else 53 #include <util.h> 54 #endif 55 56 #define DEF_SNAPLEN 116 /* default plus allow for larger header of pflog */ 57 #define PCAP_TO_MS 500 /* pcap read timeout (ms) */ 58 #define PCAP_NUM_PKTS 1000 /* max number of packets to process at each loop */ 59 #define PCAP_OPT_FIL 0 /* filter optimization */ 60 #define FLUSH_DELAY 60 /* flush delay */ 61 62 #define PFLOGD_LOG_FILE "/var/log/pflog" 63 #define PFLOGD_DEFAULT_IF "pflog0" 64 65 pcap_t *hpcap; 66 pcap_dumper_t *dpcap; 67 68 int Debug = 0; 69 int snaplen = DEF_SNAPLEN; 70 71 volatile sig_atomic_t gotsig_close, gotsig_alrm, gotsig_hup; 72 73 char *filename = PFLOGD_LOG_FILE; 74 char *interface = PFLOGD_DEFAULT_IF; 75 char *filter = NULL; 76 77 char errbuf[PCAP_ERRBUF_SIZE]; 78 79 int log_debug = 0; 80 unsigned int delay = FLUSH_DELAY; 81 82 char *copy_argv(char * const *argv); 83 int init_pcap(void); 84 void logmsg(int priority, const char *message, ...); 85 int reset_dump(void); 86 void sig_alrm(int); 87 void sig_close(int); 88 void sig_hup(int); 89 void usage(void); 90 91 92 char * 93 copy_argv(char * const *argv) 94 { 95 size_t len = 0, n; 96 char *buf; 97 98 if (argv == NULL) 99 return (NULL); 100 101 for (n = 0; argv[n]; n++) 102 len += strlen(argv[n])+1; 103 if (len == 0) 104 return (NULL); 105 106 buf = malloc(len); 107 if (buf == NULL) 108 return (NULL); 109 110 strlcpy(buf, argv[0], len); 111 for (n = 1; argv[n]; n++) { 112 strlcat(buf, " ", len); 113 strlcat(buf, argv[n], len); 114 } 115 return (buf); 116 } 117 118 void 119 logmsg(int pri, const char *message, ...) 120 { 121 va_list ap; 122 va_start(ap, message); 123 124 if (log_debug) { 125 vfprintf(stderr, message, ap); 126 fprintf(stderr, "\n"); 127 } else 128 vsyslog(pri, message, ap); 129 va_end(ap); 130 } 131 132 #ifdef __FreeBSD__ 133 __dead2 void 134 #else 135 __dead void 136 #endif 137 usage(void) 138 { 139 fprintf(stderr, "usage: pflogd [-D] [-d delay] [-f filename] "); 140 fprintf(stderr, "[-s snaplen] [expression]\n"); 141 exit(1); 142 } 143 144 void 145 sig_close(int sig) 146 { 147 gotsig_close = 1; 148 } 149 150 void 151 sig_hup(int sig) 152 { 153 gotsig_hup = 1; 154 } 155 156 void 157 sig_alrm(int sig) 158 { 159 gotsig_alrm = 1; 160 } 161 162 int 163 init_pcap(void) 164 { 165 struct bpf_program bprog; 166 pcap_t *oldhpcap = hpcap; 167 168 hpcap = pcap_open_live(interface, snaplen, 1, PCAP_TO_MS, errbuf); 169 if (hpcap == NULL) { 170 logmsg(LOG_ERR, "Failed to initialize: %s", errbuf); 171 hpcap = oldhpcap; 172 return (-1); 173 } 174 175 if (pcap_compile(hpcap, &bprog, filter, PCAP_OPT_FIL, 0) < 0) 176 logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap)); 177 else if (pcap_setfilter(hpcap, &bprog) < 0) 178 logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap)); 179 if (filter != NULL) 180 free(filter); 181 182 if (pcap_datalink(hpcap) != DLT_PFLOG) { 183 logmsg(LOG_ERR, "Invalid datalink type"); 184 pcap_close(hpcap); 185 hpcap = oldhpcap; 186 return (-1); 187 } 188 189 if (oldhpcap) 190 pcap_close(oldhpcap); 191 192 snaplen = pcap_snapshot(hpcap); 193 return (0); 194 } 195 196 int 197 reset_dump(void) 198 { 199 struct pcap_file_header hdr; 200 struct stat st; 201 int tmpsnap; 202 FILE *fp; 203 204 if (hpcap == NULL) 205 return (1); 206 if (dpcap) { 207 pcap_dump_close(dpcap); 208 dpcap = 0; 209 } 210 211 /* 212 * Basically reimplement pcap_dump_open() because it truncates 213 * files and duplicates headers and such. 214 */ 215 fp = fopen(filename, "a+"); 216 if (fp == NULL) { 217 snprintf(hpcap->errbuf, PCAP_ERRBUF_SIZE, "%s: %s", 218 filename, pcap_strerror(errno)); 219 logmsg(LOG_ERR, "Error: %s", pcap_geterr(hpcap)); 220 return (1); 221 } 222 if (fstat(fileno(fp), &st) == -1) { 223 snprintf(hpcap->errbuf, PCAP_ERRBUF_SIZE, "%s: %s", 224 filename, pcap_strerror(errno)); 225 logmsg(LOG_ERR, "Error: %s", pcap_geterr(hpcap)); 226 return (1); 227 } 228 229 dpcap = (pcap_dumper_t *)fp; 230 231 #define TCPDUMP_MAGIC 0xa1b2c3d4 232 233 if (st.st_size == 0) { 234 if (snaplen != pcap_snapshot(hpcap)) { 235 logmsg(LOG_NOTICE, "Using snaplen %d", snaplen); 236 if (init_pcap()) { 237 logmsg(LOG_ERR, "Failed to initialize"); 238 if (hpcap == NULL) return (-1); 239 logmsg(LOG_NOTICE, "Using old settings"); 240 } 241 } 242 hdr.magic = TCPDUMP_MAGIC; 243 hdr.version_major = PCAP_VERSION_MAJOR; 244 hdr.version_minor = PCAP_VERSION_MINOR; 245 hdr.thiszone = hpcap->tzoff; 246 hdr.snaplen = hpcap->snapshot; 247 hdr.sigfigs = 0; 248 hdr.linktype = hpcap->linktype; 249 250 if (fwrite((char *)&hdr, sizeof(hdr), 1, fp) != 1) { 251 dpcap = NULL; 252 fclose(fp); 253 return (-1); 254 } 255 return (0); 256 } 257 258 /* 259 * XXX Must read the file, compare the header against our new 260 * options (in particular, snaplen) and adjust our options so 261 * that we generate a correct file. 262 */ 263 (void) fseek(fp, 0L, SEEK_SET); 264 if (fread((char *)&hdr, sizeof(hdr), 1, fp) == 1) { 265 if (hdr.magic != TCPDUMP_MAGIC || 266 hdr.version_major != PCAP_VERSION_MAJOR || 267 hdr.version_minor != PCAP_VERSION_MINOR || 268 hdr.linktype != hpcap->linktype) { 269 logmsg(LOG_ERR, 270 "Invalid/incompatible log file, move it away"); 271 fclose(fp); 272 return (1); 273 } 274 if (hdr.snaplen != snaplen) { 275 logmsg(LOG_WARNING, 276 "Existing file specifies a snaplen of %u, using it", 277 hdr.snaplen); 278 tmpsnap = snaplen; 279 snaplen = hdr.snaplen; 280 if (init_pcap()) { 281 logmsg(LOG_ERR, "Failed to re-initialize"); 282 if (hpcap == 0) 283 return (-1); 284 logmsg(LOG_NOTICE, 285 "Using old settings, offset: %llu", 286 (unsigned long long)st.st_size); 287 } 288 snaplen = tmpsnap; 289 } 290 } 291 292 (void) fseek(fp, 0L, SEEK_END); 293 return (0); 294 } 295 296 int 297 main(int argc, char **argv) 298 { 299 struct pcap_stat pstat; 300 int ch, np; 301 302 while ((ch = getopt(argc, argv, "Dd:s:f:")) != -1) { 303 switch (ch) { 304 case 'D': 305 Debug = 1; 306 break; 307 case 'd': 308 delay = atoi(optarg); 309 if (delay < 5 || delay > 60*60) 310 usage(); 311 break; 312 case 'f': 313 filename = optarg; 314 break; 315 case 's': 316 snaplen = atoi(optarg); 317 if (snaplen <= 0) 318 snaplen = DEF_SNAPLEN; 319 break; 320 default: 321 usage(); 322 } 323 324 } 325 326 log_debug = Debug; 327 argc -= optind; 328 argv += optind; 329 330 if (!Debug) { 331 openlog("pflogd", LOG_PID | LOG_CONS, LOG_DAEMON); 332 if (daemon(0, 0)) { 333 logmsg(LOG_WARNING, "Failed to become daemon: %s", 334 strerror(errno)); 335 } 336 pidfile(NULL); 337 } 338 339 (void)umask(S_IRWXG | S_IRWXO); 340 341 signal(SIGTERM, sig_close); 342 signal(SIGINT, sig_close); 343 signal(SIGQUIT, sig_close); 344 signal(SIGALRM, sig_alrm); 345 signal(SIGHUP, sig_hup); 346 alarm(delay); 347 348 if (argc) { 349 filter = copy_argv(argv); 350 if (filter == NULL) 351 logmsg(LOG_NOTICE, "Failed to form filter expression"); 352 } 353 354 if (init_pcap()) { 355 logmsg(LOG_ERR, "Exiting, init failure"); 356 exit(1); 357 } 358 359 if (reset_dump()) { 360 logmsg(LOG_ERR, "Failed to open log file %s", filename); 361 pcap_close(hpcap); 362 exit(1); 363 } 364 365 while (1) { 366 np = pcap_dispatch(hpcap, PCAP_NUM_PKTS, pcap_dump, (u_char *)dpcap); 367 if (np < 0) 368 logmsg(LOG_NOTICE, "%s", pcap_geterr(hpcap)); 369 370 if (gotsig_close) 371 break; 372 if (gotsig_hup) { 373 if (reset_dump()) { 374 logmsg(LOG_ERR, "Failed to open log file!"); 375 break; 376 } 377 logmsg(LOG_NOTICE, "Reopened logfile"); 378 gotsig_hup = 0; 379 } 380 381 if (gotsig_alrm) { 382 /* XXX pcap_dumper is an incomplete type which libpcap 383 * casts to a FILE* currently. For now it is safe to 384 * make the same assumption, however this may change 385 * in the future. 386 */ 387 if (dpcap) { 388 if (fflush((FILE *)dpcap) == EOF) { 389 break; 390 } 391 } 392 gotsig_alrm = 0; 393 alarm(delay); 394 } 395 } 396 397 logmsg(LOG_NOTICE, "Exiting due to signal"); 398 if (dpcap) 399 pcap_dump_close(dpcap); 400 401 if (pcap_stats(hpcap, &pstat) < 0) 402 logmsg(LOG_WARNING, "Reading stats: %s", pcap_geterr(hpcap)); 403 else 404 logmsg(LOG_NOTICE, "%u packets received, %u dropped", 405 pstat.ps_recv, pstat.ps_drop); 406 407 pcap_close(hpcap); 408 if (!Debug) 409 closelog(); 410 return (0); 411 } 412