/*
 * Copyright (c) 2002, Hai Huang
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 *   o Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer
 *   o Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   o Neither the name of the Beijing LyangTech Corp Ltd. nor the names of
 *     its contributors may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <pcap.h>

#define MAX_PRINT 80
#define MAX_LINE 16

#define MAX_DEVICE 16
#define MAX_DEV_NAME_LEN 64

#define MAX_SESSION 64
#define MAX_RING_BUFFER  16384

// an ether frame
struct _ether_frame {
  u_char e_dest[6];
  u_char e_sour[6];
  u_char e_type[2];
  u_char ip_ver;
  u_char ip_type;
  u_char ip_len[2];
  u_char ip_id[2];
  u_char ip_flag_off[2];
  u_char ip_ttl;
  u_char ip_proto;
  u_char ip_chk[2];
  u_char ip_sour[4];
  u_char ip_dest[4];
  u_char tcp_sour_port[2];
  u_char tcp_dest_port[2];
  u_char tcp_seq[4];
  u_char tcp_ack[4];
  u_char tcp_len_rev_flag[2];
  u_char tcp_window[2];
  u_char tcp_chk[2];
  u_char tcp_urgent[2];
  u_char tcp_data[1];
};

// a MSN session
struct _msn_sess {
  u_char  ip_local[4];
  u_char  ip_remote[4];
  u_short port_remote;
  u_short port_local;
  char    ring_buf[MAX_RING_BUFFER];
  int     start, end;
  char    login[64];
};

#ifdef WIN2000
unsigned short _all_device[MAX_DEVICE][MAX_DEV_NAME_LEN];
#else
unsigned char _all_device[MAX_DEVICE][MAX_DEV_NAME_LEN];
#endif

struct _msn_sess* _all_sess[MAX_SESSION];
int _n_sess;

void dispatcher_handler(u_char *, 
  const struct pcap_pkthdr *, const u_char *);
void usage();

/* look up all available devices, return device name in _all_device, which
   is a global variable. return the
   number of device found. only work on win98 and later system. note that
   in win2000 and later system the device name is returned in wide-char
   form, which is equavilent to unsigned short */
int LookUpDev();
/* print all device name storen in global variable _all_device, ndev is the
   number of device */
void PrintDev(int ndev);

/* print out a ether frame */
void PrintFrame(struct _ether_frame *pfrm);

/* process msn message */
void proc_msn_msg(u_char local_ip[4], u_char remote_ip[4], u_short local_port,
          u_short remote_port, char *pdata, char dir, int data_len);
void debug_msg(u_char local_ip[4], u_char remote_ip[4], u_short local_port,
         u_short remote_port, char *pdata, char dir, int data_len);
void display_xml_msg(char *pc, char *pbuf);

int main(int argc, char **argv)
{
  pcap_t *fp;
  char error[PCAP_ERRBUF_SIZE];
  int n;
  int ndev = 0;

  _n_sess = 0;

  if ( (n=LookUpDev(_all_device)) == -1 ) {
    printf("Cannot find any available device\n");
    exit(0);
  }

  if ( argc > 1 ) ndev = atoi(argv[1]);
  else {
    PrintDev(n);
    exit(0);
  }

  // select a device and open live on it, in this machine _all_device[1] is
  // the one which we can capture packet
  if ( (fp= pcap_open_live((char*)_all_device[ndev], 1600, 1, 20, error) ) == NULL)
  {
    fprintf(stderr,"\nError opening adapter\n");
    return 0;
  }

  while(1)
    pcap_dispatch(fp, 100, dispatcher_handler, NULL);

  return 0;
}

int LookUpDev()
{
  char ebuf[PCAP_ERRBUF_SIZE];
  int i,j,n;

#ifdef WIN2000
  unsigned short *device;
#else
  unsigned char *device;
#endif
  
#ifdef WIN2000
  device=(unsigned short*)pcap_lookupdev(ebuf);
#else
  device=(unsigned char*)pcap_lookupdev(ebuf);
#endif

  if (device == NULL) return -1;

  i=0;
  n=0;
  j=0;
  while(device[i]!=0||device[i+1]!=0)
  {
    if(device[i]==0) {
      _all_device[n][j]=device[i];
      n++;
      j=0;
    }
    else {
      _all_device[n][j++]=device[i];
    }
    if(device[i+1]==0) {
      _all_device[n][j]=device[i+1];
      n++;
      j=0;
    }
    else {
      _all_device[n][j++]=device[i+1];
    }
    i+=2;
  }
  _all_device[n][j]=0;
  n++;

  return n;
}

