imsniff-0.04/0000755000175000017500000000000010536572530012040 5ustar amayaamayaimsniff-0.04/src/0000755000175000017500000000000010306044514012616 5ustar amayaamayaimsniff-0.04/src/util.cpp0000644000175000017500000002370010306042516014301 0ustar amayaamaya#include "imsniff.h" #include #include #include int debug_level = 2; char *gettimestring4log (char *store) { time_t now=time (NULL); #ifdef WIN32 struct tm *stm; stm = localtime (&now); strncpy (store, asctime(stm), 24); #else struct tm stm; localtime_r (&now, &stm); asctime_r (&stm, store); #endif store[24]=0; return store; } int createdir4nick (u_char *nick) { if (nick && nick[0]) { char dir[1024]; strcpy (dir, chatlogdir); if (dir[strlen (dir)-1]!='/') strcat (dir,"/"); strcat (dir,(char *) nick); #ifdef WIN32 mkdir (dir); #else mkdir (dir,0700); #endif } return 0; } int log_switchboard_end (struct msn_connection *conn) { if (conn!=NULL && conn->log_full_path!=NULL) { FILE *o=fopen (conn->log_full_path, "a+t"); if (o!=NULL) { fprintf (o, "********************* CHAT END *************************\n"); fclose (o); } } return 0; } int log_switchboard_event (struct msn_connection *conn, const char *fmt, ...) { char strtme[27]; char *result; int firsttime = 0; if (conn == NULL || conn->owner == NULL) { log_debug (0, "No switchboard or unknown owner, can't log"); return -1; } if (conn->log_full_path==NULL && chatlogdir[0]!=0) { if (conn->users==NULL || conn->users[0]==NULL) { log_debug (0, "No known partipants in SB owned by %s, can't log", conn->owner); return -1; } size_t l=strlen (chatlogdir)+1+strlen ((char *) conn->owner)+1+ strlen ((char *) conn->users[0])+5; conn->log_full_path=(char *) malloc (l); if (conn->log_full_path==NULL) return -1; sprintf (conn->log_full_path,"%s/%s/%s.log",chatlogdir,conn->owner,conn->users[0]); log_debug (0, "Set SB log name to: %s",conn->log_full_path); firsttime = 1; } gettimestring4log (strtme); va_list ap; #ifdef WIN32 result=(char *) malloc (MAX_VPRINTF); if (result) { va_start(ap, fmt); vsnprintf (result, MAX_VPRINTF, fmt, ap); va_end (ap); } #else va_start (ap, fmt); vasprintf (&result,fmt,ap); va_end (ap); #endif if (conn->log_full_path!=NULL) { FILE *o=fopen (conn->log_full_path, "a+t"); if (o!=NULL) { if (firsttime) fprintf (o, "********************* CHAT START *************************\n"); fprintf (o, "%s | %s\n", strtme, result); fclose (o); } else { log_debug (0, "Failed to create/append SB event file at [%s]", conn->log_full_path); } } else { log_debug (0, "SB event: %s", result); } free (result); return 0; } int delete_profile (u_char *nick) { if (nick && nick[0]!=0 && chatlogdir[0]!=0) { log_debug (3, "Deleting profile [%s]", nick); char fn[1024]; sprintf (fn, "%s/%s/profile.log", chatlogdir, nick); unlink (fn); } return 0; } int delete_contact_list (u_char *nick) { if (nick && nick[0]!=0 && chatlogdir[0]!=0) { log_debug (3, "Deleting contact list for [%s]", nick); char fn[1024]; sprintf (fn, "%s/%s/contact_list.log", chatlogdir, nick); unlink (fn); } return 0; } int log_profile (u_char *nick, u_char *payload, int length) { char strtme[27]; gettimestring4log (strtme); log_debug (5, "Entry in log_profile"); if (nick && nick[0]==0) log_debug (3, "Profile for an unknown user, not logged"); else { u_char *prof = (u_char *) malloc (length + 1); if (prof == NULL) return OUT_OF_MEMORY; memcpy (prof, payload, length); prof[length]=0; if (chatlogdir[0]==0) { log_debug (1, "%s | %s | %s", strtme, nick, prof); } else { createdir4nick (nick); char fn[1024]; sprintf (fn, "%s/%s/profile.log", chatlogdir, nick); FILE *o=fopen (fn, "w"); // printf ("%s\n",fn); if (o!=NULL) { fwrite (prof, 1, length, o); fclose (o); } else { log_debug (0, "Failed to create profile file [%s]", fn); } } free (prof); } return 0; } int log_contact (u_char *nick, u_char *contact) { char strtme[27]; gettimestring4log (strtme); log_debug (5, "Entry in log_contact"); if (nick && nick[0]==0) log_debug (3, "Contact [%s] for an unknown user, not logged", contact); else { if (chatlogdir[0]==0) { log_debug (1, "%s | %s | Contact: %s", strtme, nick, contact); } else { createdir4nick (nick); char fn[1024]; sprintf (fn, "%s/%s/contact_list.log", chatlogdir, nick); FILE *o=fopen (fn, "a+t"); // printf ("%s\n",fn); if (o!=NULL) { fprintf (o, "%s | %s\n", strtme, contact); fclose (o); } else { log_debug (0, "Failed to create contact file [%s]", fn); } } } return 0; } int log_debug (int level, const char *fmt, ...) { char *result; char strtme[27]; if (level<=debug_level) { gettimestring4log (strtme); va_list ap; #ifdef WIN32 result=(char *) malloc (MAX_VPRINTF); if (result) { va_start(ap, fmt); vsnprintf (result, MAX_VPRINTF, fmt, ap); va_end (ap); } #else va_start (ap, fmt); vasprintf (&result,fmt,ap); va_end (ap); #endif if (debuglogdir[0]==0) { if (!daemonize) printf ("%s | %d | %s\n", strtme, level, result); } else { char fn[1024]; time_t now=time (NULL); #ifdef WIN32 struct tm *stm; stm= localtime (&now); sprintf (fn, "%s/imsniff_%04d-%02d-%02d.log", debuglogdir, stm->tm_year, stm->tm_mon, stm->tm_mday); #else struct tm stm; localtime_r (&now, &stm); sprintf (fn, "%s/imsniff_%04d-%02d-%02d.log", debuglogdir, stm.tm_year, stm.tm_mon, stm.tm_mday); #endif FILE *o=fopen (fn, "a+t"); if (o!=NULL) { fprintf (o, "%s | %d | %s\n", strtme, level, result); fclose (o); } } free (result); } return 0; } int log_event (u_char *nick, const char *fmt, ...) { char *result; va_list ap; char strtme[27]; #ifdef WIN32 result=(char *) malloc (MAX_VPRINTF); if (result) { va_start(ap, fmt); vsnprintf (result, MAX_VPRINTF, fmt, ap); va_end (ap); } #else va_start (ap, fmt); vasprintf (&result,fmt,ap); va_end (ap); #endif gettimestring4log (strtme); if (nick==NULL || nick[0]==0) log_debug (1, "Event for an unknown nick: %s", result); else { if (chatlogdir[0]==0) { log_debug (1, "Nick [%s] Event: [%s]", nick, result); } else { createdir4nick (nick); char fn[1024]; sprintf (fn, "%s/%s/events.log", chatlogdir, nick); FILE *o=fopen (fn, "a+t"); // printf ("%s\n",fn); if (o!=NULL) { fprintf (o, "%s | %s\n", strtme, result); fclose (o); } } } free (result); return 0; } int get_new_line_malloc (u_char **target, u_char *source, int length) { int must_free=0; if (length<2) // No room for \r\n { log_debug (5, "get_new_line_malloc: line too short."); return LINE_INCOMPLETE; } if (target!=NULL) { if (*target != NULL) free (*target); *target = (u_char *) malloc (length + 1); if (*target == NULL) return OUT_OF_MEMORY; memset (*target, 0, length+1); } u_char *now = source; // Where we are copying from u_char *work = (target==NULL)?NULL:*target; // Where, if anywhere, we are copying to int skipped=0; while (skipped=' ') { *work=*now; *work++; } skipped++; *now++; } if ( *(now)!='\r' || *(now+1)!='\n') // No \r\n? Not MSN or incomplete { log_debug (5, "get_new_line_malloc: Incomplete\n"); log_debug (5, "get_new_line_malloc: Source was: %s\n",source); // TODO: Fix not null-terminated! return -1; } return skipped+2; // Skip \r\n too } /* Note: *line must be zero-terminated (as returned by get_new_line_malloc) */ void dump_tokens (u_char **tokens) { if (tokens==NULL) return; int i=0; while (tokens[i]!=NULL) { log_debug (0, "Token %d: %s",i, tokens[i]); i++; } } u_char *strcpymalloc (u_char **target, u_char * src) { if (target==NULL) return NULL; if (*target!=NULL) free (*target); *target=(u_char *) malloc (strlen ((char *) src)+1); if (*target!=NULL) { strcpy ((char *) *target,(char *) src); } return *target; } void free_array (u_char ***tokens) { if (*tokens!=NULL) { int i=0; while ((*tokens)[i]!=NULL) { free ((*tokens)[i]); i++; } /* ...free the array itself */ free (*tokens); } } int get_tokens (u_char *line, u_char ***tokens, int max_tokens) { log_debug (5, "entry in get_tokens"); int capacity = (max_tokens==0)?50:max_tokens; /* First, delete the tokens if there are any ... */ free_array(tokens); *tokens=(u_char **) malloc (sizeof (u_char *) * (capacity +1)); // Final one is NULL if (*tokens==NULL) return OUT_OF_MEMORY; int num=0; /* Number of tokens added so far */ u_char *now = line; u_char *newtoken; for (;;) { size_t i; /* Skip spaces and control stuff */ while (*now<=' ' && *now!=0) { now++; } if (*now==0) /* End of line */ break; i=0; if (max_tokens==num+1 && max_tokens!=0) i=strlen ((char *) now); else { while (now[i]>' ') /* Look ahead, how long is the next token? */ i++; } newtoken=(u_char *) malloc (i+1); memcpy (newtoken, now, i); now = now + i; newtoken[i]=0; if (num==capacity) { capacity += 10; *tokens = (u_char **) realloc (*tokens, sizeof (u_char *) * (capacity +1)); if (*tokens==NULL) /* A bit unstable now I'm afraid */ return OUT_OF_MEMORY; } (*tokens)[num]=newtoken; if (num==7) { exit (7); } num++; } (*tokens)[num]=NULL; return num; } int get_value_from_hex (char c) { c=toupper (c); if (c>='0' && c<='9') return c-'0'; if (c>='A' && c<='F') return c-'A'; return -1; } u_char *urldecode (u_char *src) { u_char *tmp = (u_char *) malloc (strlen ((char *) src) + 1); u_char *c = src; if (tmp==NULL) return src; memset (tmp, 0, strlen ((char *) src) +1); u_char *now = tmp; while (*c) { if (*c!='%') { *now = *c; now++; c++; } else { if (*(c+1)==0 || *(c+2)==0) // ?? Doesn't look good. { free (tmp); return src; } int v1 = get_value_from_hex (* (c+1)); int v2 = get_value_from_hex (* (c+2)); if (v1==-1 || v2==-1) { free (tmp); return src; } *now = (v1*16+v2); now++; c+=3; } } strcpy ((char *) src, (char *) tmp); free (tmp); return src; } imsniff-0.04/src/pcap_stuff.cpp0000644000175000017500000000123010306042516015450 0ustar amayaamaya/* PCAP related stuff should go here */ #include "imsniff.h" struct datalink_type { int pcap_nr; char *name; int offset; } dtl [] = { { DLT_EN10MB, "Ethernet", 14}, { DLT_PPP, "PPP", 4}, { DLT_PPP_ETHER, "PPPoE", 4}, { DLT_LINUX_SLL, "Linux cooked sockets", 16}, { -1, NULL, -1} }; int get_datalink_type (pcap_t *dh) { return pcap_datalink (dh); } int get_datalink_info (pcap_t *dh, char **name, int *offset) { int i = 0; int link = pcap_datalink (dh); while (dtl[i].offset != -1) { if (link==dtl[i].pcap_nr) { *name = dtl[i].name; *offset = dtl[i].offset; return 0; } i++; } *name = NULL; *offset = -1; return -1; } imsniff-0.04/src/imsniff.cpp0000644000175000017500000005366410306045414014773 0ustar amayaamaya#include "imsniff.h" //char chatlogdir[1024]="/tmp/imsniff"; //char debuglogdir[1024]="/var/log"; char chatlogdir[MAX_DIR_LENGTH+1]=""; char debuglogdir[MAX_DIR_LENGTH+1]=""; int daemonize=0; char *currentversion = "0.04"; int data_offset = -1; char *devname=NULL; int promisc=0; void show_help (void); int analyze_packet (u_char *data) { struct msn_cmd { char *command; int type; } cmds[]= { { "MSG", PT_MSN_MSG}, { "USR", PT_MSN_USR}, { "ANS", PT_MSN_ANS}, { "IRO", PT_MSN_IRO}, { "JOI", PT_MSN_JOI}, { "OUT", PT_MSN_OUT}, { "PNG", PT_MSN_IGNORE}, { "QNG", PT_MSN_IGNORE}, { "CAL", PT_MSN_IGNORE}, { "BYE", PT_MSN_BYE}, { "CHG", PT_MSN_CHG}, { "ILN", PT_MSN_ILN}, { "SYN", PT_MSN_SYN}, { "GTC", PT_MSN_IGNORE}, { "BLP", PT_MSN_IGNORE}, { "PRP", PT_MSN_PRP}, { "NLN", PT_MSN_NLN}, { "QRY", PT_MSN_IGNORE}, { "LST", PT_MSN_LST}, { "BPR", PT_MSN_IGNORE}, { "CHL", PT_MSN_IGNORE}, { "FLN", PT_MSN_FLN}, { "XFR", PT_MSN_IGNORE}, { "VER", PT_MSN_IGNORE}, { "CVR", PT_MSN_IGNORE}, { NULL, 0} }; for (int i=0; cmds[i].command!=NULL; i++) { // printf ("[%s] [%s] [%d]\n", data, cmds[i].command, strlen (cmds[i].command)); if (strncmp ((char *) data,cmds[i].command, strlen (cmds[i].command))==0) { return cmds[i].type; } } return PT_UNKNOWN; } void process_packet (long packet_id, struct pcap_pkthdr *header, const u_char *packet) { struct tm *ltime; char timestr[16]; /* Make sure we have enough data to read the IP header*/ if ( header->caplen < (data_offset + sizeof (struct ip_header))) return; /* Get IP header, which comes after the datalink header (14 bytes for ethernet) */ struct ip_header *ih = (ip_header *) (packet + data_offset); /* TCP header comes right after */ int ip_len = (ih->ver_ihl & 0xf) * 4; struct tcp_header *th = (tcp_header *) ((u_char*)ih + ip_len); if ( ( (u_char *) th+sizeof (struct tcp_header)) > (packet + header->caplen) ) { /* the TCP header ends beyond the received data */ return; } int source_port = ntohs (th->th_sport); int destination_port = ntohs (th->th_dport); if (source_port!=1863 && destination_port!=1863) return; u_short th_len = (th->off_unused& 0xF0) >> 4; // Convert time ltime=localtime(&header->ts.tv_sec); strftime( timestr, sizeof timestr, "%H:%M:%S", ltime); u_char *data_onpacket = (u_char *) th +th_len*4; int data_onpacket_size = header->caplen - th_len*4 - ip_len - data_offset; int comesfromA = 0; int data_size; u_char *payload; int allzeros = 1; for (int i=0; isaddr, source_port, &ih->daddr, destination_port, create_no); log_debug (3, "Processing packet with ID: %ld", packet_id); if (ci==NULL) { // No. log_debug (3, "Packet is not from a known conversation"); payload=(u_char *) malloc (data_onpacket_size); memcpy (payload, data_onpacket, data_onpacket_size); data_size=data_onpacket_size; } else { if (is_from_A(ci, &ih->saddr, source_port)==1) { log_debug (3, "Packet is from a known conversation (A), pending =%d", ci->pending_A_length); payload=(u_char *) malloc (data_onpacket_size + ci->pending_A_length); if (ci->pending_A_length>0) memcpy (payload, ci->pending_A, ci->pending_A_length); memcpy (payload + ci->pending_A_length, data_onpacket, data_onpacket_size); data_size=data_onpacket_size + ci->pending_A_length; ci->pending_A_length=0; if (ci->pending_A!=NULL) { free (ci->pending_A); } ci->pending_A=NULL; comesfromA=1; } else { log_debug (3, "Packet is from a known conversation (B), pending =%d", ci->pending_B_length); payload=(u_char *) malloc (data_onpacket_size + ci->pending_B_length); if (ci->pending_B_length>0) memcpy (payload, ci->pending_B, ci->pending_B_length); memcpy (payload + ci->pending_B_length, data_onpacket, data_onpacket_size); data_size=data_onpacket_size + ci->pending_B_length; ci->pending_B_length=0; if (ci->pending_B!=NULL) { free (ci->pending_B); } ci->pending_B=NULL; comesfromA=0; } } log_debug (3, "Real size = %d, current size=%d",data_onpacket_size, data_size); int pos = 0; u_char *ref = payload; while (data_size) { log_debug (5, "In while (data_size)"); int packet_type; if (data_size<3) packet_type= PT_UNKNOWN; else packet_type = analyze_packet (payload); log_debug (5, "data_size = %d, packet type =%d", data_size, packet_type); char fromto[1024]; sprintf(fromto, "%d.%d.%d.%d:%d -> %d.%d.%d.%d:%d", ih->saddr.byte1, ih->saddr.byte2, ih->saddr.byte3, ih->saddr.byte4, source_port, ih->daddr.byte1, ih->daddr.byte2, ih->daddr.byte3, ih->daddr.byte4, destination_port); log_debug (3, "%s",fromto); log_debug (5, "%s.%.6d longitud:%d (cap: %d)", timestr, header->ts.tv_usec, header->len, header->caplen); u_char prefix[1024]; sprintf ((char *) prefix, "%s | %s | ",timestr, fromto); // int processed; int bytes_parsed=-1; // Number of bytes the handler managed // Vamos a ver si es un MSG y si tiene buena // pinta despues char line_x[30]=""; switch (packet_type) { case PT_UNKNOWN: if (ci==NULL) { log_debug (3, "Unknown data from an unknown conversation, skipping."); bytes_parsed = data_size; // No sabemos lo que es, mejor anular todo el paquete break; } else { log_debug (3, "Unknown data but from a known MSN conversation, attempting to skip and resume"); } line_x[0]=0; for (int j=0; j0) log_debug (5,"%s",line_x); line_x[0]=0; for (int j=0; j0) log_debug (5,"%s",line_x); bytes_parsed = get_new_line_malloc (NULL,payload,data_size); break; case PT_MSN_SYN: // List sync. bytes_parsed = handler_msn_syn (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; case PT_MSN_MSG: bytes_parsed = handler_msn_msg (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; case PT_MSN_USR: // User identification and authentification bytes_parsed = handler_msn_usr (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; case PT_MSN_ANS: /* Entry in a switchboard after being invited */ bytes_parsed = handler_msn_ans (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; case PT_MSN_IRO: /* Initial user list in a switchboard */ bytes_parsed = handler_msn_iro (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; case PT_MSN_JOI: // Usuario entrando en el switchboard bytes_parsed = handler_msn_joi (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; case PT_MSN_OUT: // Session termination bytes_parsed = handler_msn_out (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; case PT_MSN_IGNORE: // Ignore these packets bytes_parsed = handler_msn_ignore (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; case PT_MSN_BYE: // User leaving switchboard bytes_parsed = handler_msn_bye (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; case PT_MSN_CHG: // User changing status bytes_parsed = handler_msn_chg (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; case PT_MSN_ILN: // Initial user status bytes_parsed = handler_msn_iln (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; case PT_MSN_NLN: // User changing status bytes_parsed = handler_msn_nln (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; case PT_MSN_LST: // Contact list bytes_parsed = handler_msn_lst (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; case PT_MSN_PRP: // Contact list bytes_parsed = handler_msn_prp (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; case PT_MSN_FLN: // Contact list bytes_parsed = handler_msn_fln (payload, data_size, ih->saddr, source_port, ih->daddr, destination_port); break; default: log_debug (0, "this is a bug! (packet_Type=%d)", packet_type); bytes_parsed = data_size; /* Don't know what it is, skip the whole thing */ break; } switch (bytes_parsed) { case NOT_MSN: bytes_parsed = data_size; /* Don't know what it is, skip the whole thing */ data_size = 0; log_debug (3, "Skipping rest of packet"); break; case LINE_INCOMPLETE: log_debug (3, "Processed = LINE_INCOMPLETE, no complete line available"); if (ci!=NULL) /* It's from a known conversation, store the rest of stuff for later */ { if (comesfromA) { log_debug (3, "It's from a known conversation (A > B), added to pending data"); if (ci->pending_A!=NULL) log_debug (0, "pending_A has data!"); ci->pending_A=(u_char *) malloc (data_size); memcpy (ci->pending_A,payload,data_size); ci->pending_A_length=data_size; } else { log_debug (3, "It's from a known conversation (B > A), added to pending data"); if (ci->pending_B!=NULL) log_debug (0, "pending_B has data!"); ci->pending_B=(u_char *) malloc (data_size); memcpy (ci->pending_B,payload,data_size); ci->pending_B_length=data_size; } if (ci->pending_A_length>8192 || ci->pending_B_length>8192) { /* No payload is this long - skip everything */ log_debug (3, "Payload too long, skipping everything"); remove_msn_connection(ci); } } bytes_parsed = data_size; /* Get out */ data_size = 0; break; case CONN_DESTROYED: bytes_parsed = data_size; /* Pointless to go on... */ data_size = 0; break; case OUT_OF_MEMORY: log_debug (0, "Out of memory somewhere, likely to crash soon"); bytes_parsed = data_size; /* Get out */ data_size = 0; break; default: data_size -=bytes_parsed; payload += bytes_parsed; } } log_debug (5, "Freeing stuff"); /* if (dump!=NULL) free (dump); */ if (ref!=NULL) free (ref); log_debug (5, "Leaving process_packet"); } #ifndef WIN32 int go_daemon (void) { pid_t pid, sid; /* Fork off the parent process */ pid = fork(); if (pid < 0) return -1; if (pid>0) return 1; /* Change the file mode mask */ umask(0); /* Create a new SID for the child process */ sid = setsid(); if (sid < 0) { return -1; } /* Change the current working directory */ if ((chdir("/")) < 0) { return -1; } /* Close out the standard file descriptors */ close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); return 0; } #endif #ifdef WIN32 /* This function blatantly ripped from http://www.winpcap.org/docs/docs31/html/group__wpcap__tut1.html */ void list_devices (void) { pcap_if_t *alldevs; pcap_if_t *d; int i=0; char errbuf[PCAP_ERRBUF_SIZE]; /* Retrieve the device list from the local machine */ if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL /* auth is not needed */, &alldevs, errbuf) == -1) { fprintf(stderr,"Error in pcap_findalldevs_ex: %s\n", errbuf); exit(1); } /* Print the list */ for(d= alldevs; d != NULL; d= d->next) { printf("%d. %s", ++i, d->name); if (d->description) printf(" (%s)\n", d->description); else printf(" (No description available)\n"); } if (i == 0) { printf("\nNo interfaces found! Make sure WinPcap is installed.\n"); return; } /* We don't need any more the device list. Free it */ pcap_freealldevs(alldevs); } #endif int process_parms (int argc, char *argv[]) { int i=1; while (i directory where chat logs, event logs\n"); printf (" and contact lists will be stored. If\n"); printf (" not specified, output will go to stdout\n"); printf ("debug_dir -> directory where general debug information\n"); printf (" and information that cannot go to the chat\n"); printf (" dir for whatever reason will be stored.\n"); printf (" If not specified, output will go to stdout\n"); printf ("-p -> Use promiscuous mode. Whether you need it\n"); printf (" or not depends on your setup.\n"); #ifndef WIN32 printf ("-d -> Daemonize, i.e. make imsniff put itself\n"); #endif printf (" in the background.\n"); printf ("-vvv -> Debug mode. The more 'v' you use, the more\n"); printf (" verbose output will be\n"); printf ("-offset -> Offset from packet start to data start, in case\n"); printf (" you are using a datalink layer imsniff does not\n"); printf (" know about\n"); printf ("interface -> Device name (such as eth0) to listen to.\n\n"); } void read_file (FILE *f) { long length; fseek (f, 0, SEEK_END); length = ftell (f); fseek (f, 0, SEEK_SET); char *data = (char *) malloc (length + 1); fread (data, 1, length, f); fclose (f); data[length]=0; char *c = data; int line = 1; while (c< (data + length)) { while (*c && (*c=='\n' || *c=='\r')) c++; if (!*c) break; char *d = strchr (c, '\n'); if (d == NULL) d= strchr (c, '\r'); if (d != NULL) *d = 0; char *next = c+strlen (c)+1; d = strchr (c, '#'); if (d != NULL) *d = 0; d= strchr (c, '='); if (d != NULL) *d = ' '; if (get_tokens((u_char*) c, &line_tokens, 0)==2) { log_debug (0, "Parsing %s", c); u_char *s = line_tokens[0]; while (*s) { *s=tolower (*s); s++; } if (strcmp ((char *) line_tokens[0], "chatdir")==0) { strncpy (chatlogdir, (char *) line_tokens[1], MAX_DIR_LENGTH); chatlogdir[MAX_DIR_LENGTH]=0; log_debug (0, "chatdir = %s", chatlogdir); } if (strcmp ((char *) line_tokens[0], "debugdir")==0) { strncpy (debuglogdir, (char *) line_tokens[1], MAX_DIR_LENGTH); debuglogdir[MAX_DIR_LENGTH]=0; log_debug (0, "debuglogdir = %s", debuglogdir); } #ifndef WIN32 if (strcmp ((char *) line_tokens[0], "daemonize")==0) { daemonize=atoi ((char *) line_tokens[1]); log_debug (0, "daemonize = %d", daemonize); } #endif if (strncmp ((char *) line_tokens[0], "promisc", 7)==0) { promisc=atoi ((char *) line_tokens[1]); log_debug (0, "promisc = %d", daemonize); } if (strcmp ((char *) line_tokens[0], "verbose")==0) { debug_level=atoi ((char *) line_tokens[1]); log_debug (0, "verbose = %d", debug_level); } if (strcmp ((char *) line_tokens[0], "data_offset")==0) { data_offset=atoi ((char *) line_tokens[1]); log_debug (0, "data_offset = %d", data_offset); } if (strcmp ((char *) line_tokens[0], "interface")==0) { strcpymalloc((u_char **) &devname, line_tokens[1]); log_debug (0, "interface = %s", devname); } } else { log_debug (0, "Skipping %s", c); } c=next; line++; } free (data); } int parse_config (char *myname) { char *c = myname + strlen (myname); char *fn; FILE *f; while (c>=myname && *c!='/' && *c!='\\') c--; c++; /* Start of real name */ if (strlen (c) == 0) /* Uh? */ return 0; if (strchr (c, '%')!=NULL) /* Prevent strange things */ return -1; fn = (char *) malloc (strlen (c) + 11); if (fn == NULL) return -1; sprintf (fn, "/etc/%s.conf", c); f= fopen (fn, "r"); if (f == NULL) { sprintf (fn, "%s.conf", c); f = fopen (fn, "r"); } if (f!=NULL) { read_file (f); } free (fn); return 0; } int main (int argc, char *argv[]) { char errbuf[PCAP_ERRBUF_SIZE]; pcap_t *dh; struct bpf_program filter; char filter_app[] = "ip and tcp"; bpf_u_int32 mask; bpf_u_int32 net; struct pcap_pkthdr header; const u_char *packet; if (parse_config (argv[0])) { printf ("Failed to parse config file, leaving\n"); return -1; } if (process_parms (argc,argv)) { printf ("Bad parameters, leaving\n"); return -1; } if (devname==NULL) { #ifdef WIN32 printf ("A device number is required. Run with -list to get a list.\n"); #else printf ("A device name (such as eth0) is required\n"); #endif exit (-1); } if (daemonize && debuglogdir[0]==0) { printf ("In daemon mode at least a debug log directory (-dd) must be used\n"); exit (-1); } #ifndef WIN32 if (daemonize) { switch (go_daemon()) { case -1: daemonize=0; log_debug (0, "Failed to become a daemon!"); exit (-1); case 1: // We are the parent. Exit and let the child on its own exit (0); case 0: log_debug (3, "Successfully became a daemon."); break; default: daemonize=0; log_debug (0, "This is a bug!"); exit (-1); } } #endif #ifdef WIN32 pcap_if_t *alldevs; int inum = atoi (devname); if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1) { fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf); exit(1); } int devnum=0; pcap_if_t *d; for(d=alldevs; d; d=d->next) { devnum++; printf("%d. %s", devnum, d->name); if (d->description) printf(" (%s)\n", d->description); else printf(" (No description available)\n"); } if(devnum==0) { printf("\nNo interfaces found! Make sure WinPcap is installed.\n"); return -1; } if(inum < 1 || inum > devnum) { printf("\nInterface number out of range.\n"); /* Free the device list */ pcap_freealldevs(alldevs); return -1; } /* Jump to the selected adapter */ for(d=alldevs, devnum=0; devnum< inum-1 ;d=d->next, devnum++) {;} strcpymalloc ( (u_char **) &devname, (u_char *) d->name); #endif log_debug (3, "Getting address and mask for device %s...",devname); if (pcap_lookupnet(devname, &net, &mask, errbuf)==-1) { log_debug (0, "error [%s]",errbuf); exit (-1); } log_debug (3, "OK"); log_debug (3, "Opening device..."); #ifdef WIN32 /* At this point, we don't need any more the device list. Free it */ dh = pcap_open (devname, 65535, promisc?PCAP_OPENFLAG_PROMISCUOUS:0, 1000, NULL, errbuf); pcap_freealldevs(alldevs); #else dh = pcap_open_live (devname, 65535, promisc, 1000, errbuf); #endif if (dh==NULL) { log_debug (0, "error [%s]",errbuf); exit (-1); } log_debug (3, "OK"); if (data_offset == -1) /* User didn't force an offset, try to find out */ { char *dln; log_debug (3, "Checking datalink type..."); if (get_datalink_info (dh, &dln, &data_offset)) { log_debug (0, "not supported. Please play with the -offset option (see docs)"); exit (-1); } log_debug (3, "OK, %s, offset = %d", dln, data_offset); } else { log_debug (1, "Using an user defined offset [%d], for datalink type [%d], good luck!", data_offset, get_datalink_type(dh)); } log_debug (3, "Compiling filter [%s]...",filter_app); if (pcap_compile(dh, &filter, filter_app, 0, net)==-1) { log_debug (0, "error [%s]",errbuf); exit (-1); } log_debug (3, "OK"); log_debug (3, "Setting filter..."); if (pcap_setfilter(dh, &filter)==-1) { log_debug (0, "error [%s]",errbuf); exit (-1); } log_debug (3, "OK"); log_debug (3, "Entering capture loop..."); if (chatlogdir[0]!=0) #ifdef WIN32 mkdir (chatlogdir); #else mkdir (chatlogdir,0700); #endif if (debuglogdir[0]!=0) #ifdef WIN32 mkdir (debuglogdir); #else mkdir (debuglogdir,0700); #endif long packet_count = 0; while (1) { packet = pcap_next(dh, &header); if (packet==NULL) { log_debug (5, "No packet received"); continue; } process_packet (++packet_count, &header,packet); } } imsniff-0.04/src/imsniff.h0000644000175000017500000001601710306042421014422 0ustar amayaamaya#include #include #include #include #ifdef WIN32 #include #include #else #include #include #endif #include #include #include #include #include /* 4 bytes IP address */ typedef struct ip_address { u_char byte1; u_char byte2; u_char byte3; u_char byte4; }ip_address; /* IPv4 header */ typedef struct ip_header{ u_char ver_ihl; // Version (4 bits) + Internet header length (4 bits) u_char tos; // Type of service u_short tlen; // Total length u_short identification; // Identification u_short flags_fo; // Flags (3 bits) + Fragment offset (13 bits) u_char ttl; // Time to live u_char proto; // Protocol u_short crc; // Header checksum ip_address saddr; // Source address ip_address daddr; // Destination address u_int op_pad; // Option + Padding }ip_header; /* TCP Header */ #define TH_FIN 0x01 #define TH_SYN 0x02 #define TH_RST 0x04 #define TH_PUSH 0x08 #define TH_ACK 0x10 #define TH_URG 0x20 #define TH_ECE 0x40 #define TH_CWR 0x80 #define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR) typedef struct tcp_header { u_short th_sport; /* source port */ u_short th_dport; /* destination port */ unsigned th_seq; /* sequence number */ unsigned th_ack; /* acknowledgement number */ u_short off_unused; /* 4 bits data offset, 4 bits unused */ union { u_char byte; struct { unsigned unused: 2; unsigned th_fin: 1; unsigned th_syn: 1; unsigned th_rst: 1; unsigned th_push: 1; unsigned th_ack: 1; unsigned th_urg: 1; } bits; } th_flags; u_short th_win; /* window */ u_short th_sum; /* checksum */ u_short th_urp; /* urgent pointer */ }tcp_header; enum e_who_is_server { unknown = 0, endpointA = 1, endpointB = 2 }; enum e_msn_conn_create { create_no=0, create_yes=1, create_replace=2 /* If found, delete data */ }; enum msn_conn_types { type_unknown = 0, type_notification_server = 1, type_switchboard = 2 }; struct msn_connection { ip_address IP_A; u_short port_A; ip_address IP_B; u_short port_B; // Should always be 1863 e_who_is_server whowserver; u_char *owner; u_char **users; // If this is a switchboard, ID of users here int num_users; enum msn_conn_types conn_type; struct msn_connection *previous, *next; char *log_full_path; int pending_A_length, pending_B_length; u_char *pending_A; u_char *pending_B; }; #define LINE_INCOMPLETE -1 #define OUT_OF_MEMORY -2 #define NOT_MSN -3 #define CONN_DESTROYED -4 #define BT_UNKNOWN 0 #define BT_TEXTPLAIN 1 #define BT_CONTROL 2 #define BT_PROFILE 3 #define BT_INITIAL_EMAIL 4 #define BT_NEW_EMAIL 5 #define PT_UNKNOWN 0 #define PT_MSN_MSG 1 #define PT_MSN_USR 2 #define PT_MSN_ANS 3 #define PT_MSN_IRO 4 #define PT_MSN_JOI 5 #define PT_MSN_OUT 6 #define PT_MSN_IGNORE 7 // Non interesting packets #define PT_MSN_CAL 8 #define PT_MSN_BYE 9 #define PT_MSN_CHG 10 #define PT_MSN_ILN 11 #define PT_MSN_NLN 12 #define PT_MSN_LST 13 #define PT_MSN_PRP 14 #define PT_MSN_FLN 15 #define PT_MSN_SYN 16 #define HDR_CONTENT_TYPE "Content-Type: " #define HDR_TYPINGUSER "TypingUser: " int handler_msn_usr (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); int handler_msn_chg (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); int handler_msn_ans (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); int handler_msn_iro (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); int handler_msn_joi (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); int handler_msn_out (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); int handler_msn_bye (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); int handler_msn_fln (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); int handler_msn_nln (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); int handler_msn_iln (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); int handler_msn_syn (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); int handler_msn_lst (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); int handler_msn_msg (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); int handler_msn_prp (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); int handler_msn_ignore (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port); struct msn_connection *get_or_create_msn_connection (ip_address *source_ip, int source_port, ip_address *target_ip, int target_port, enum e_msn_conn_create create); void set_owner (struct msn_connection *conn, u_char *owner); void set_as_server (struct msn_connection *conn, ip_address *ip, int port); void add_user_to_sb (struct msn_connection *conn, u_char *user); int is_from_server (struct msn_connection *conn, ip_address *ip, int port); int remove_msn_connection (struct msn_connection *conn); int is_from_A (struct msn_connection *conn, ip_address *ip, int port); int log_event (u_char *nick, const char *fmt, ...); int log_debug (int level, const char *fmt, ...); int log_contact (u_char *nick, u_char *contact); int log_profile (u_char *nick, u_char *payload, int length); int log_switchboard_end (struct msn_connection *conn); int log_switchboard_event (struct msn_connection *conn, const char *fmt, ...); int delete_contact_list (u_char *nick); int delete_profile (u_char *nick); u_char *urldecode (u_char *src); u_char *strcpymalloc (u_char **target, u_char * src); void free_array (u_char ***tokens); int get_new_line_malloc (u_char **target, u_char *source, int length); int get_tokens (u_char *line, u_char ***tokens, int max_tokens); void dump_tokens (u_char **tokens); int get_datalink_info (pcap_t *dh, char **name, int *offset); int get_datalink_type (pcap_t *dh); #define MAX_DIR_LENGTH 1024 #define MAX_VPRINTF 4096 extern char chatlogdir[MAX_DIR_LENGTH+1]; extern char debuglogdir[MAX_DIR_LENGTH+1]; extern int debug_level; extern int daemonize; extern u_char **line_tokens; imsniff-0.04/src/msn_conntrack.cpp0000644000175000017500000001641210306042775016174 0ustar amayaamaya/* THE CODE HERE TAKES CARE OF KEEPING TRACKING OF THE INFORMATION WE COLLECT ABOUT ONGOING TCP CONNECTIONS */ #include "imsniff.h" struct msn_connection *msn_conns_first = NULL; struct msn_connection *msn_conns_last = NULL; void add_user_to_sb (struct msn_connection *conn, u_char *user) { if (conn!=NULL && conn->users!=NULL) { int i=0; while (inum_users) { if (strcmp ((char *) conn->users[i], (char *) user)==0) return; // Don't duplicate i++; } } log_debug (5, "Adding user [%s] to SB",user); conn->users=(u_char **) realloc (conn->users, sizeof (u_char *) * (conn->num_users+1)); log_debug (5, "Done realloc"); if (conn->users!=NULL) { conn->users[conn->num_users]=(u_char *) malloc (strlen ((char *) user) +1 ); log_debug (5, "Done malloc"); strcpy ((char *) conn->users[conn->num_users],(char *) user); log_debug (5, "Done strcpy"); conn->num_users++; } log_debug (5, "Done, number of users now = %d",conn->num_users); } void clear_msn_connection (struct msn_connection *conn) { log_debug (3, "Clearing connection %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d", conn->IP_A.byte1,conn->IP_A.byte2,conn->IP_A.byte3,conn->IP_A.byte4, conn->port_A, conn->IP_B.byte1, conn->IP_B.byte2,conn->IP_B.byte3,conn->IP_B.byte4,conn->port_B); if (conn->owner!=NULL) { free (conn->owner); conn->owner=NULL; } conn->conn_type=type_unknown; if (conn->users!=NULL) { int i=0; while (inum_users) { free (conn->users[i]); i++; } free (conn->users); conn->num_users=0; } if (conn->pending_A!=NULL) free (conn->pending_A); if (conn->pending_B!=NULL) free (conn->pending_B); if (conn->log_full_path!=NULL) free (conn->log_full_path); conn->pending_A_length=0; conn->pending_B_length=0; conn->pending_A=NULL; conn->pending_B=NULL; conn->log_full_path=NULL; conn->whowserver=unknown; } void set_owner (struct msn_connection *conn, u_char *owner) { if (conn==NULL || owner==NULL) { log_debug (0, "Entry in set_owner() with NULL parameter(s)"); return; } log_debug (5, "Setting owner [%s] to connection %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d", owner, conn->IP_A.byte1,conn->IP_A.byte2,conn->IP_A.byte3,conn->IP_A.byte4, conn->port_A, conn->IP_B.byte1, conn->IP_B.byte2,conn->IP_B.byte3,conn->IP_B.byte4,conn->port_B); if (conn->owner != NULL) { if (strcmp ((char *) conn->owner, (char *) owner)) { log_debug (0, "Warning: Owner change in MSN connection, this looks like a bug"); } else { log_debug (5, "set_owner(): Owner match, all OK"); } } else { log_debug (5, "(no previous owner)"); } strcpymalloc (&conn->owner, owner); } int is_from_A (struct msn_connection *conn, ip_address *ip, int port) { if (conn==NULL) return -1; if (conn->IP_A.byte1 == ip->byte1 && conn->IP_A.byte2 == ip->byte2 && conn->IP_A.byte3 == ip->byte3 && conn->IP_A.byte4 == ip->byte4 && conn->port_A == port) { return 1; } if (conn->IP_B.byte1 == ip->byte1 && conn->IP_B.byte2 == ip->byte2 && conn->IP_B.byte3 == ip->byte3 && conn->IP_B.byte4 == ip->byte4 && conn->port_B == port) { return 0; } return -1; // Not from any of them */ } int is_from_server (struct msn_connection *conn, ip_address *ip, int port) { if (conn==NULL) return -1; if (conn->IP_A.byte1 == ip->byte1 && conn->IP_A.byte2 == ip->byte2 && conn->IP_A.byte3 == ip->byte3 && conn->IP_A.byte4 == ip->byte4 && conn->port_A == port) { if (conn->whowserver==endpointA) return 1; else return 0; } if (conn->IP_B.byte1 == ip->byte1 && conn->IP_B.byte2 == ip->byte2 && conn->IP_B.byte3 == ip->byte3 && conn->IP_B.byte4 == ip->byte4 && conn->port_B == port) { if (conn->whowserver==endpointB) return 1; else return 0; } return -1; // Not server and not user? } void set_as_server (struct msn_connection *conn, ip_address *ip, int port) { if (conn==NULL) return; if (conn->IP_A.byte1 == ip->byte1 && conn->IP_A.byte2 == ip->byte2 && conn->IP_A.byte3 == ip->byte3 && conn->IP_A.byte4 == ip->byte4 && conn->port_A == port) { if (conn->whowserver==endpointB) { log_debug (0, "Warning: In this connection, the server was previously misidentified"); } conn->whowserver=endpointA; } else { if (conn->whowserver==endpointA) { log_debug (0, "Warning: In this connection, the server was previously misidentified"); } conn->whowserver=endpointB; } } int remove_msn_connection (struct msn_connection *conn) { if (conn) { log_debug (5, "Removing connection from linked list"); clear_msn_connection (conn); if (conn->previous!=NULL) conn->previous->next=conn->next; if (conn->next!=NULL) conn->next->previous=conn->previous; if (msn_conns_first == conn) msn_conns_first = conn->next; if (msn_conns_last == conn) msn_conns_last = conn->previous; free (conn); } return 0; } struct msn_connection *get_or_create_msn_connection (ip_address *source_ip, int source_port, ip_address *target_ip, int target_port, enum e_msn_conn_create create) { struct msn_connection * ipa = msn_conns_first; log_debug (5,"get_or_create_msn_connection: %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d", source_ip->byte1,source_ip->byte2,source_ip->byte3,source_ip->byte4,source_port, target_ip->byte1,target_ip->byte2,target_ip->byte3,target_ip->byte4,target_port); int i=0; while (ipa) { log_debug (6,"%d - IPA: %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d",i, ipa->IP_A.byte1,ipa->IP_A.byte2,ipa->IP_A.byte3,ipa->IP_A.byte4,ipa->port_A, ipa->IP_B.byte1,ipa->IP_B.byte2,ipa->IP_B.byte3,ipa->IP_B.byte4,ipa->port_B); if ((ipa->IP_A.byte1 == source_ip->byte1 && ipa->IP_A.byte2 == source_ip->byte2 && ipa->IP_A.byte3 == source_ip->byte3 && ipa->IP_A.byte4 == source_ip->byte4 && ipa->port_A == source_port && ipa->IP_B.byte1 == target_ip->byte1 && ipa->IP_B.byte2 == target_ip->byte2 && ipa->IP_B.byte3 == target_ip->byte3 && ipa->IP_B.byte4 == target_ip->byte4 && ipa->port_B == target_port) || (ipa->IP_A.byte1 == target_ip->byte1 && ipa->IP_A.byte2 == target_ip->byte2 && ipa->IP_A.byte3 == target_ip->byte3 && ipa->IP_A.byte4 == target_ip->byte4 && ipa->port_A == target_port && ipa->IP_B.byte1 == source_ip->byte1 && ipa->IP_B.byte2 == source_ip->byte2 && ipa->IP_B.byte3 == source_ip->byte3 && ipa->IP_B.byte4 == source_ip->byte4 && ipa->port_B == source_port)) { log_debug (6, "Match"); if (create==create_replace) clear_msn_connection(ipa); log_debug (5, "Connection requested found"); return ipa; } else { log_debug (5, "No match"); } i++; ipa=ipa->next; } if (create==create_yes) { struct msn_connection * ipa = (struct msn_connection *) malloc (sizeof (struct msn_connection)); log_debug (5, "Creating new connection, %d", i); if (ipa!=NULL) { if (msn_conns_first==NULL) msn_conns_first=ipa; memset (ipa,0,sizeof (struct msn_connection)); // All zeros is fine if (msn_conns_last != NULL) { msn_conns_last->next=ipa; ipa->previous=msn_conns_last; } msn_conns_last=ipa; memcpy (&ipa->IP_A,source_ip,sizeof (struct ip_address)); ipa->port_A=source_port; memcpy (&ipa->IP_B,target_ip,sizeof (struct ip_address)); ipa->port_B=target_port; ipa->whowserver=unknown; ipa->num_users=0; ipa->users=NULL; ipa->log_full_path=NULL; return ipa; } } return NULL; } imsniff-0.04/src/msn_handlers.cpp0000644000175000017500000005372410306042516016012 0ustar amayaamaya /* MSN Handlers --------------- These functions always take the same parameters: u_char *raw -> Start of data to analyze int length -> Length of data ip_address source -> Packet's source IP address u_short source_port -> Packet's source port ip_address target -> Packet's target IP address u_short target_port -> Packet's target port And they must always return: LINE_INCOMPLETE if the data is incomplete (most likely it continues in the next packet) OUT_OF_MEMORY NOT_MSN if there was en error parsing the data which makes it likely that it's not MSN otherwise, the number of bytes processed, so the main packet process loop knows where to continue */ #include "imsniff.h" static u_char *next_line=NULL; // Functions can use this to get the next line u_char **line_tokens=NULL; // Split tokens go here #ifdef WIN32 #define strcasecmp stricmp #endif int handler_msn_ignore (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { return get_new_line_malloc (&next_line, raw, length); } int handler_msn_msg (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { int rc, nt; u_char *typingnick = NULL; log_debug (4, "Entry into handler_msn_msg"); rc= get_new_line_malloc (&next_line, raw, length); if (rc<0) return rc; nt= get_tokens (next_line, &line_tokens, 0); /* Split in all tokens */ if (nt!=4) return NOT_MSN; int pl = atoi ((char *) line_tokens[3]); log_debug (5, "Payload length: %d", pl); if (pl > length-rc) /* We don't have the whole payload yet */ return LINE_INCOMPLETE; struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_yes); int rem = pl; u_char *payload_start = raw+rc; u_char *now =payload_start; u_char **header_tk=NULL; u_char *newemailfrom=NULL, *newemailsubj=NULL, *newemailaddr=NULL; int header=1; rc=pl + rc; // Payload + MSG line int block_type = BT_UNKNOWN; while (rem>0) { int j= get_new_line_malloc (&next_line, now, rem); if (j==LINE_INCOMPLETE) // Payload doesn't have have to end in \r\n { memcpy (next_line,now,rem); j= rem; } if (j==OUT_OF_MEMORY) { rc = OUT_OF_MEMORY; break; } log_debug (5, " MSG : %d : [%s]", header, next_line); if (header) { if (j==2) header=0; else if (get_tokens(next_line,&header_tk,2)==2) { log_debug (5, "Word: %s | %s", header_tk[0], header_tk[1]); if (strcasecmp ((char *) header_tk[0],"Content-Type:")==0) { log_debug (5, "Es un content-type"); if (!strncmp ((char *) header_tk[1], "text/plain", strlen ("text/plain"))) { log_debug (5, "text/plain"); block_type = BT_TEXTPLAIN; } else if (strstr ((const char *) header_tk[1], "x-msmsgscontrol")) { block_type = BT_CONTROL; log_debug (5, "Control"); } else if (strstr ((const char *) header_tk[1], "text/x-msmsgsprofile")) { block_type = BT_PROFILE; delete_profile (conn->owner); log_debug (5, "Profile"); log_profile(conn->owner, payload_start, pl); break; } else if (strstr ((const char *) header_tk[1], "text/x-msmsgsinitialemailnotification")) { block_type = BT_INITIAL_EMAIL; log_debug (5, "Initial mail notification"); } else if (strstr ((const char *) header_tk[1], "text/x-msmsgsemailnotification")) { block_type = BT_NEW_EMAIL; log_debug (1, "New mail notification"); } else { log_debug (1, "Unknown content-type: %s", header_tk[1]); } } if (strcasecmp ((char *) header_tk[0],"TypingUser:")==0) { log_debug (5, "It's a typing user!: %s", header_tk[1]); if (conn->owner==NULL) { conn->conn_type=type_switchboard; log_debug (5, "Unknown owner"); if (strchr ((char *) line_tokens[1],'@')!=NULL) { log_debug (5, "Incoming: %s",line_tokens[1]); typingnick=line_tokens[1]; } else { log_debug (5, "Outgoing?: %s", header_tk[1]); set_owner(conn,header_tk[1]); typingnick=conn->owner; } } else { log_debug (5, "SB owner: %s", conn->owner); } } } } else { switch (block_type) { case BT_TEXTPLAIN: if (strchr ((char *) line_tokens[1],'@')!=NULL) { typingnick=line_tokens[1]; add_user_to_sb(conn,typingnick); } else typingnick=(conn->owner==NULL) ? (u_char *) "Unknown" : conn->owner; log_switchboard_event(conn, "%s: %s", typingnick, next_line); break; case BT_PROFILE: log_debug (0, "Profile with a body? Shouldn't happen"); break; case BT_INITIAL_EMAIL: if (get_tokens(next_line,&header_tk,2)==2) { if (strcmp ((char *) header_tk[0],"Inbox-Unread:")==0) log_event(conn->owner, "has %s unread emails in his/her inbox", header_tk[1]); if (strcmp ((char *) header_tk[0],"Folders-Unread:")==0) log_event(conn->owner, "has %s unread emails in other folders", header_tk[1]); } break; case BT_NEW_EMAIL: if (get_tokens(next_line,&header_tk,2)==2) { if (strcmp ((char *) header_tk[0],"From:")==0) strcpymalloc(&newemailfrom, header_tk[1]); if (strcmp ((char *) header_tk[0],"Subject:")==0) strcpymalloc(&newemailsubj, header_tk[1]); if (strcmp ((char *) header_tk[0],"From-Addr:")==0) strcpymalloc(&newemailaddr, header_tk[1]); } break; default: log_debug (5, "%d - %s:%s", block_type, line_tokens[1], line_tokens[2]); // dump_tokens(line_tokens); break; } } now=now+j; rem=rem-j; } if (block_type==BT_NEW_EMAIL) { log_event(conn->owner, "got email from [%s] (%s) about [%s]", (u_char *) newemailfrom ? (u_char *) newemailfrom : (u_char *) "Unknown", (u_char *) newemailaddr ? (u_char *) newemailaddr : (u_char *) "unknown address", (u_char *) newemailsubj ? (u_char *) newemailsubj : (u_char *) "(no subject)"); } free_array (&header_tk); free (newemailfrom); free (newemailsubj); free (newemailaddr); return rc; } /* USR When user -> server, attempt to identify When server -> user, request to authentificate or final OK Useful information here: Connection owner's ID */ int handler_msn_usr (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { int rc, nt; log_debug (4, "Entry into handler_msn_usr"); rc= get_new_line_malloc (&next_line, raw, length); if (rc<0) return rc; nt= get_tokens (next_line, &line_tokens, 0); /* Split in all tokens */ if (nt == 5) { if (strcmp ((char *) line_tokens[2], "TWN")==0) { if (strcmp ((char *) line_tokens[3],"I")==0) { log_event (line_tokens[4],"trying to log in"); struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_yes); set_owner (conn, line_tokens[4]); conn->conn_type=type_notification_server; set_as_server(conn,&destination,destination_port); } if (strcmp ((char *) line_tokens[3], "S")==0) { /* We only process this for notification purposes, but it's not really useful since there's no information we need here */ struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) { nick=conn->owner; conn->conn_type=type_notification_server; } log_event (nick, "Notification server authentificating user"); } } if (strcmp ((char *) line_tokens[2], "OK")==0) { // User successfully logged into a SB struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_yes); set_owner (conn, line_tokens[3]); conn->conn_type=type_notification_server; set_as_server(conn,&source,source_port); log_event (line_tokens[3], "entered switchboard at %d.%d.%d.%d:%d", source.byte1,source.byte2,source.byte3,source.byte4, source_port); } } else if (nt == 4) { log_event (line_tokens[2],"attempting to enter switchboard at %d.%d.%d.%d:%d", destination.byte1,destination.byte2,destination.byte3,destination.byte4, destination_port); } else if (nt == 6 || nt == 7) /* No idea why sometimes it's 7 */ { if (strcmp ((char *) line_tokens[2], "OK")==0) { log_event (line_tokens[3],"successfully authentificated"); struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_yes); set_owner (conn, line_tokens[3]); conn->conn_type=type_notification_server; set_as_server(conn,&source,source_port); } } else { log_debug (0, "Unable to parse USR correcty"); log_debug (0, "Line read: %s", next_line); dump_tokens (line_tokens); return NOT_MSN; } return rc; } /* CHG - change status information */ int handler_msn_chg (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { int rc, nt; log_debug (4, "Entry into handler_msn_chg"); rc= get_new_line_malloc (&next_line, raw, length); if (rc<0) return rc; nt= get_tokens (next_line, &line_tokens, 0); /* Split in all tokens */ if (nt >= 4) { struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) nick=conn->owner; log_event (nick, "Changed status to %s", line_tokens[2]); } else { log_debug (0, "Unable to parse CHG correcty"); log_debug (0, "Line read: %s", next_line); dump_tokens (line_tokens); return NOT_MSN; } return rc; } /* ANS - join a switchboard we have been invited to */ int handler_msn_ans (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { int rc, nt; log_debug (4, "Entry into handler_msn_ans"); rc= get_new_line_malloc (&next_line, raw, length); if (rc<0) return rc; nt= get_tokens (next_line, &line_tokens, 0); /* Split in all tokens */ if (nt == 5) { struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_yes); set_owner(conn, line_tokens[2]); log_event (line_tokens[2], "is entering the SB at %d.%d.%d.%d:%d", destination.byte1,destination.byte2,destination.byte3, destination.byte4,destination_port); set_as_server(conn,&destination,destination_port); } else if (nt == 3 && strcmp ((char *) line_tokens[2], "OK")==0) { struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) nick=conn->owner; log_event (nick, "successfully logged into SB at %d.%d.%d.%d:%d", source.byte1,source.byte2,source.byte3, source.byte4,source_port); set_as_server(conn,&source,source_port); } else { log_debug (0, "Unable to parse ANS correcty"); log_debug (0, "Line read: %s", next_line); dump_tokens (line_tokens); return NOT_MSN; } return rc; } /* IRO - initial list of people in a switchboard */ int handler_msn_iro (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { int rc, nt; log_debug (4, "Entry into handler_msn_iro"); rc= get_new_line_malloc (&next_line, raw, length); if (rc<0) return rc; nt= get_tokens (next_line, &line_tokens, 0); /* Split in all tokens */ if (nt == 6) { struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) nick=conn->owner; urldecode (line_tokens[5]); log_event (nick, "%s (%s) is in the SB at %d.%d.%d.%d:%d", line_tokens[4], line_tokens[5], source.byte1,source.byte2,source.byte3, source.byte4,source_port); set_as_server(conn,&source,source_port); add_user_to_sb (conn,line_tokens[4]); log_switchboard_event(conn, "%s (%s) is in the conversation", line_tokens[4], line_tokens[5]); } else { log_debug (0, "Unable to parse IRO correcty"); log_debug (0, "Line read: %s", next_line); dump_tokens (line_tokens); return NOT_MSN; } return rc; } /* JOI - new participants entering a switchboard */ int handler_msn_joi (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { int rc, nt; log_debug (4, "Entry into handler_msn_joi"); rc= get_new_line_malloc (&next_line, raw, length); if (rc<0) return rc; log_debug (5, "Line read: %s", next_line); nt= get_tokens (next_line, &line_tokens, 0); /* Split in all tokens */ if (nt == 3) { struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) nick=conn->owner; set_as_server(conn,&source,source_port); add_user_to_sb (conn,line_tokens[1]); urldecode (line_tokens[2]); log_switchboard_event(conn, "%s (%s) joined the conversation", line_tokens[1], line_tokens[2]); } else { log_debug (0, "Unable to parse JOI correcty"); dump_tokens (line_tokens); return NOT_MSN; } return rc; } /* OUT - user leaving the switchboard */ int handler_msn_out (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { int rc, nt; log_debug (4, "Entry into handler_msn_out"); rc= get_new_line_malloc (&next_line, raw, length); if (rc<0) return rc; log_debug (5, "Line read: %s", next_line); nt= get_tokens (next_line, &line_tokens, 0); /* Split in all tokens */ if (nt==1 || nt==2) // 2 because trillian add's a TrID { struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) nick=conn->owner; log_event (nick, "is leaving the SB at %d.%d.%d.%d:%d", destination.byte1,destination.byte2,destination.byte3, destination.byte4,destination_port); log_switchboard_event(conn, "%s left the conversation", nick); log_switchboard_end (conn); remove_msn_connection (conn); return CONN_DESTROYED; } else { log_debug (0, "Unable to parse OUT correcty"); dump_tokens (line_tokens); return NOT_MSN; } return rc; } /* BYE - user has the switchboard or it has been closed for idleness */ int handler_msn_bye (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { int rc, nt; log_debug (4, "Entry into handler_msn_bye"); rc= get_new_line_malloc (&next_line, raw, length); if (rc<0) return rc; log_debug (5, "Line read: %s", next_line); nt= get_tokens (next_line, &line_tokens, 0); /* Split in all tokens */ if (nt==2) { struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) { nick=conn->owner; set_as_server(conn,&source,source_port); } log_switchboard_event (conn, "%s left the conversation", line_tokens[1]); // TODO: Remove user from the SB structure } else if (nt==3) { struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) nick=conn->owner; log_switchboard_event (conn, "The switchboard is being closed for idleness"); log_switchboard_end (conn); remove_msn_connection (conn); return CONN_DESTROYED; } else { log_debug (0, "Unable to parse BYE correcty"); dump_tokens (line_tokens); return NOT_MSN; } return rc; } /* FLN - user disconnecting or hidding */ int handler_msn_fln (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { int rc, nt; log_debug (4, "Entry into handler_msn_fln"); rc= get_new_line_malloc (&next_line, raw, length); if (rc<0) return rc; nt= get_tokens (next_line, &line_tokens, 0); /* Split in all tokens */ if (nt==2) { struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) nick=conn->owner; log_event (nick, "%s disconnected or hid", line_tokens[1], destination.byte1,destination.byte2,destination.byte3, destination.byte4,destination_port); } else { log_debug (0, "Unable to parse FLN correcty"); log_debug (0, "Line read: %s", next_line); dump_tokens (line_tokens); return NOT_MSN; } return rc; } /* NLN - status change */ int handler_msn_nln (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { int rc, nt; log_debug (4, "Entry into handler_msn_nln"); rc= get_new_line_malloc (&next_line, raw, length); if (rc<0) return rc; nt= get_tokens (next_line, &line_tokens, 0); /* Split in all tokens */ if (nt>=5) { struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) nick=conn->owner; urldecode (line_tokens[3]); log_event (nick, "%s [%s] changed his/her status to %s", line_tokens[2],line_tokens[3],line_tokens[1]); } else { log_debug (0, "Unable to parse NLN correcty"); log_debug (0, "Line read: %s", next_line); dump_tokens (line_tokens); return NOT_MSN; } return rc; } /* ILN - initial user user */ int handler_msn_iln (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { int rc, nt; log_debug (4, "Entry into handler_msn_iln"); rc= get_new_line_malloc (&next_line, raw, length); if (rc<0) return rc; nt= get_tokens (next_line, &line_tokens, 0); /* Split in all tokens */ if (nt>=6) { struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) nick=conn->owner; urldecode (line_tokens[4]); log_event (nick, "Contact status, nick [%s], status [%s], display name [%s]", line_tokens[3], line_tokens[2],line_tokens[4]); } else { log_debug (0, "Unable to parse ILN correcty"); log_debug (0, "Line read: %s", next_line); dump_tokens (line_tokens); return NOT_MSN; } return rc; } /* SYN - list sync */ int handler_msn_syn (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { int rc, nt; log_debug (5, "Entry into handler_msn_syn"); rc= get_new_line_malloc (&next_line, raw, length); if (rc<0) return rc; nt= get_tokens (next_line, &line_tokens, 0); /* Split in all tokens */ struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) nick=conn->owner; switch (nt) { case 3: case 4: /* Trillian sends something else */ if (conn!=NULL) { switch (is_from_server(conn,&source,source_port)) { case 1: log_event (nick, "has an updated contact list"); break; case 0: log_event (nick, "requested a list sync"); break; default: log_debug (0, "bug in handler_msn_syn"); break; } } else { log_debug (0, "Saw a SYN, can't tell origin"); } break; case 6: log_event (nick, "has an outdated contact list, a new one should follow"); log_event (nick, "# of contacts = %s, # of groups= %s", line_tokens[4], line_tokens[5]); delete_contact_list (nick); break; default: log_debug (0, "Unable to parse SYN correcty"); log_debug (0, "Line read: %s", next_line); dump_tokens (line_tokens); return NOT_MSN; } return rc; } /* PRP - list of contacts */ int handler_msn_prp (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { int rc, nt; log_debug (4, "Entry into handler_msn_prp"); rc= get_new_line_malloc (&next_line, raw, length); if (rc<0) return rc; nt= get_tokens (next_line, &line_tokens, 0); /* Split in all tokens */ if (nt==3) { if (strcmp ((char *) line_tokens[1],"MFN")==0) { struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) nick=conn->owner; urldecode (line_tokens[2]); log_event(nick, "Changed display name to [%s]", line_tokens[2]); } } else if (nt==4) { if (strcmp ((char *) line_tokens[2],"MFN")==0) { struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) nick=conn->owner; urldecode (line_tokens[3]); log_event(nick, "Changed display name to [%s]", line_tokens[3]); } } else { log_debug (0, "Unable to parse PRP correcty"); log_debug (0, "Line read: %s", next_line); dump_tokens (line_tokens); return NOT_MSN; } return rc; } /* LST - list of contacts */ int handler_msn_lst (u_char *raw, int length, ip_address source, u_short source_port, ip_address destination, u_short destination_port) { int rc, nt; log_debug (4, "Entry into handler_msn_lst"); rc= get_new_line_malloc (&next_line, raw, length); if (rc<0) return rc; nt= get_tokens (next_line, &line_tokens, 0); /* Split in all tokens */ if (nt>=3) { struct msn_connection *conn = get_or_create_msn_connection (&source,source_port,&destination,destination_port, create_no); u_char *nick=NULL; if (conn!=NULL) nick=conn->owner; if (strlen ((char *) line_tokens[1])<3) { log_debug (0, "Unable to parse LST correctly"); return NOT_MSN; } log_contact(nick,line_tokens[1]+2); } else { log_debug (0, "Unable to parse LST correcty"); log_debug (0, "Line read: %s", next_line); dump_tokens (line_tokens); return NOT_MSN; } return rc; } imsniff-0.04/docs/0000755000175000017500000000000010306044417012761 5ustar amayaamayaimsniff-0.04/docs/TODO0000644000175000017500000000116110244416502013447 0ustar amayaamayaTODO ---- Not sure I will do all (or any) of this list, as the program is working fairly well for my needs. - Delete old entries in the conversation linked list, so memory usage doesn't grow forever (there are times where a switchboard can be closed with no warning, so we need to have our own timeout). - Improve documentation. - Some parts of the protocol are being ignored because I don't absolutely need them, however keeping track of XFR's (and possibly others) would be useful. - Fix bugs. - Rewrite capture loop. - Deal with packets arriving out of order. - Capture file transfers. imsniff-0.04/docs/README0000644000175000017500000000764210306044345013652 0ustar amayaamayaIMsnif, by Carlos Fernandez, carlos.fernandez.sanz@gmail.com ------------------------------------------------------------ DESCRIPTION ----------- IMsnif is a simple program to log Instant Message activity on the network. It uses libpcap to capture packets and analyzes them, logging conversation, contact lists, etc. RUNNING (LINUX) --------------- You can configure it via command line parameters or via a file called imsniff.conf either in the current directory or in /etc. If for some reason you rename the IMsniff execute, you need to rename the config file as well. A sample imsniff.conf.sample file is included. The only required parameter is the interface name to listen to. This can be any interface that libpcap supports, such as imsnif eth0 If you use a non-ethernet interface, please read the data offset section below. For users connecting after IMsnif starts running you can get pretty good results, including complete contact lists and events (display name change, for example). For users already connected you will be able to get the conversations, which is not bad, but you will miss other stuff. OTHER PARAMETERS ---------------- command line config file Description ------------------------------------------------------------ -cd chatdir Directory where conversations will be stored. -dd debugdir Directory where logs will be stored. These logs contain debug information as well as certain MSN events. -v* verbose Debug level. The more v's (or higher the number in the config file), the more info that is dumped. For regular usage, use 1 or 2. More than that will dump a lot of useless stuff. -p promisc Put the device in promiscuous mode. -d daemonize Become a daemon. -offset data_offset See below. -help N/A Display help. With no prefix interface Interface to use. DATA OFFSET ----------- The offset (in this context) is the length of the datalink header when capturing packets. This is an important number because we need to skip this header when processing packets. For ethernet, this number is 14, and IMsniff knows about it. If you use a different interface, you might have to help IMsniff by providing the number yourself. Por example: imniff ppp0 -offset 4 How do you figure out this number? The easiest way is just try different numbers (and keep your own MSN connection busy (type something) until IMsniff starts dumping conversations. The number is never high anyway. A few tries should always do. If you have to use this, once it's working please drop me a note telling me what interface type IMsnif reported, and the offset you used. I will add this to the code so next versions don't have to be tuned manually. RUNNING (WINDOWS) ----------------- Everything is like linux, except: - Instead of a device NAME you need to enter a device NUMBER. This is because device names in Windows are pretty much unreadable. In order to get the number, run the program with -list just once, i.e. imsniff -list You will get a list of available devices, with a number, a name (you will see why we don't want to use this), and a description. Just choose the correct number, and use it, like this: imsniff 2 - There is no deaemon mode. If someone bothers to make imsniff a service, please send me the patches. In the meantime, it's just console mode. STATUS ------ Beta version. Seems to work decently. SUPPORTED PROTOCOLS ------------------- For now, only MSN. Others could follow. REQUIREMENTS ------------ libpcab or winpcap. imsniff-0.04/docs/imsniff.conf.sample0000644000175000017500000000014110244416556016547 0ustar amayaamayadaemonize = 0 promisc = 0 verbose = 2 chatdir = /tmp/chats debugdir = /tmp/debug interface eth0 imsniff-0.04/docs/AUTHORS0000644000175000017500000000043110306043430014021 0ustar amayaamayaAUTHOR ------ Carlos Fernandez, carlos.fernandez.sanz@gmail.com CREDITS ------- The PCAP project, obviously the heart inside my small program. The excellent WinPCAP port. They also have some good tutorials. hypothetic.com, for their work reverse engineering the MSN protocol. imsniff-0.04/docs/LICENSE0000644000175000017500000000001210243075013013753 0ustar amayaamayaGPL v2. imsniff-0.04/docs/ChangeLog0000644000175000017500000000060510306044417014534 0ustar amayaamaya0.04 ---- - Preliminary port to Windows. It seems to work, but it's not very tested. Please report bugs. 0.03 ---- - Allows to be configured via configuration file. - Added support to non-ethernet interfaces. - Added profile information capture. - Added support for initial email notification. - Added support for new email notification. 0.02 ---- First public release