/*

Copyright 1993, 1994, Cornell University

Cornell hereby grants permission to use, copy, modify, and distribute this program for any purpose 
and without fee, provided that these copyright and permission notices appear on all copies and 
supporting documentation, the name of Cornell not be used in advertising or publicity pertaining 
to distribution of the program without specific prior permission, notice be given in supporting 
documentation that copying and distribution is by permission of Cornell.  CORNELL MAKES NO 
REPRESENTATIONS OR WARRANTEES, EXPRESS OR IMPLIED.  By way of example, but not limitation, 
CORNELL MAKES NO REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR 
PURPOSE OR THAT THE USE OF THIS SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY PATENTS, COPYRIGHTS, 
TRADEMARKS, OR OTHER RIGHTS.  Cornell shall not be held liable for any liability with respect to 
any claim by the user or any other party arising from use of the program.

This material is partially based on work sponsored by the National Science Foundation under Cooperative 
Agreement No. NCR-9318337.  The government has certain rights in this material.

*/

#define __MAIN__

#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>

#ifndef LINUX
#include <sys/socketvar.h>
#endif

#include <sys/time.h>
#include <netinet/in.h>

#include "reflect.h"
#include "refmon.h"
#include "globals.h"

main(argc, argv)
    int                argc;
    char               *argv[];
{

    unsigned char       *msg,*s,*cptr,*cptr1,version;
    VideoPacketHeader   *vidptr, *vtmp; 
    RefConPkt           pkt;
    RefConPkt           *conpkt;
    client              *cltptr,*ctmp;
    vat_client        *mcltptr,*mtmp;
    struct sockaddr_in  csock,clnt_addr;
    struct in_addr      in;
    int                 msglen,type;
    short               datatype; 
    struct timeval      tp;
    struct timezone     tzp;
    char                *tmp;


    argc--; argv++;

    tracefile = LOGFILE;
    maxallowed = DEFMAXCLIENT;

    cap = ((1024 * 80) /8);
    cap += (cap * .2);

    if (argc > 0)
       load_config(*argv);
    else
       load_config(NULL);

    if (strlen(ci_buf) == 0)
       strcpy(ci_buf,CFD);
    
    if (strlen(mp_buf) == 0)
       strcpy(mp_buf,MAX_CL);

    if (strlen(dy_buf) == 0)
       strcpy(dy_buf,DENY_ACCESS);

    if (strlen(cap_buf) == 0)
       strcpy(cap_buf,CAPMSG);

    if (strlen(ml_buf) == 0)
       strcpy(ml_buf,MLMSG);

    if (strlen(ms_buf) == 0)
       strcpy(ms_buf,MSMSG);

    if (maxsenders == -1)
       maxsenders = maxallowed;

    if (maxlurkers == -1) 
       maxlurkers = maxallowed;

    if (log_limit != 0)
    {
       if (strcmp(tracefile,"stdout") == 0)
          log_file = stdout;
       else
       {
          if ((log_file = fopen(tracefile,"w")) != NULL)
          {
#ifdef _BSD
             setlinebuf(log_file);
#else
             setvbuf(log_file,NULL,_IOLBF,0);
#endif
             dolog("open_log file: %s\n",tracefile);
          }
          else
             log_limit = 0;
       }
    }

    signal(SIGTERM, proc_sig);  
    signal(SIGINT, proc_sig);    

    get_my_addr(&myaddr);
    if (myaddr.sin_addr.s_addr == 0)
    {
       dolog("Unable to get a internet address\n");
       exit(1);
    }

    init_mem();
    init_socket();
    init_timer();
    open_bcc_servers();

    gettimeofday(&tp, &tzp);
    tmp = ctime(&tp.tv_sec);
    tmp[24] = ' ';
    bcopy(tmp,start_time,strlen(tmp));

    msg = &buffer[40];
    vidptr = (VideoPacketHeader *) msg;
    while (1)
    {
       type = receive(msg,&msglen,&csock);
       pkts_in++;
       bytes_in += msglen;

       if (deny(csock))
       {
          clnt_addr.sin_family = AF_INET;
          clnt_addr.sin_port = htons(VID_PORT);
          bcopy(&vidptr->routing.src.addr,&clnt_addr.sin_addr,4);
          write_msg(&clnt_addr,kMessageType1,dy_buf); 
          continue;
       }

       switch (type)
       {
          /* process VAT, MAVEN, and NV packets        */

          case VAT_CNTL:
          case MAVEN_CNTL:
          case VAT:
          case MAVEN:
          case NV_UCAST:
          case NV_MCAST:

             mbone_pkt(msg,msglen,csock,type);
             break;

          case REF1VIDEO:
          case REF2VIDEO:
          case VIDEO:

#ifdef DEBUG
             if (debug)
             {
                if (ntohs(vidptr->message) == kAudio)
                   printf("AUDIO\n");
                printf("video packet len %d network src %s ", msglen, inet_ntoa(csock.sin_addr));
                in.s_addr = vidptr->routing.src.addr;
                printf("content src %s ",inet_ntoa(in));
                printf("family %d  seq %ld msg %d dtype %d\n", ntohs(vidptr->routing.dest.family),
                            ntohl(vidptr->seqNum), ntohs(vidptr->message),ntohs(vidptr->dataType));
             }
#endif
             /* JAL 5/11 move this check to the very beginning */

            if (vidptr->routing.src.addr == 0)
            {
               dolog("received a packet with a 0 source address from %s\n",inet_ntoa(csock.sin_addr));
               break;
            }
 
            if ((cltptr = find_client(csock.sin_addr.s_addr)) == NULL)
            {         
#ifdef DEBUG
               if (debug)
                  printf("client not found \n");
#endif
               if (type == VIDEO)
               {
                  if ((type = get_type(vidptr,csock)) == -1)
                  {
                     dolog("client not found and packet being dropped due to address or configuration restrictions\n");
                     break;
                  }
               }
               else
                  if (type == REF1VIDEO)
                     type = REF1_SERVER;
                  else
                     type = REF2_SERVER;


               if (ntohs(vidptr->dataType) == kConfigRefType)
               {
                  if ((type != BCC_SERVER) && (type != BCC_CLIENT) && (type != REF3_SERVER) && (type != BCC_GCLIENT))
                  {
                     dolog("Reflector configuration mis-match from %s\n",inet_ntoa(csock.sin_addr));
                     break;
                  }
               }
               else
                  if ((ntohs(vidptr->dataType) != kConfigVideoType) || (ntohs(vidptr->message) != kOpenConnection))
                  {
                     dolog("client not found and initial message is not Open\n");
                     break;
                  }


                if ((type == CLIENT) && (conference_id != 0))
                   if ((ntohs(vidptr->conferenceid) != conference_id) && ((conference_id & 0x8000) == 0))
                   {
                      dolog("conference ids do not match %d %d\n",ntohs(vidptr->conferenceid),conference_id);
                      clnt_addr.sin_family = AF_INET;
                      clnt_addr.sin_port = htons(VID_PORT);
                      bcopy(&vidptr->routing.src.addr,&clnt_addr.sin_addr,4);
                      write_msg(&clnt_addr,kMessageType1,ci_buf);
                      break;
                   }

                cptr = (unsigned char *) ((unsigned char *) vidptr + HEADERLEN) + 
                         sizeof(cltptr->clnt_config.clientCount) +
                         sizeof(cltptr->clnt_config.seqNum) +
                         sizeof(cltptr->clnt_config.name) +
                         sizeof(cltptr->clnt_config.sendMode) +
                         sizeof(cltptr->clnt_config.recvMode) +
                         sizeof(cltptr->clnt_config.flags);

                version = *cptr;

                cptr1 = (unsigned char *) ((unsigned char *) vidptr + HEADERLEN) + 
                          sizeof(cltptr->clnt_config.clientCount) +
                          sizeof(cltptr->clnt_config.seqNum) +
                          sizeof(cltptr->clnt_config.name) +
                          sizeof(cltptr->clnt_config.sendMode) +
                          sizeof(cltptr->clnt_config.recvMode);

                /* assume version == 1 is a PC client     */

                if (((*cptr1 & PC_CLIENT) || (version == 1)) && (min_pc_version != 0))
                {
                   if (version < min_pc_version)
                   {
                      dolog("old PC version # %d is being rejected\n",version);
                      clnt_addr.sin_family = AF_INET;
                      clnt_addr.sin_port = htons(VID_PORT);
                      bcopy(&vidptr->routing.src.addr,&clnt_addr.sin_addr,4);
                      write_msg(&clnt_addr,kMessageType1,mv_pc_buf);
                      break;
                   }
                }
                else
                   if (min_mac_version != 0) 
                   {
                      if (version < min_mac_version)
                      {
                         dolog("old MAC version # %d is being rejected\n",version);
                         clnt_addr.sin_family = AF_INET;
                         clnt_addr.sin_port = htons(VID_PORT);
                         bcopy(&vidptr->routing.src.addr,&clnt_addr.sin_addr,4);
                         write_msg(&clnt_addr,kMessageType1,mv_mac_buf);
                         break;
                      }         
                   }

                if ((cltptr = open_connection(vidptr,&csock,type)) != NULL)
                   distribute(vidptr,cltptr,TRUE);

                break;
             }

#ifdef DEBUG
             if (debug)
             {
                if (cltptr->clnt_flags & CLIENT)        
                   printf("CLIENT\n");

                if (cltptr->clnt_flags & BCC_CLIENT)
                   printf("BCC_CLIENT\n");
                if (cltptr->clnt_flags & BCC_SERVER)  
                   printf("BCC_SERVER\n");
                if (cltptr->clnt_flags & BCC_ORIGIN)  
                   printf("BCC_ORIGIN\n");
               
                if (cltptr->clnt_flags & REF1_CLIENT)   
                   printf("REF1_CLIENT\n");
                if (cltptr->clnt_flags & REF1_SERVER)   
                   printf("REF1_SERVER\n");
                if (cltptr->clnt_flags & REF1_ORIGIN)   
                   printf("REF1_ORIGIN\n");
               
                if (cltptr->clnt_flags & REF2_SERVER)  
                   printf("REF2_SERVER\n");
                if (cltptr->clnt_flags & REF2_ORIGIN)  
                   printf("REF2_ORIGIN\n");

                if (cltptr->clnt_flags & REF3_SERVER)  
                   printf("REF3_SERVER\n");
                if (cltptr->clnt_flags & REF3_ORIGIN)  
                   printf("REF3_ORIGIN\n");
                if (cltptr->clnt_flags & BCC_GCLIENT)
                   printf("BCC_GCLIENT\n");
             }
#endif
               
#ifdef DEBUG
             if (debug)
                 printf("client found \n");
#endif
             cltptr->clnt_rtimer = 0;

              /* if this is just a keep alive from a REF3 SERVER just return now */ 
              if ((cltptr->clnt_flags & REF3_SERVER) && ((csock.sin_addr.s_addr == vidptr->routing.src.addr)))
                  break;

              /* if this is just a keep alive from a BCC SERVER just return now */ 
              if ((cltptr->clnt_flags & BCC_SERVER) && ((csock.sin_addr.s_addr == vidptr->routing.src.addr)))
                  break;

              
               switch (ntohs(vidptr->routing.dest.family))
              {
                 case kReflector:

                      if ((ntohs(vidptr->dataType) == kConfigVideoType) && 
                           (ntohs(vidptr->message) == kOpenConnection))
                      {
                         /*   added by MAG 6/27/94   */
                         if ((csock.sin_addr.s_addr) == god_ip) 
                         {
                            if (debug)
                               printf("This is the God-ip. Changing conf-id if necessary.\n");
                 
                            if (conference_id != ntohs(vidptr->conferenceid)) 
                            {
                               dolog("Conf-id was %d\n", conference_id);
 
                               conference_id = ntohs(vidptr->conferenceid);
                               cltptr->clnt_conf_id = conference_id;
 
                               dolog("Conf-id changed to %d\n", conference_id);
 
                               /* weed out all the clients who now have bad conf-id's          */
                               /* however if the god-ip changed it to 0, don't kick anybody off*/

                               if ((conference_id != 0) && ((conference_id & 0x8000) == 0))
                                  remove_some_clients(conference_id);
                            }
                         }

                         continue_connection(cltptr,vidptr);  
                         distribute(vidptr,cltptr,TRUE);
                      }
                      else
                         if ((ntohs(vidptr->dataType) == kConfigVideoType) && 
                              (ntohs(vidptr->message) == kCloseConnection))
                         {
                             if (cltptr->clnt_flags & (BCC_SERVER | REF1_SERVER | REF2_SERVER | REF3_SERVER))
                             {
                               if ((cltptr = find_client(vidptr->routing.src.addr)) == NULL)
                                {
                                  in.s_addr = vidptr->routing.src.addr;
                                  dolog("Unable to find server's client at %s for close\n",inet_ntoa(in));
                                  break;
                                }
                             }

                             dolog("closing connection from %s\n",cltptr->clnt_config.name);

                             if ((cltptr->clnt_flags & HOLD_DOWN) == 0)
                                hold_down_client(cltptr);

                             distribute(vidptr,cltptr,TRUE);

                             cltptr->clnt_flags |= HOLD_DOWN;

                         }
                    break;

                 case kClient:
                      if ((ctmp = find_client(vidptr->routing.dest.addr)) == NULL)
                      {
                         in.s_addr = vidptr->routing.dest.addr;
                         dolog("Unable to find the destination client at %s for a kClient mesg\n",inet_ntoa(in));
                         dolog("msg from %s\n",inet_ntoa(csock.sin_addr));
                         break;
                      }

                    if (ntohs(vidptr->message) == kAudio)
                      {
                       if (cltptr->clnt_talker == 0)
                           dolog("%s is speaking privetly to %s\n",cltptr->clnt_config.name,ctmp->clnt_config.name);

                        if (cltptr->clnt_talker++ > 50)
                            cltptr->clnt_talker = 0;
                    }

                    write_pkt(vidptr,ctmp);
                    break;

                 case kGroup:
                     if (cltptr->clnt_flags & (BCC_SERVER | REF1_SERVER | REF2_SERVER | REF3_SERVER))
                      {
                         if ((cltptr = find_client(vidptr->routing.src.addr)) == NULL)
                         {
                            in.s_addr = vidptr->routing.src.addr;
                            dolog("Unable to find server's client at %s for group\n",inet_ntoa(in));
                            break;
                         }
                     }

                      datatype = ntohs(vidptr->dataType);

                     if (datatype == kAudio)
                        distribute_audio(vidptr,cltptr);
                      else
                         if ((datatype <= kMaxVideoDataType) && (datatype >= kMinVideoDataType))
                         {
                            cltptr->clnt_bytecnt += msglen;
                            distribute(vidptr,cltptr,FALSE);
                         }
                         else
                            if (datatype == kAuxDataTypeData)
                              distribute_aux(vidptr,cltptr);
                             else
                               if (datatype == kAuxDataTypeSupervisor)
                                 distribute(vidptr,cltptr,TRUE);

                    break;

                   default:
                    dolog("unknown video message type\n");
                    break;
              }
               break;


           case CONTROL:
#ifdef DEBUG
              if (debug)
                 printf("process control packet\n");
#endif
              conpkt = (RefConPkt *) msg;

              if (msglen < MINREFPKT)
              {
                 dolog("msglen is less then MINREFPKT\n");
                 break;    
              }

              if (msglen < ntohs(conpkt->msg_len))
              {
                 dolog("conpkt->msg_len is less then msglen\n");
                 break;    
              }

              process_control_pkt(conpkt);

              break;

           default:
              dolog("bad value returned from receive\n");
              exit(1);
       }

       if (timer_expired) 
       {
          timer_expired--;
          do_timer();
       }
    }
}

init_mem()
{
     unsigned char *stash;
     client *cltptr;
     slist *sptr;
     int cnt;



     if ((stash = (unsigned char *) calloc(MAXCLIENT,sizeof(client))) == NULL)
     {
        dolog("Unable to get client memory\n");
        exit(1);
     }

     for (cnt = 0, cltptr = (client *) stash; cnt < MAXCLIENT; cnt++, cltptr++)
         free_client(cltptr);

     if ((stash = (unsigned char *) calloc((MAXCLIENT * MAXCLIENT * 3),sizeof(slist))) == NULL)
     {
        dolog("Unable to get slist memory \n");
        exit(1);
     }

     for (cnt = 0, sptr = (slist *) stash; cnt < (MAXCLIENT * MAXCLIENT * 3); cnt++, sptr++)
         free_slist(sptr);
}