void PrintDev(int ndev)
{
  int i,j;

  for ( i=0;i<ndev;i++) {
    j=0;
    while (_all_device[i][j]!=0) {
      printf("%c",_all_device[i][j++]);
    }
    printf("\n");
  }

}

void PrintFrame(struct _ether_frame *pfrm)
{
  printf("%d.%d.%d.%d:%d -> %d.%d.%d.%d:%d\n",
      pfrm->ip_sour[0], pfrm->ip_sour[1], pfrm->ip_sour[2], 
      pfrm->ip_sour[3], 
      pfrm->tcp_sour_port[0]*256 + pfrm->tcp_sour_port[1],
      pfrm->ip_dest[0], pfrm->ip_dest[1], pfrm->ip_dest[2], 
      pfrm->ip_dest[3], 
      pfrm->tcp_dest_port[0]*256 + pfrm->tcp_dest_port[1] );

  printf("%s\n", pfrm->tcp_data);
  printf("\n");
}

void dispatcher_handler(u_char *temp1, 
  const struct pcap_pkthdr *header, const u_char *pkt_data)
{
  u_int i=0;
  struct _ether_frame *pfrm;
  u_char msn_port[2]={0x07, 0x47};
  u_short sour_port, dest_port;
  int len;

  pfrm = (struct _ether_frame*) pkt_data;
  if ( pfrm->e_type[0] == 0x08 && pfrm->ip_proto == 0x06 ) {
    // a tcp packet
    if ( memcmp(pfrm->tcp_sour_port, msn_port, 2) == 0 ) {
      // a message from msn server to client
      sour_port = pfrm->tcp_sour_port[0] * 256 + pfrm->tcp_sour_port[1];
      dest_port = pfrm->tcp_dest_port[0] * 256 + pfrm->tcp_dest_port[1];
      len = pfrm->ip_len[0] * 256 + pfrm->ip_len[1];
      len -= 30;
      proc_msn_msg(pfrm->ip_dest, pfrm->ip_sour, dest_port,
             sour_port, pfrm->tcp_data, 'D', len);
    }
    else if ( memcmp(pfrm->tcp_dest_port, msn_port, 2) == 0 ) {
      // a message from client to msn server
      sour_port = pfrm->tcp_sour_port[0] * 256 + pfrm->tcp_sour_port[1];
      dest_port = pfrm->tcp_dest_port[0] * 256 + pfrm->tcp_dest_port[1];
      len = pfrm->ip_len[0] * 256 + pfrm->ip_len[1];
      len -= 30;
      proc_msn_msg(pfrm->ip_sour, pfrm->ip_dest, sour_port,
             dest_port, pfrm->tcp_data, 'U', len);

    }
  }
}

