diff options
Diffstat (limited to 'usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop.c')
-rw-r--r-- | usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop.c | 1127 |
1 files changed, 1127 insertions, 0 deletions
diff --git a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop.c b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop.c new file mode 100644 index 0000000..097dd6e --- /dev/null +++ b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop.c @@ -0,0 +1,1127 @@ +/* + * 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 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#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; + +/* 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; +boolean_t Pflg; +boolean_t Iflg; +boolean_t qflg; +boolean_t rflg; +#ifdef DEBUG +boolean_t zflg; +#endif +struct Pf_ext_packetfilt pf; + +static int vlanid = 0; + +static void usage(void); +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; + boolean_t nflg = B_FALSE; + boolean_t Nflg = B_FALSE; + int Cflg = 0; + boolean_t Uflg = B_FALSE; + int first = 1; + int last = 0x7fffffff; + boolean_t 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; + char *datalink = NULL; + dlpi_handle_t dh; + + 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:I:vVp:f:c:x:U?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 'I': + if (datalink != NULL) + usage(); + Iflg = B_TRUE; + datalink = optarg; + break; + case 'P': + Pflg = B_TRUE; + 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 = B_TRUE; + break; + case 'n': + nflg = B_TRUE; + (void) strlcpy(names, optarg, MAXPATHLEN); + break; + case 's': + snaplen = atoi(optarg); + break; + case 'd': + if (Iflg) + usage(); + datalink = 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 = B_TRUE; + break; + case 'q': + qflg = B_TRUE; + break; + case 'r': + rflg = B_TRUE; + break; + case 'U': + Uflg = 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 datalink is specified, + * open_datalink() 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 = open_datalink(&dh, datalink); + } else { + use_kern_pf = B_FALSE; + cap_open_read(icapfile); + + if (!nflg) { + names[0] = '\0'; + (void) strlcpy(names, icapfile, MAXPATHLEN); + (void) strlcat(names, ".names", MAXPATHLEN); + } + } + + if (Uflg) + use_kern_pf = B_FALSE; + + /* 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 (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; + } + + init_datalink(dh, snaplen, chunksize, &timeout, fp); + if (! qflg && ocapfile) + show_count(); + resetperm(); + net_read(dh, chunksize, filter, proc, flags); + dlpi_close(dh); + + 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_SUM) && !(flags & F_ALLSUM) && (vlanid != 0)) { + (void) snprintf(lp, MAXLINE, "VLAN#%i: ", vlanid); + 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 three 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); +} + +/* + * This function exists to make sure that VLAN information is + * prepended to summary lines displayed. The problem this function + * solves is how to display VLAN information while in summary mode. + * Each interpretor uses the get_sum_line and get_detail_line functions + * to get a character buffer to display information to the user. + * get_sum_line is the important one here. Each call to get_sum_line + * gets a buffer which stores one line of information. In summary mode, + * the last line generated is the line printed. Instead of changing each + * interpreter to add VLAN information to the summary line, the ethernet + * interpreter changes to call this function and set an ID. If the ID is not + * zero and snoop is in default summary mode, snoop displays the + * VLAN information at the beginning of the output line. Otherwise, + * no VLAN information is displayed. + */ +void +set_vlan_id(int id) +{ + vlanid = id; +} + +/* + * 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 */ +} + +/* + * Store a copy of linkname associated with the DLPI handle. + * Save errno before closing the dlpi handle so that the + * correct error value is used if 'err' is a system error. + */ +void +pr_errdlpi(dlpi_handle_t dh, const char *cmd, int err) +{ + int save_errno = errno; + char linkname[DLPI_LINKNAME_MAX]; + + (void) strlcpy(linkname, dlpi_linkname(dh), sizeof (linkname)); + + dlpi_close(dh); + errno = save_errno; + + pr_err("%s on \"%s\": %s", cmd, linkname, dlpi_strerror(err)); +} + +/* + * 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 link ] # Listen on named link\n"); + (void) fprintf(stderr, + "\t[ -s snaplen ] # Truncate packets\n"); + (void) fprintf(stderr, + "\t[ -I IP interface ] # Listen on named IP interface\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); + } +} |