/* spa daemon - sniffs packets, decrypts, sends commands via a FIFO pipe to the spa firewall daemon Compile using: gcc -O3 -Wall -o spad spad.c -lpcap 27Jul2005 Added several more items to the config file. Got the UDP code working properly. 19Jul2005 Fixed bug in the execl call. Changed how SPA data is shoveled down the FIFO so that there are now three parameters -- ip address, fifo length, and data. Makes things easier on the spa_engine.pl end to get the data and gives us error checking. 15Jul2005 Full amount of data wasn't making it down the FIFO, so am now calculating the size of the payload based off of packet size minus the headers, Added a realloc to prevent segfaults. 08Jul2005 General cleanup of code, added some more comments, fixed a few error handling bugs. 07Jul2005 More bug fixes, added a lot of debugging code, eliminated a fairly bad bug that caused a heap overflow (fuck!) which was potentially exploitable (double fuck!). 23Jun2005 Passes data to FIFO, both ip address and data, or ip address and port if doing firewall stuff. 11Apr2005 More cleanup. 24Feb2005 Initial attempt. */ /* includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define __USE_GNU #include /* some constants */ #define MAX_LINE 256 #define SPA_FIFO "/var/spa/spa" #define SPA_CONF "/usr/local/etc/spa.conf" /* globals */ struct bpf_program pcapfilter; struct in_addr net, mask; pcap_t *pd; char pcap_err[PCAP_ERRBUF_SIZE]; u_char *ebuf; char *dev = NULL; int spa_fd, fw_fd; int link_offset, firewall, verbose, debug, console; struct in_addr my_addr; union gpg_id { char char_server_id[4]; unsigned long long_server_id; } gpg_id; struct payload_packet { char payload[1400]; }; /* * This is the heart of the spa daemon. Currently supported is TCP and UDP. This * routine looks at every packet we receive, sees if the packet is addressed to * us, whether it is TCP or UDP, and checks the packet's contents for our GPG user * ID. On TCP packets only packets with the SYN flag are checked. Otherwise, if * we are doing firewalling, we pass the source address and destination port on * regular traffic (ACK packets) to maintain "state". * * The trick here is we don't watch for session setup and teardown to maintain * timers on "how long" we are seeing traffic for ports that have been opened, we * are seeing the regular traffic and simply pass it along. A lack of regular * traffic will lead to an eventual timeout. */ void process_packets(unsigned char *data1, struct pcap_pkthdr* h, unsigned char *p) { struct ip* ip = (struct ip *)(p + link_offset); struct tcphdr* tcp = (struct tcphdr *)(ip + 1); struct udphdr* udp = (struct udphdr *)(ip + 1); // not yet on icmp, errors compiling due to gnu vs non-gnu shit, will // figure it out later // struct icmphdr *icmp = (struct icmphdr *)(ip + 1); struct payload_packet *tcp_data = (struct payload_packet *)(tcp + 1); struct payload_packet *udp_data = (struct payload_packet *)(udp + 1); // struct payload_packet icmp_data = (struct payload_packet )(icmp + 1); int found=0,i=0,j=0,datasize=0,datalen=0; // ok if I try to free one of these later, I get an occassional double free // error -- is glibc as fucked as I think it is, or do I have some other // bug? maybe a REAL coder should look at this shite char *buf = (char *)malloc(sizeof(buf)); char *buf2 = (char *)malloc(sizeof(buf2)); // length of packet datalen = ntohs(ip->ip_len); if(my_addr.s_addr == ip->ip_dst.s_addr) { switch(ip->ip_p) { case IPPROTO_TCP: // we don't want to see a rst or fin packet if(tcp->rst || tcp->fin) break; // recalc the length of the payload by subtracting the header datalen = datalen - 40; // on tcp, we check the syn packets as well as out gpg id within // that syn packet at a specific offset. we check it two ways as // we've seen some odd byte ordering (endian issues) // also note, syn packets with payloads may get dropped by // certain firewalls, maybe I'll make that a configurable option // to not use/look for the syn and grab say an ack or something if((tcp->syn) && (((tcp_data->payload[8] == gpg_id.char_server_id[3]) && (tcp_data->payload[9] == gpg_id.char_server_id[2]) && (tcp_data->payload[10] == gpg_id.char_server_id[1]) && (tcp_data->payload[11] == gpg_id.char_server_id[0])) || ((tcp_data->payload[9] == gpg_id.char_server_id[3]) && (tcp_data->payload[8] == gpg_id.char_server_id[2]) && (tcp_data->payload[11] == gpg_id.char_server_id[1]) && (tcp_data->payload[10] == gpg_id.char_server_id[0])))) { // got one if(console) printf("TCP user packet received from %s (%d bytes)\n",(char *)inet_ntoa(ip->ip_src),datasize); else syslog(LOG_INFO,"TCP user packet received from %s (%d bytes)",(char *)inet_ntoa(ip->ip_src),datasize); found = 1; // we need to send ip address, total length, and data portion thru // the fifo, so we have to calc the total length of the fifo's // payload. the four covers space and final \n\n on the payload datasize = strlen((char *)inet_ntoa(ip->ip_src)) + datalen + 4; // gawd this is butt ugly but we have to know how much space our // datasize takes up in the payload as a part of a string if(datalen>0) datasize++; if(datalen>9) datasize++; if(datalen>99) datasize++; if(datalen>999) datasize++; // realloc our space buf2 = realloc((char *)buf2,datasize); for(i = 0; i < datasize; i++) buf2[i] = 0; // now we build our fifo buffer, first the ip address and total // fifo length go in sprintf(buf2,"%s %d ",(char *)inet_ntoa(ip->ip_src),datasize); j = datasize - datalen - 2; // next we add the data from the packet for(i=j;i<(datalen+j);i++) buf2[i] = tcp_data->payload[i-j]; // cap it off with a couple of \n's buf2[datasize-2] = 0x0a; buf2[datasize-1] = 0x0a; // then we send it if(debug) { if(console) printf("Sending SPA payload... \n"); else syslog(LOG_INFO,"DEBUG: Sending SPA payload... "); } j = write(spa_fd,buf2,datasize); if(debug) { if(console) printf("Sent %d bytes via FIFO\n",j); else syslog(LOG_INFO,"DEBUG: Sent %d bytes via FIFO",j); } } // if we didn't find our GPG payload and we are doing firewall stuff // we want to show regular traffic moving for tracking in the firewall if((!found) && (firewall) && (!tcp->syn)) { if(console) printf("Sending TCP packet from %s (dst port %d) for firewall timer processing\n",(char *)inet_ntoa(ip->ip_src),ntohs(tcp->dest)); else syslog(LOG_INFO,"Sending TCP packet from %s (dst port %d) for firewall timer processing",(char *)inet_ntoa(ip->ip_src),ntohs(tcp->dest)); sprintf(buf, "%s %d\n",(char *)inet_ntoa(ip->ip_src),ntohs(tcp->dest)); if(debug) { if(console) printf("Sending '%s' (%d bytes) via FIFO\n",buf,strlen(buf)); else syslog(LOG_INFO,"DEBUG: Sending '%s' (%d bytes) via FIFO",buf,strlen(buf)); } j = write(spa_fd,buf,strlen(buf)); if(debug) { if(console) printf("Sent %d bytes via FIFO\n",j); else syslog(LOG_INFO,"DEBUG: Sent %d bytes via FIFO",j); } } break; case IPPROTO_UDP: // same as above, basically, except udp datalen = datalen - 28; if(((udp_data->payload[8] == gpg_id.char_server_id[3]) && (udp_data->payload[9] == gpg_id.char_server_id[2]) && (udp_data->payload[10] == gpg_id.char_server_id[1]) && (udp_data->payload[11] == gpg_id.char_server_id[0])) || ((udp_data->payload[9] == gpg_id.char_server_id[3]) && (udp_data->payload[8] == gpg_id.char_server_id[2]) && (udp_data->payload[11] == gpg_id.char_server_id[1]) && (udp_data->payload[10] == gpg_id.char_server_id[0]))) { // got one if(console) printf("UDP user packet received from %s (%d bytes)\n",(char *)inet_ntoa(ip->ip_src),datasize); else syslog(LOG_INFO,"UDP user packet received from %s (%d bytes)",(char *)inet_ntoa(ip->ip_src),datasize); found = 1; // recalc our buffer size datasize = strlen((char *)inet_ntoa(ip->ip_src)) + datalen + 4; //ugly but we have to know how much space our datasize takes up if(datalen>0) datasize++; if(datalen>9) datasize++; if(datalen>99) datasize++; if(datalen>999) datasize++; // realloc our space buf2 = realloc((char *)buf2,datasize); for(i = 0; i < datasize; i++) buf2[i] = 0; // now we build our fifo buffer, first the ip address and total // fifo length go in sprintf(buf2,"%s %d ",(char *)inet_ntoa(ip->ip_src),datasize); j = datasize - datalen - 2; // add in the payload from the packet for(i=j;i<(datalen+j);i++) buf2[i] = tcp_data->payload[i-j]; // cap it off with a couple of \n's buf2[datasize-2] = 0x0a; buf2[datasize-1] = 0x0a; if(debug) { if(console) printf("Sending SPA payload...\n"); else syslog(LOG_INFO,"DEBUG: Sending SPA payload... "); } j = write(spa_fd,buf2,datasize); if(debug) { if(console) printf("Sent %d bytes via FIFO\n",j); else syslog(LOG_INFO,"DEBUG: Sent %d bytes via FIFO",j); } } // if we didn't find our gpg id, and we are firewalling stuff, // send it along if(!found && firewall) { if(console) printf("Sending UDP packet from %s (dst port %d) for firewall timer processing\n",(char *)inet_ntoa(ip->ip_src),ntohs(udp->dest)); else syslog(LOG_INFO,"Sending UDP packet from %s (dst port %d) for firewall timer processing",(char *)inet_ntoa(ip->ip_src),ntohs(udp->dest)); sprintf(buf, "%s %d\n",(char *)inet_ntoa(ip->ip_src),ntohs(udp->dest)); if(debug) { if(console) printf("Sending '%s' (%d bytes) via FIFO\n",buf,strlen(buf)); else syslog(LOG_INFO,"DEBUG: Sending '%s' (%d bytes) via FIFO",buf,strlen(buf)); } j = write(spa_fd,buf,strlen(buf)); if(debug) { if(console) printf("Sent %d bytes via FIFO\n",j); else syslog(LOG_INFO,"DEBUG: Sent %d bytes via FIFO",j); } } break; case IPPROTO_ICMP: /* if((icmp_data.payload[8] == GPG_SERVER_ID[0]) && (icmp_data.payload[9] == GPG_SERVER_ID[1]) && (icmp_data.payload[10] == GPG_SERVER_ID[2]) && (icmp_data.payload[11] == GPG_SERVER_ID[3])) { provide_data(inet_ntoa(ip->ip_src), icmp_data); } */ break; default: break; } } } /* * Your basic sniffing loop.... */ void receive_packets(void) { int snaplen = 65535, promisc = 1, to_ms = 1000; if (pcap_lookupnet(dev,&net.s_addr,&mask.s_addr, pcap_err) == -1) { perror(pcap_err); exit(-1); } if ((pd = pcap_open_live(dev, snaplen, promisc, to_ms, pcap_err)) == NULL) { perror(pcap_err); exit(-1); } switch(pcap_datalink(pd)) { case DLT_EN10MB: case DLT_IEEE802: link_offset = 14; break; case DLT_SLIP: link_offset = 16; break; case DLT_PPP: case DLT_NULL: link_offset = 4; break; case DLT_RAW: link_offset = 0; break; default: fprintf(stderr,"unsupported interface type\n"); exit(-1); } while (pcap_loop(pd,0,(pcap_handler)process_packets,0)); } /* * For the time being, I am assuming you need an IP address on the device you * are sniffing on to get this daemon running, so this routine pulls the IP * address from the specified interface. */ unsigned long get_myaddr(const char *device) { struct ifreq ifr; register struct sockaddr_in *sin; int fd; fd = socket(PF_INET, SOCK_DGRAM, 0); if (fd < 0) return(0); memset(&ifr, 0, sizeof(ifr)); sin = (struct sockaddr_in *)&ifr.ifr_addr; strncpy(ifr.ifr_name, device, sizeof(ifr.ifr_name)); ifr.ifr_addr.sa_family = AF_INET; if (ioctl(fd, SIOCGIFADDR, (char*) &ifr) < 0) { close(fd); return(0); } close(fd); return (sin->sin_addr.s_addr); } /* * This routine is an exercise in reading text from a text "config" file. It is * a sad and arduous process, yet it works. Granted, one fucking space extra and * the whole thing sucks ass. This is definitely NOT one of C's strong suits... */ int process_config_file(char *cf) { FILE *spa_conf = NULL; char buf_conf[MAX_LINE+1]; int line = 0; int confdev = 0,j = 0; int ret = 0; spa_conf = fopen(cf,"r"); if(spa_conf == NULL) { if(console) printf("Cannot find configuration file\n"); else syslog(LOG_INFO,"Cannot find configuration file"); exit(-1); } buf_conf[MAX_LINE] = 0; while(!feof(spa_conf)) { fgets(buf_conf,MAX_LINE,spa_conf); line++; if((buf_conf[0] == '#') || (buf_conf[0] == '\n')) continue; if(buf_conf[strlen(buf_conf)-1] == '\n') buf_conf[strlen(buf_conf)-1] = 0; if(!strncasecmp(buf_conf,"FIREWALL",8)) { if(debug) { if(console) printf("Firewall option processed in config\n"); else syslog(LOG_INFO,"DEBUG: Firewall option processed in config"); } if((!strncasecmp(strdup(strchr(buf_conf,' ')+1),"YES",3)) || (!strncasecmp(strdup(strchr(buf_conf,' ')+1),"1",1))) { if(debug) { if(console) printf("Firewall processing is on\n"); else syslog(LOG_INFO,"DEBUG: Firewall processing is on"); } firewall = 1; } else { if(debug) { if(console) printf("Firewall processing is off\n"); else syslog(LOG_INFO,"DEBUG: Firewall processing is off"); } } continue; } if((!strncasecmp(buf_conf,"INTERFACE",9)) && (dev == NULL)) { j = strlen(buf_conf); /* parse for lo (for testing), eth1, iwlan0 etc*/ if((j < 12) || (j > 16)) { if(debug) { if(console) printf("Invalid interface option in config\n"); else syslog(LOG_INFO,"DEBUG: Invalid interface option in config"); } ret = -1; } else { dev = (char *)strndup(strchr(buf_conf,' ')+1,j - 10); confdev = 1; if(debug) { if(console) printf("Using interface %s\n",dev); else syslog(LOG_INFO,"DEBUG: Using interface %s",dev); } continue; } } if(!strncasecmp(buf_conf,"ID",2)) { j = strlen(buf_conf); /* parse for ID */ if(j > 13) { if(debug) { if(console) printf("Invalid server ID in config\n"); else syslog(LOG_INFO,"DEBUG: Invalid server ID in config"); } ret = -1; } else { gpg_id.long_server_id = strtoul(strchr(buf_conf,' ')+1,NULL,0); if(debug) { if(console) printf("Looking for server GPG ID %s\n",strchr(buf_conf,' ')+1); else syslog(LOG_INFO,"DEBUG: Looking for server GPG ID %s",strchr(buf_conf,' ')+1); } continue; } } // stuff in conf we don't care about if(!strncasecmp(buf_conf,"SERVER",6)) continue; if(!strncasecmp(buf_conf,"PACKETTIMEOUT",13)) continue; if(!strncasecmp(buf_conf,"FIREWALLTIMEOUT",15)) continue; if(!strncasecmp(buf_conf,"DEBUG",5)) continue; if(!strncasecmp(buf_conf,"FIREWALLSTATE",13)) continue; if(!strncasecmp(buf_conf,"USERCONF",8)) continue; if(!strncasecmp(buf_conf,"IPFW",4)) continue; if(!strncasecmp(buf_conf,"GPGDIR",6)) continue; // non-fatal errors parsing the conf file (should these be fatal?) if(console) printf("Unable to parse line %d of %s\n",line,cf); else syslog(LOG_INFO,"Unable to parse line %d of %s",line,cf); } fclose(spa_conf); /* some kind of default */ if((dev != NULL) && (confdev)) { if(console) printf("Listening on interface defined in config file: %s\n",dev); else syslog(LOG_INFO,"Listening on interface defined in config file: %s",dev); } return(ret); } // usage routine void usage(char *prog) { fprintf(stdout,"USAGE:\n%s \n",prog); fprintf(stderr,"Options include:\n"); fprintf(stderr," -c read in config file from alternate location\n"); fprintf(stderr," -v verbose output\n"); fprintf(stderr," -C console mode, don't fork, good for testing\n"); fprintf(stderr," -D debugging output, good for finding problems\n"); fprintf(stderr," -h this help screen\n\n"); } int main(int argc, char **argv) { pid_t pid; char *prog; char * spa_conf_name = NULL; extern char *optarg; extern int optind; extern int optopt; extern int opterr; char ch; int ret; /* defaults */ prog = argv[0]; firewall = 0; verbose = 0; debug = 0; console = 0; /* process cmd line args */ while ((ch = getopt(argc, argv, "CDhvc:i:")) != EOF) switch(ch) { case 'C': console = 1; break; case 'D': debug = 1; break; case 'h': usage(prog); exit(0); case 'v': verbose = 1; break; case 'c': if(spa_conf_name) { fprintf(stderr,"You cannot use -c more than once\n"); exit(-1); } spa_conf_name = strdup(optarg); break; case 'i': if(dev) { fprintf(stderr,"You cannot use -i more than once\n"); exit(-1); } dev = strdup(optarg); break; default: usage(prog); exit(-1); } /* say hello... */ if(console) { printf("SPA - Single Packet Authentication\nCopyright (c) 2005 NMRC spa@nmrc.org\n"); printf("SPA daemon started, uid %d\n",getuid()); } else syslog(LOG_INFO,"SPA daemon started, uid %d",getuid()); if(dev != NULL) { if(console) printf("Listening on interface defined on command line: %s\n",dev); else syslog(LOG_INFO,"Listening on interface defined on command line: %s",dev); } /* assign a conf name if not given on the cmd line */ if(!spa_conf_name) spa_conf_name = SPA_CONF; /* read in and process the conf file */ ret = process_config_file(spa_conf_name); if(ret == -1) { if(console) printf("Error opening config file %s\n",spa_conf_name); else syslog(LOG_INFO,"Error opening config file %s",spa_conf_name); exit(-1); } /* can't listen to nothing, assign a default... */ if(dev == NULL) { dev = "eth0"; if(console) printf("Listening on default interface: %s\n",dev); else syslog(LOG_INFO,"Listening on default interface: %s",dev); } /* probe the network device and get its assigned address */ my_addr.s_addr = get_myaddr(dev); if(my_addr.s_addr == 0) { if(console) printf("Unable to get IP address from %s\n",dev); else syslog(LOG_INFO,"Unable to get IP address from %s",dev); } if(console) printf("Listening for SPA packets on %s\n",(char *)inet_ntoa(my_addr)); else syslog(LOG_INFO,"Listening for SPA packets on %s ",(char *)inet_ntoa(my_addr)); /* open the FIFO to talk to the spa engine */ mkfifo(SPA_FIFO, 0770); spa_fd = open(SPA_FIFO, O_RDWR); if(spa_fd == -1) { if(console) printf("Error opening SPA FIFO %s\n",SPA_FIFO); else syslog(LOG_INFO,"Error opening SPA FIFO %s",SPA_FIFO); exit(-1); } /* if in console mode, don't fork */ if(!console) { pid = fork(); switch(pid) { case -1: fprintf(stderr,"Unable to fork off, exiting\n"); exit(-1); case 0: // XXX // start spa engine execl("/usr/local/sbin/spa_engine.pl","&", 0); break; default: receive_packets(); } } else { execl("/usr/local/sbin/spa_engine.pl", "&", 0); receive_packets(); } exit(-1); }