| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| |
| /* |
| * Copyright 2006 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| #pragma ident "%Z%%M% %I% %E% SMI" /* SunOS */ |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <stropts.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <fcntl.h> |
| #include <stdarg.h> |
| #include <setjmp.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <sys/types.h> |
| #include <sys/time.h> |
| #include <signal.h> |
| #include <sys/mman.h> |
| #include <assert.h> |
| #include <sys/sysmacros.h> |
| |
| #include <sys/socket.h> |
| #include <sys/pfmod.h> |
| #include <net/if.h> |
| #include <netinet/in_systm.h> |
| #include <netinet/in.h> |
| #include <netinet/if_ether.h> |
| #include <netdb.h> |
| |
| #include "snoop.h" |
| |
| static int snaplen; |
| char *device = NULL; |
| |
| /* Global error recovery variables */ |
| sigjmp_buf jmp_env, ojmp_env; /* error recovery jmp buf */ |
| int snoop_nrecover; /* number of recoveries on curr pkt */ |
| int quitting; /* user termination flag */ |
| |
| static struct snoop_handler *snoop_hp; /* global alarm handler head */ |
| static struct snoop_handler *snoop_tp; /* global alarm handler tail */ |
| static time_t snoop_nalarm; /* time of next alarm */ |
| |
| /* protected interpreter output areas */ |
| #define MAXSUM 8 |
| #define REDZONE 64 |
| static char *sumline[MAXSUM]; |
| static char *detail_line; |
| static char *line; |
| static char *encap; |
| |
| static int audio; |
| int maxcount; /* maximum no of packets to capture */ |
| int count; /* count of packets captured */ |
| static int sumcount; |
| int x_offset = -1; |
| int x_length = 0x7fffffff; |
| FILE *namefile; |
| int Pflg; |
| boolean_t qflg = B_FALSE; |
| boolean_t rflg = B_FALSE; |
| #ifdef DEBUG |
| boolean_t zflg = B_FALSE; /* debugging packet corrupt flag */ |
| #endif |
| struct Pf_ext_packetfilt pf; |
| |
| static void usage(void); |
| void show_count(); |
| static void snoop_sigrecover(int sig, siginfo_t *info, void *p); |
| static char *protmalloc(size_t); |
| static void resetperm(void); |
| |
| int |
| main(int argc, char **argv) |
| { |
| int c; |
| int filter = 0; |
| int flags = F_SUM; |
| struct Pf_ext_packetfilt *fp = NULL; |
| char *icapfile = NULL; |
| char *ocapfile = NULL; |
| int nflg = 0; |
| int Nflg = 0; |
| int Cflg = 0; |
| int first = 1; |
| int last = 0x7fffffff; |
| int ppa; |
| int use_kern_pf; |
| char *p, *p2; |
| char names[MAXPATHLEN + 1]; |
| char self[MAXHOSTNAMELEN + 1]; |
| char *argstr = NULL; |
| void (*proc)(); |
| char *audiodev; |
| int ret; |
| struct sigaction sigact; |
| stack_t sigstk; |
| char *output_area; |
| int nbytes; |
| |
| names[0] = '\0'; |
| /* |
| * Global error recovery: Prepare for interpreter failures |
| * with corrupted packets or confused interpreters. |
| * Allocate protected output and stack areas, with generous |
| * red-zones. |
| */ |
| nbytes = (MAXSUM + 3) * (MAXLINE + REDZONE); |
| output_area = protmalloc(nbytes); |
| if (output_area == NULL) { |
| perror("Warning: mmap"); |
| exit(1); |
| } |
| |
| /* Allocate protected output areas */ |
| for (ret = 0; ret < MAXSUM; ret++) { |
| sumline[ret] = (char *)output_area; |
| output_area += (MAXLINE + REDZONE); |
| } |
| detail_line = output_area; |
| output_area += MAXLINE + REDZONE; |
| line = output_area; |
| output_area += MAXLINE + REDZONE; |
| encap = output_area; |
| output_area += MAXLINE + REDZONE; |
| |
| /* Initialize an alternate signal stack to increase robustness */ |
| if ((sigstk.ss_sp = (char *)malloc(SIGSTKSZ+REDZONE)) == NULL) { |
| perror("Warning: malloc"); |
| exit(1); |
| } |
| sigstk.ss_size = SIGSTKSZ; |
| sigstk.ss_flags = 0; |
| if (sigaltstack(&sigstk, (stack_t *)NULL) < 0) { |
| perror("Warning: sigaltstack"); |
| exit(1); |
| } |
| |
| /* Initialize a master signal handler */ |
| sigact.sa_handler = NULL; |
| sigact.sa_sigaction = snoop_sigrecover; |
| (void) sigemptyset(&sigact.sa_mask); |
| sigact.sa_flags = SA_ONSTACK|SA_SIGINFO; |
| |
| /* Register master signal handler */ |
| if (sigaction(SIGHUP, &sigact, (struct sigaction *)NULL) < 0) { |
| perror("Warning: sigaction"); |
| exit(1); |
| } |
| if (sigaction(SIGINT, &sigact, (struct sigaction *)NULL) < 0) { |
| perror("Warning: sigaction"); |
| exit(1); |
| } |
| if (sigaction(SIGQUIT, &sigact, (struct sigaction *)NULL) < 0) { |
| perror("Warning: sigaction"); |
| exit(1); |
| } |
| if (sigaction(SIGILL, &sigact, (struct sigaction *)NULL) < 0) { |
| perror("Warning: sigaction"); |
| exit(1); |
| } |
| if (sigaction(SIGTRAP, &sigact, (struct sigaction *)NULL) < 0) { |
| perror("Warning: sigaction"); |
| exit(1); |
| } |
| if (sigaction(SIGIOT, &sigact, (struct sigaction *)NULL) < 0) { |
| perror("Warning: sigaction"); |
| exit(1); |
| } |
| if (sigaction(SIGEMT, &sigact, (struct sigaction *)NULL) < 0) { |
| perror("Warning: sigaction"); |
| exit(1); |
| } |
| if (sigaction(SIGFPE, &sigact, (struct sigaction *)NULL) < 0) { |
| perror("Warning: sigaction"); |
| exit(1); |
| } |
| if (sigaction(SIGBUS, &sigact, (struct sigaction *)NULL) < 0) { |
| perror("Warning: sigaction"); |
| exit(1); |
| } |
| if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) < 0) { |
| perror("Warning: sigaction"); |
| exit(1); |
| } |
| if (sigaction(SIGSYS, &sigact, (struct sigaction *)NULL) < 0) { |
| perror("Warning: sigaction"); |
| exit(1); |
| } |
| if (sigaction(SIGALRM, &sigact, (struct sigaction *)NULL) < 0) { |
| perror("Warning: sigaction"); |
| exit(1); |
| } |
| if (sigaction(SIGTERM, &sigact, (struct sigaction *)NULL) < 0) { |
| perror("Warning: sigaction"); |
| exit(1); |
| } |
| |
| /* Prepare for failure during program initialization/exit */ |
| if (sigsetjmp(jmp_env, 1)) { |
| exit(1); |
| } |
| (void) setvbuf(stdout, NULL, _IOLBF, BUFSIZ); |
| |
| while ((c = getopt(argc, argv, "at:CPDSi:o:Nn:s:d:vVp:f:c:x:?rqz")) |
| != EOF) { |
| switch (c) { |
| case 'a': |
| audiodev = getenv("AUDIODEV"); |
| if (audiodev == NULL) |
| audiodev = "/dev/audio"; |
| audio = open(audiodev, O_WRONLY); |
| if (audio < 0) { |
| pr_err("Audio device %s: %m", |
| audiodev); |
| exit(1); |
| } |
| break; |
| case 't': |
| flags |= F_TIME; |
| switch (*optarg) { |
| case 'r': flags |= F_RTIME; break; |
| case 'a': flags |= F_ATIME; break; |
| case 'd': break; |
| default: usage(); |
| } |
| break; |
| case 'P': |
| Pflg++; |
| break; |
| case 'D': |
| flags |= F_DROPS; |
| break; |
| case 'S': |
| flags |= F_LEN; |
| break; |
| case 'i': |
| icapfile = optarg; |
| break; |
| case 'o': |
| ocapfile = optarg; |
| break; |
| case 'N': |
| Nflg++; |
| break; |
| case 'n': |
| nflg++; |
| (void) strlcpy(names, optarg, MAXPATHLEN); |
| break; |
| case 's': |
| snaplen = atoi(optarg); |
| break; |
| case 'd': |
| device = optarg; |
| break; |
| case 'v': |
| flags &= ~(F_SUM); |
| flags |= F_DTAIL; |
| break; |
| case 'V': |
| flags |= F_ALLSUM; |
| break; |
| case 'p': |
| p = optarg; |
| p2 = strpbrk(p, ",:-"); |
| if (p2 == NULL) { |
| first = last = atoi(p); |
| } else { |
| *p2++ = '\0'; |
| first = atoi(p); |
| last = atoi(p2); |
| } |
| break; |
| case 'f': |
| (void) gethostname(self, MAXHOSTNAMELEN); |
| p = strchr(optarg, ':'); |
| if (p) { |
| *p = '\0'; |
| if (strcmp(optarg, self) == 0 || |
| strcmp(p+1, self) == 0) |
| (void) fprintf(stderr, |
| "Warning: cannot capture packets from %s\n", |
| self); |
| *p = ' '; |
| } else if (strcmp(optarg, self) == 0) |
| (void) fprintf(stderr, |
| "Warning: cannot capture packets from %s\n", |
| self); |
| argstr = optarg; |
| break; |
| case 'x': |
| p = optarg; |
| p2 = strpbrk(p, ",:-"); |
| if (p2 == NULL) { |
| x_offset = atoi(p); |
| x_length = -1; |
| } else { |
| *p2++ = '\0'; |
| x_offset = atoi(p); |
| x_length = atoi(p2); |
| } |
| break; |
| case 'c': |
| maxcount = atoi(optarg); |
| break; |
| case 'C': |
| Cflg++; |
| break; |
| case 'q': |
| qflg = B_TRUE; |
| break; |
| case 'r': |
| rflg = B_TRUE; |
| break; |
| #ifdef DEBUG |
| case 'z': |
| zflg = B_TRUE; |
| break; |
| #endif /* DEBUG */ |
| case '?': |
| default: |
| usage(); |
| } |
| } |
| |
| if (argc > optind) |
| argstr = (char *)concat_args(&argv[optind], argc - optind); |
| |
| /* |
| * Need to know before we decide on filtering method some things |
| * about the interface. So, go ahead and do part of the initialization |
| * now so we have that data. Note that if no device is specified, |
| * check_device selects one and returns it. In an ideal world, |
| * it might be nice if the "correct" interface for the filter |
| * requested was chosen, but that's too hard. |
| */ |
| if (!icapfile) { |
| use_kern_pf = check_device(&device, &ppa); |
| } else { |
| cap_open_read(icapfile); |
| |
| if (!nflg) { |
| names[0] = '\0'; |
| (void) strlcpy(names, icapfile, MAXPATHLEN); |
| (void) strlcat(names, ".names", MAXPATHLEN); |
| } |
| } |
| |
| /* attempt to read .names file if it exists before filtering */ |
| if ((!Nflg) && names[0] != '\0') { |
| if (access(names, F_OK) == 0) { |
| load_names(names); |
| } else if (nflg) { |
| (void) fprintf(stderr, "%s not found\n", names); |
| exit(1); |
| } |
| } |
| |
| if (argstr) { |
| if (!icapfile && use_kern_pf) { |
| ret = pf_compile(argstr, Cflg); |
| switch (ret) { |
| case 0: |
| filter++; |
| compile(argstr, Cflg); |
| break; |
| case 1: |
| fp = &pf; |
| break; |
| case 2: |
| fp = &pf; |
| filter++; |
| break; |
| } |
| } else { |
| filter++; |
| compile(argstr, Cflg); |
| } |
| |
| if (Cflg) |
| exit(0); |
| } |
| |
| if (flags & F_SUM) |
| flags |= F_WHO; |
| |
| /* |
| * If the -o flag is set then capture packets |
| * directly to a file. Don't attempt to |
| * interpret them on the fly (F_NOW). |
| * Note: capture to file is much less likely |
| * to drop packets since we don't spend cpu |
| * cycles running through the interpreters |
| * and possibly hanging in address-to-name |
| * mappings through the name service. |
| */ |
| if (ocapfile) { |
| cap_open_write(ocapfile); |
| proc = cap_write; |
| } else { |
| flags |= F_NOW; |
| proc = process_pkt; |
| } |
| |
| |
| /* |
| * If the -i flag is set then get packets from |
| * the log file which has been previously captured |
| * with the -o option. |
| */ |
| if (icapfile) { |
| names[0] = '\0'; |
| (void) strlcpy(names, icapfile, MAXPATHLEN); |
| (void) strlcat(names, ".names", MAXPATHLEN); |
| |
| if (Nflg) { |
| namefile = fopen(names, "w"); |
| if (namefile == NULL) { |
| perror(names); |
| exit(1); |
| } |
| flags = 0; |
| (void) fprintf(stderr, |
| "Creating name file %s\n", names); |
| } |
| |
| if (flags & F_DTAIL) |
| flags = F_DTAIL; |
| else |
| flags |= F_NUM | F_TIME; |
| |
| resetperm(); |
| cap_read(first, last, filter, proc, flags); |
| |
| if (Nflg) |
| (void) fclose(namefile); |
| |
| } else { |
| const int chunksize = 8 * 8192; |
| struct timeval timeout; |
| |
| /* |
| * If listening to packets on audio |
| * then set the buffer timeout down |
| * to 1/10 sec. A higher value |
| * makes the audio "bursty". |
| */ |
| if (audio) { |
| timeout.tv_sec = 0; |
| timeout.tv_usec = 100000; |
| } else { |
| timeout.tv_sec = 1; |
| timeout.tv_usec = 0; |
| } |
| |
| initdevice(device, snaplen, chunksize, &timeout, fp, ppa); |
| if (! qflg && ocapfile) |
| show_count(); |
| resetperm(); |
| net_read(chunksize, filter, proc, flags); |
| |
| if (!(flags & F_NOW)) |
| (void) printf("\n"); |
| } |
| |
| if (ocapfile) |
| cap_close(); |
| |
| return (0); |
| } |
| |
| static int tone[] = { |
| 0x076113, 0x153333, 0x147317, 0x144311, 0x147315, 0x050353, 0x037103, 0x051106, |
| 0x157155, 0x142723, 0x133273, 0x134664, 0x051712, 0x024465, 0x026447, 0x072473, |
| 0x136715, 0x126257, 0x135256, 0x047344, 0x034476, 0x027464, 0x036062, 0x133334, |
| 0x127256, 0x130660, 0x136262, 0x040724, 0x016446, 0x025437, 0x137171, 0x127672, |
| 0x124655, 0x134654, 0x032741, 0x021447, 0x037450, 0x125675, 0x127650, 0x077277, |
| 0x046514, 0x036077, 0x035471, 0x147131, 0x136272, 0x162720, 0x166151, 0x037527, |
| }; |
| |
| /* |
| * Make a sound on /dev/audio according to the length of the packet. The |
| * tone data was ripped from /usr/share/audio/samples/au/bark.au. The |
| * amount of waveform used is a function of packet length e.g. a series |
| * of small packets is heard as clicks, whereas a series of NFS packets in |
| * an 8k read sounds like a "WHAAAARP". |
| */ |
| void |
| click(len) |
| int len; |
| { |
| len /= 8; |
| len = len ? len : 4; |
| |
| if (audio) { |
| (void) write(audio, tone, len); |
| } |
| } |
| |
| /* Display a count of packets */ |
| void |
| show_count() |
| { |
| static int prev = -1; |
| |
| if (count == prev) |
| return; |
| |
| prev = count; |
| (void) fprintf(stderr, "\r%d ", count); |
| } |
| |
| #define ENCAP_LEN 16 /* Hold "(NN encap)" */ |
| |
| /* |
| * Display data that's external to the packet. |
| * This constitutes the first half of the summary |
| * line display. |
| */ |
| void |
| show_pktinfo(flags, num, src, dst, ptvp, tvp, drops, len) |
| int flags, num, drops, len; |
| char *src, *dst; |
| struct timeval *ptvp, *tvp; |
| { |
| struct tm *tm; |
| static struct timeval tvp0; |
| int sec, usec; |
| char *lp = line; |
| int i, start; |
| |
| if (flags & F_NUM) { |
| (void) sprintf(lp, "%3d ", num); |
| lp += strlen(lp); |
| } |
| tm = localtime(&tvp->tv_sec); |
| |
| if (flags & F_TIME) { |
| if (flags & F_ATIME) { |
| (void) sprintf(lp, "%d:%02d:%d.%05d ", |
| tm->tm_hour, tm->tm_min, tm->tm_sec, |
| (int)tvp->tv_usec / 10); |
| lp += strlen(lp); |
| } else { |
| if (flags & F_RTIME) { |
| if (tvp0.tv_sec == 0) { |
| tvp0.tv_sec = tvp->tv_sec; |
| tvp0.tv_usec = tvp->tv_usec; |
| } |
| ptvp = &tvp0; |
| } |
| sec = tvp->tv_sec - ptvp->tv_sec; |
| usec = tvp->tv_usec - ptvp->tv_usec; |
| if (usec < 0) { |
| usec += 1000000; |
| sec -= 1; |
| } |
| (void) sprintf(lp, "%3d.%05d ", sec, usec / 10); |
| lp += strlen(lp); |
| } |
| } |
| |
| if (flags & F_WHO) { |
| (void) sprintf(lp, "%12s -> %-12s ", src, dst); |
| lp += strlen(lp); |
| } |
| |
| if (flags & F_DROPS) { |
| (void) sprintf(lp, "drops: %d ", drops); |
| lp += strlen(lp); |
| } |
| |
| if (flags & F_LEN) { |
| (void) sprintf(lp, "length: %4d ", len); |
| lp += strlen(lp); |
| } |
| |
| if (flags & F_SUM) { |
| if (flags & F_ALLSUM) |
| (void) printf("________________________________\n"); |
| |
| start = flags & F_ALLSUM ? 0 : sumcount - 1; |
| (void) sprintf(encap, " (%d encap)", total_encap_levels - 1); |
| (void) printf("%s%s%s\n", line, sumline[start], |
| ((flags & F_ALLSUM) || (total_encap_levels == 1)) ? "" : |
| encap); |
| |
| for (i = start + 1; i < sumcount; i++) |
| (void) printf("%s%s\n", line, sumline[i]); |
| |
| sumcount = 0; |
| } |
| |
| if (flags & F_DTAIL) { |
| (void) printf("%s\n\n", detail_line); |
| detail_line[0] = '\0'; |
| } |
| } |
| |
| /* |
| * The following two routines are called back |
| * from the interpreters to display their stuff. |
| * The theory is that when snoop becomes a window |
| * based tool we can just supply a new version of |
| * get_sum_line and get_detail_line and not have |
| * to touch the interpreters at all. |
| */ |
| char * |
| get_sum_line() |
| { |
| int tsumcount = sumcount; |
| |
| if (sumcount >= MAXSUM) { |
| sumcount = 0; /* error recovery */ |
| pr_err( |
| "get_sum_line: sumline overflow (sumcount=%d, MAXSUM=%d)\n", |
| tsumcount, MAXSUM); |
| } |
| |
| sumline[sumcount][0] = '\0'; |
| return (sumline[sumcount++]); |
| } |
| |
| /*ARGSUSED*/ |
| char * |
| get_detail_line(off, len) |
| int off, len; |
| { |
| if (detail_line[0]) { |
| (void) printf("%s\n", detail_line); |
| detail_line[0] = '\0'; |
| } |
| return (detail_line); |
| } |
| |
| /* |
| * Print an error. |
| * Works like printf (fmt string and variable args) |
| * except that it will substitute an error message |
| * for a "%m" string (like syslog) and it calls |
| * long_jump - it doesn't return to where it was |
| * called from - it goes to the last setjmp(). |
| */ |
| /* VARARGS1 */ |
| void |
| pr_err(const char *fmt, ...) |
| { |
| va_list ap; |
| char buf[1024], *p2; |
| const char *p1; |
| |
| (void) strcpy(buf, "snoop: "); |
| p2 = buf + strlen(buf); |
| |
| /* |
| * Note that we terminate the buffer with '\n' and '\0'. |
| */ |
| for (p1 = fmt; *p1 != '\0' && p2 < buf + sizeof (buf) - 2; p1++) { |
| if (*p1 == '%' && *(p1+1) == 'm') { |
| const char *errstr; |
| |
| if ((errstr = strerror(errno)) != NULL) { |
| *p2 = '\0'; |
| (void) strlcat(buf, errstr, sizeof (buf)); |
| p2 += strlen(p2); |
| } |
| p1++; |
| } else { |
| *p2++ = *p1; |
| } |
| } |
| if (p2 > buf && *(p2-1) != '\n') |
| *p2++ = '\n'; |
| *p2 = '\0'; |
| |
| va_start(ap, fmt); |
| /* LINTED: E_SEC_PRINTF_VAR_FMT */ |
| (void) vfprintf(stderr, buf, ap); |
| va_end(ap); |
| snoop_sigrecover(-1, NULL, NULL); /* global error recovery */ |
| } |
| |
| /* |
| * Ye olde usage proc |
| * PLEASE keep this up to date! |
| * Naive users *love* this stuff. |
| */ |
| static void |
| usage(void) |
| { |
| (void) fprintf(stderr, "\nUsage: snoop\n"); |
| (void) fprintf(stderr, |
| "\t[ -a ] # Listen to packets on audio\n"); |
| (void) fprintf(stderr, |
| "\t[ -d device ] # Listen on interface named device\n"); |
| (void) fprintf(stderr, |
| "\t[ -s snaplen ] # Truncate packets\n"); |
| (void) fprintf(stderr, |
| "\t[ -c count ] # Quit after count packets\n"); |
| (void) fprintf(stderr, |
| "\t[ -P ] # Turn OFF promiscuous mode\n"); |
| (void) fprintf(stderr, |
| "\t[ -D ] # Report dropped packets\n"); |
| (void) fprintf(stderr, |
| "\t[ -S ] # Report packet size\n"); |
| (void) fprintf(stderr, |
| "\t[ -i file ] # Read previously captured packets\n"); |
| (void) fprintf(stderr, |
| "\t[ -o file ] # Capture packets in file\n"); |
| (void) fprintf(stderr, |
| "\t[ -n file ] # Load addr-to-name table from file\n"); |
| (void) fprintf(stderr, |
| "\t[ -N ] # Create addr-to-name table\n"); |
| (void) fprintf(stderr, |
| "\t[ -t r|a|d ] # Time: Relative, Absolute or Delta\n"); |
| (void) fprintf(stderr, |
| "\t[ -v ] # Verbose packet display\n"); |
| (void) fprintf(stderr, |
| "\t[ -V ] # Show all summary lines\n"); |
| (void) fprintf(stderr, |
| "\t[ -p first[,last] ] # Select packet(s) to display\n"); |
| (void) fprintf(stderr, |
| "\t[ -x offset[,length] ] # Hex dump from offset for length\n"); |
| (void) fprintf(stderr, |
| "\t[ -C ] # Print packet filter code\n"); |
| (void) fprintf(stderr, |
| "\t[ -q ] # Suppress printing packet count\n"); |
| (void) fprintf(stderr, |
| "\t[ -r ] # Do not resolve address to name\n"); |
| (void) fprintf(stderr, |
| "\n\t[ filter expression ]\n"); |
| (void) fprintf(stderr, "\nExample:\n"); |
| (void) fprintf(stderr, "\tsnoop -o saved host fred\n\n"); |
| (void) fprintf(stderr, "\tsnoop -i saved -tr -v -p19\n"); |
| exit(1); |
| } |
| |
| /* |
| * sdefault: default global alarm handler. Causes the current packet |
| * to be skipped. |
| */ |
| static void |
| sdefault(void) |
| { |
| snoop_nrecover = SNOOP_MAXRECOVER; |
| } |
| |
| /* |
| * snoop_alarm: register or unregister an alarm handler to be called after |
| * s_sec seconds. Because snoop wasn't written to tolerate random signal |
| * delivery, periodic SIGALRM delivery (or SA_RESTART) cannot be used. |
| * |
| * s_sec argument of 0 seconds unregisters the handler. |
| * s_handler argument of NULL registers default handler sdefault(), or |
| * unregisters all signal handlers (for error recovery). |
| * |
| * Variables must be volatile to force the compiler to not optimize |
| * out the signal blocking. |
| */ |
| /*ARGSUSED*/ |
| int |
| snoop_alarm(int s_sec, void (*s_handler)()) |
| { |
| volatile time_t now; |
| volatile time_t nalarm = 0; |
| volatile struct snoop_handler *sh = NULL; |
| volatile struct snoop_handler *hp, *tp, *next; |
| volatile sigset_t s_mask; |
| volatile int ret = -1; |
| |
| (void) sigemptyset((sigset_t *)&s_mask); |
| (void) sigaddset((sigset_t *)&s_mask, SIGALRM); |
| if (s_sec < 0) |
| return (-1); |
| |
| /* register an alarm handler */ |
| now = time(NULL); |
| if (s_sec) { |
| sh = malloc(sizeof (struct snoop_handler)); |
| sh->s_time = now + s_sec; |
| if (s_handler == NULL) |
| s_handler = sdefault; |
| sh->s_handler = s_handler; |
| sh->s_next = NULL; |
| (void) sigprocmask(SIG_BLOCK, (sigset_t *)&s_mask, NULL); |
| if (snoop_hp == NULL) { |
| snoop_hp = snoop_tp = (struct snoop_handler *)sh; |
| |
| snoop_nalarm = sh->s_time; |
| (void) alarm(sh->s_time - now); |
| } else { |
| snoop_tp->s_next = (struct snoop_handler *)sh; |
| snoop_tp = (struct snoop_handler *)sh; |
| |
| if (sh->s_time < snoop_nalarm) { |
| snoop_nalarm = sh->s_time; |
| (void) alarm(sh->s_time - now); |
| } |
| } |
| (void) sigprocmask(SIG_UNBLOCK, (sigset_t *)&s_mask, NULL); |
| |
| return (0); |
| } |
| |
| /* unregister an alarm handler */ |
| (void) sigprocmask(SIG_BLOCK, (sigset_t *)&s_mask, NULL); |
| tp = (struct snoop_handler *)&snoop_hp; |
| for (hp = snoop_hp; hp; hp = next) { |
| next = hp->s_next; |
| if (s_handler == NULL || hp->s_handler == s_handler) { |
| ret = 0; |
| tp->s_next = hp->s_next; |
| if (snoop_tp == hp) { |
| if (tp == (struct snoop_handler *)&snoop_hp) |
| snoop_tp = NULL; |
| else |
| snoop_tp = (struct snoop_handler *)tp; |
| } |
| free((void *)hp); |
| } else { |
| if (nalarm == 0 || nalarm > hp->s_time) |
| nalarm = now < hp->s_time ? hp->s_time : |
| now + 1; |
| tp = hp; |
| } |
| } |
| /* |
| * Stop or adjust timer |
| */ |
| if (snoop_hp == NULL) { |
| snoop_nalarm = 0; |
| (void) alarm(0); |
| } else if (nalarm > 0 && nalarm < snoop_nalarm) { |
| snoop_nalarm = nalarm; |
| (void) alarm(nalarm - now); |
| } |
| |
| (void) sigprocmask(SIG_UNBLOCK, (sigset_t *)&s_mask, NULL); |
| return (ret); |
| } |
| |
| /* |
| * snoop_recover: reset snoop's output area, and any internal variables, |
| * to allow continuation. |
| * XXX: make this an interface such that each interpreter can |
| * register a reset routine. |
| */ |
| void |
| snoop_recover(void) |
| { |
| int i; |
| |
| /* Error recovery: reset output_area and associated variables */ |
| for (i = 0; i < MAXSUM; i++) |
| sumline[i][0] = '\0'; |
| detail_line[0] = '\0'; |
| line[0] = '\0'; |
| encap[0] = '\0'; |
| sumcount = 0; |
| |
| /* stacking/unstacking cannot be relied upon */ |
| encap_levels = 0; |
| total_encap_levels = 1; |
| |
| /* remove any pending timeouts */ |
| (void) snoop_alarm(0, NULL); |
| } |
| |
| /* |
| * snoop_sigrecover: global sigaction routine to manage recovery |
| * from catastrophic interpreter failures while interpreting |
| * corrupt trace files/packets. SIGALRM timeouts, program errors, |
| * and user termination are all handled. In the case of a corrupt |
| * packet or confused interpreter, the packet will be skipped, and |
| * execution will continue in scan(). |
| * |
| * Global alarm handling (see snoop_alarm()) is managed here. |
| * |
| * Variables must be volatile to force the compiler to not optimize |
| * out the signal blocking. |
| */ |
| /*ARGSUSED*/ |
| static void |
| snoop_sigrecover(int sig, siginfo_t *info, void *p) |
| { |
| volatile time_t now; |
| volatile time_t nalarm = 0; |
| volatile struct snoop_handler *hp; |
| |
| /* |
| * Invoke any registered alarms. This involves first calculating |
| * the time for the next alarm, setting it up, then progressing |
| * through handler invocations. Note that since handlers may |
| * use siglongjmp(), in the worst case handlers may be serviced |
| * at a later time. |
| */ |
| if (sig == SIGALRM) { |
| now = time(NULL); |
| /* Calculate next alarm time */ |
| for (hp = snoop_hp; hp; hp = hp->s_next) { |
| if (hp->s_time) { |
| if ((hp->s_time - now) > 0) { |
| if (nalarm == 0 || nalarm > hp->s_time) |
| nalarm = now < hp->s_time ? |
| hp->s_time : now + 1; |
| } |
| } |
| } |
| /* Setup next alarm */ |
| if (nalarm) { |
| snoop_nalarm = nalarm; |
| (void) alarm(nalarm - now); |
| } else { |
| snoop_nalarm = 0; |
| } |
| |
| /* Invoke alarm handlers (may not return) */ |
| for (hp = snoop_hp; hp; hp = hp->s_next) { |
| if (hp->s_time) { |
| if ((now - hp->s_time) >= 0) { |
| hp->s_time = 0; /* only invoke once */ |
| if (hp->s_handler) |
| hp->s_handler(); |
| } |
| } |
| } |
| } else { |
| snoop_nrecover++; |
| } |
| |
| /* |
| * Exit if a signal has occurred after snoop has begun the process |
| * of quitting. |
| */ |
| if (quitting) |
| exit(1); |
| |
| /* |
| * If an alarm handler has timed out, and snoop_nrecover has |
| * reached SNOOP_MAXRECOVER, skip to the next packet. |
| * |
| * If any other signal has occurred, and snoop_nrecover has |
| * reached SNOOP_MAXRECOVER, give up. |
| */ |
| if (sig == SIGALRM) { |
| if (ioctl(STDOUT_FILENO, I_CANPUT, 0) == 0) { |
| /* |
| * We've stalled on output, which is not a critical |
| * failure. Reset the recovery counter so we do not |
| * consider this a persistent failure, and return so |
| * we do not skip this packet. |
| */ |
| snoop_nrecover = 0; |
| return; |
| } |
| if (snoop_nrecover >= SNOOP_MAXRECOVER) { |
| (void) fprintf(stderr, |
| "snoop: WARNING: skipping from packet %d\n", |
| count); |
| snoop_nrecover = 0; |
| } else { |
| /* continue trying */ |
| return; |
| } |
| } else if (snoop_nrecover >= SNOOP_MAXRECOVER) { |
| (void) fprintf(stderr, |
| "snoop: ERROR: cannot recover from packet %d\n", count); |
| exit(1); |
| } |
| |
| #ifdef DEBUG |
| (void) fprintf(stderr, "snoop_sigrecover(%d, %p, %p)\n", sig, info, p); |
| #endif /* DEBUG */ |
| |
| /* |
| * Prepare to quit. This allows final processing to occur |
| * after first terminal interruption. |
| */ |
| if (sig == SIGTERM || sig == SIGHUP || sig == SIGINT) { |
| quitting = 1; |
| return; |
| } else if (sig != -1 && sig != SIGALRM) { |
| /* Inform user that snoop has taken a fault */ |
| (void) fprintf(stderr, |
| "WARNING: received signal %d from packet %d\n", |
| sig, count); |
| } |
| |
| /* Reset interpreter variables */ |
| snoop_recover(); |
| |
| /* Continue in scan() with the next packet */ |
| siglongjmp(jmp_env, 1); |
| /*NOTREACHED*/ |
| } |
| |
| /* |
| * Protected malloc for global error recovery: prepare for interpreter |
| * failures with corrupted packets or confused interpreters. Dynamically |
| * allocate `nbytes' bytes, and sandwich it between two PROT_NONE pages to |
| * catch writes outside of the allocated region. |
| */ |
| static char * |
| protmalloc(size_t nbytes) |
| { |
| caddr_t start; |
| int psz = sysconf(_SC_PAGESIZE); |
| |
| nbytes = P2ROUNDUP(nbytes, psz); |
| start = mmap(NULL, nbytes + psz * 2, PROT_READ|PROT_WRITE, |
| MAP_PRIVATE|MAP_ANON, -1, 0); |
| if (start == MAP_FAILED) { |
| perror("Error: protmalloc: mmap"); |
| return (NULL); |
| } |
| assert(IS_P2ALIGNED(start, psz)); |
| if (mprotect(start, 1, PROT_NONE) == -1) |
| perror("Warning: mprotect"); |
| |
| start += psz; |
| if (mprotect(start + nbytes, 1, PROT_NONE) == -1) |
| perror("Warning: mprotect"); |
| |
| return (start); |
| } |
| |
| /* |
| * resetperm - reduce security vulnerabilities by resetting |
| * owner/group/permissions. Always attempt setuid() - if we have |
| * permission to drop our privilege level, do so. |
| */ |
| void |
| resetperm(void) |
| { |
| if (geteuid() == 0) { |
| (void) setgid(GID_NOBODY); |
| (void) setuid(UID_NOBODY); |
| } |
| } |