void proc_msn_msg(u_char local_ip[4], u_char remote_ip[4], u_short local_port,
          u_short remote_port, char *pdata, char dir, int data_len)
{
  // msn has many commands, in this version, we only support the following
  // commands:
  //
  // command     direction                  description
  // BYE         D            tell client another user close the session
  // CAL         U            initial a session
  // CHG         U            change the user's status
  // FLN         D            tell client another user logoff
  // ILN         D            tell client another user login
  // JOI         D            tell client another user join a session
  // MSG         U/D          send message
  // NLN         D            tell client another user change status
  // OUT         U/D          close session
  // USR         D            login
  //
  // for more information, please refer to the MSN protocol

  char buf[1600];
  char msgbuf[1600];
  char *pc, *pstart;
  char substate[128];
  char userhandler[128];
  char friendlyname[128];

  char filename[256];
  FILE *pf;

  sprintf(filename, "log/%d-%d-%d-%d_%d", local_ip[0], local_ip[1], local_ip[2], 
        local_ip[3], local_port);

  strncpy(buf, pdata, data_len);
  buf[data_len] = 0;

  if (strncmp(pdata, "BYE", 3) == 0) {
    // BYE         D            tell client another user close the session
    if ( dir == 'D' ) {
      debug_msg(local_ip, remote_ip, local_port, remote_port, pdata, dir, data_len);
      pc = strchr(((char*)buf)+4, '\r');
      if ( pc != NULL ) {
        *pc = 0;
      }
      printf("%s leave the session\n", buf+4);
      if ( (pf = fopen(filename, "a") ) != NULL ) {
        fprintf(pf, "%s leave the session\n", buf+4);
        fclose(pf);
      }     
    }
  }
  else if (strncmp(pdata, "CAL", 3) == 0) {
    // CAL         U            initial a session
    if ( dir == 'U' ) {
      debug_msg(local_ip, remote_ip, local_port, remote_port, pdata, dir, data_len);
      pc = strchr(buf+4, ' ');
      pc++;
      pstart = pc;
      pc = strchr(pc, '\r');
      if ( pc != NULL ) {
        *pc = 0;
      }
      printf("invited %s into the session\n", pstart);
      if ( (pf = fopen(filename, "a") ) != NULL ) {
        fprintf(pf,"invited %s into the session\n", pstart);
        fclose(pf);
      }     
    }
  }
  else if (strncmp(pdata, "CHG", 3) == 0) {
    // CHG         U            change the user's status
    if ( dir == 'U' ) {
      debug_msg(local_ip, remote_ip, local_port, remote_port, pdata, dir, data_len);
      pc = strchr(buf+4, ' ');
      pc++;
      pstart = pc;
      pc = strchr(pc, '\r');
      if ( pc != NULL ) {
        *pc = 0;
      }
      printf("change status to %s\n", pstart);
      if ( (pf = fopen(filename, "a") ) != NULL ) {
        fprintf(pf, "change status to %s\n", pstart);
        fclose(pf);
      }     
    }
  }
  else if (strncmp(pdata, "FLN", 3) == 0) {
    // FLN         D            tell client another user logoff
    if ( dir == 'D' ) {
      debug_msg(local_ip, remote_ip, local_port, remote_port, pdata, dir, data_len);
      pc = strchr(buf+4, '\r');
      if ( pc != NULL ) {
        *pc = 0;
      }
      printf("%s logoff\n", buf+4);
      if ( (pf = fopen(filename, "a") ) != NULL ) {
        fprintf(pf, "%s logoff\n", buf+4);
        fclose(pf);
      }     
    }
  }
  else if (strncmp(pdata, "ILN", 3) == 0) {
    // ILN         D            tell client another user login
    if ( dir == 'D' ) {
      debug_msg(local_ip, remote_ip, local_port, remote_port, pdata, dir, data_len);
      pc = strchr(buf+4, ' ');
      pc++;
      pstart = pc;
      pc = strchr(pstart, ' ');
      *pc = 0;
      strcpy(substate, pstart);
      pc++;
      pstart = pc;
      pc = strchr(pstart, ' ');
      *pc = 0;
      strcpy(userhandler, pstart);
      pc++;
      pstart = pc;
      pc = strchr(pstart, '\r');
      if ( pc!=NULL ) *pc = 0;
      strcpy(friendlyname, pstart);
      printf("%s status:%s friendlyname:%s login\n", userhandler, substate,
           friendlyname);
      if ( (pf = fopen(filename, "a") ) != NULL ) {
        fprintf(pf, "%s status:%s friendlyname:%s login\n", userhandler, substate,
                friendlyname);
        fclose(pf);
      }     
    }
  }
  else if (strncmp(pdata, "JOI", 3) == 0) {
    // JOI         D            tell client another user join a session
    if ( dir == 'D' ) {
      debug_msg(local_ip, remote_ip, local_port, remote_port, pdata, dir, data_len);
      pc = strchr(buf+4, ' ');
      *pc = 0;
      strcpy(userhandler, buf+4);
      pc++;
      pstart = pc;
      pc = strchr(pstart, '\r');
      if ( pc != NULL ) *pc = 0;
      strcpy(friendlyname, pstart);
      printf("%s firendlyname:%s join session\n", userhandler, friendlyname);
      if ( (pf = fopen(filename, "a") ) != NULL ) {
        fprintf(pf, "%s firendlyname:%s join session\n", userhandler, friendlyname);
        fclose(pf);
      }     
    }
  }
  else if (strncmp(pdata, "MSG", 3) == 0) {
    // MSG         U/D          send message
    if (strstr(buf, "Content-Type: text/plain") != NULL) {
      debug_msg(local_ip, remote_ip, local_port, remote_port, pdata, dir, data_len);
      if ( dir == 'U' ) {
        // upload message
        pc = strchr(buf+4,' ');
        pc++;
        printf("send out the following message\n", pc);
        display_xml_msg(pc, msgbuf);
        if ( (pf = fopen(filename, "a") ) != NULL ) {
          fprintf(pf, "send out the following message\n", pc);
          fprintf(pf, "%s\n", msgbuf);
          fclose(pf);
        }     
      }
      else {
        // download message
        pc = strchr(buf+4, ' ');
        *pc = 0;
        strcpy(userhandler, buf+4);
        pc++;
        pstart = pc;
        pc = strchr(pstart, ' ');
        if ( pc != NULL ) *pc = 0;
        strcpy(friendlyname, pstart);
        pc++;
        printf("%s friendlyname:%s send the following message\n", userhandler,
             friendlyname);
        display_xml_msg(pc, msgbuf);
        if ( (pf = fopen(filename, "a") ) != NULL ) {
          fprintf(pf, "%s friendlyname:%s send the following message\n", userhandler,
              friendlyname);
          fprintf(pf, "%s\n", msgbuf);
          fclose(pf);
        }     
      }
    }
  }
  else if (strncmp(pdata, "NLN", 3) == 0) {
    // NLN         D            tell client another user change status
    if ( dir == 'D' ) {
      debug_msg(local_ip, remote_ip, local_port, remote_port, pdata, dir, data_len);
      pstart = buf + 4;
      pc = strchr(pstart, ' ');
      *pc = 0;
      strcpy(substate, pstart);
      pc++;
      pstart = pc;
      pc = strchr(pstart, ' ');
      *pc = 0;
      strcpy(userhandler, pstart);
      pc++;
      pstart = pc;
      pc = strchr(pstart, '\r');
      if ( pc!=NULL ) *pc = 0;
      strcpy(friendlyname, pstart);
      printf("%s friendlyname:%s change to status %s\n", userhandler,
           friendlyname, substate);
      if ( (pf = fopen(filename, "a") ) != NULL ) {
        fprintf(pf, "%s friendlyname:%s change to status %s\n", userhandler,
            friendlyname, substate);
        fclose(pf);
      }     
    }
  }
  else if (strncmp(pdata, "OUT", 3) == 0) {
    // OUT         U          close session
    if ( dir == 'U' ) {
      debug_msg(local_ip, remote_ip, local_port, remote_port, pdata, dir, data_len);
      printf("logoff\n");
      if ( (pf = fopen(filename, "a") ) != NULL ) {
        fprintf(pf, "logoff\n*****END*****\n\n");
        fclose(pf);
      }     
    }
  }
  else if (strncmp(pdata, "USR", 3) == 0) {
    // USR         D            login
    if ( dir == 'D' ) {
      if ( (pstart = strstr(buf," OK ")) != NULL ) {
        debug_msg(local_ip, remote_ip, local_port, remote_port, pdata, dir, data_len);
        pstart+=4;
        pc = strchr(pstart, ' ');
        *pc = 0;
        strcpy(userhandler, pstart);
        pc++;
        pstart = pc;
        pc = strchr(pstart, ' ');
        if ( pc!=NULL ) *pc = 0;
        pc = strchr(pstart, '\r');
        if ( pc!=NULL ) *pc = 0;
        strcpy(friendlyname, pstart);
        printf("local %s friendlyname %s login\n", userhandler, friendlyname);
        if ( (pf = fopen(filename, "a") ) != NULL ) {
          fprintf(pf, "*****START*****\n");
          fprintf(pf, "remote server:%d.%d.%d.%d:%d\n", remote_ip[0],
                remote_ip[1], remote_ip[2], remote_ip[3], remote_port);
          fprintf(pf, "local %s friendlyname %s login\n", 
              userhandler, friendlyname);
          fclose(pf);
        }     
      }
    }
  }
}

void debug_msg(u_char local_ip[4], u_char remote_ip[4], u_short local_port,
         u_short remote_port, char *pdata, char dir, int data_len)
{
  printf("local:%d.%d.%d.%d:%d remote:%d.%d.%d.%d:%d DIR:%c CMD:%c%c%c LEN:%d\n",
       local_ip[0], local_ip[1], local_ip[2], local_ip[3], local_port,
       remote_ip[0], remote_ip[1], remote_ip[2], remote_ip[3], remote_port,
       dir, pdata[0], pdata[1], pdata[2], data_len);
}

void display_xml_msg(char *pc, char *pbuf)
{
  char *pstart, *pend;
  int len;

  if ( *pc == 'U' || *pc == 'N' || *pc == 'A' ) {
    pstart=strchr(pc, ' ');
    pstart++;
  }
  else {
    pstart = pc;
  }
  pend = strchr(pstart, '\r');
  *pend = 0;
  len = atoi(pstart);
  pstart=pend+2;
  *(pstart+len)=0;
  printf("%s\n", pstart);
  sprintf(pbuf, "%s\n", pstart);
}

void usage()
{ 
  printf("hear [ndev]\n");
  printf("\tif no ndev specified, then it display all available devices.\n");

  exit(0);
}
