/*
   This file is part of the IRC Bounce source code
   copyright (c) 1998 v00d00 Net Services
   and is distributed according to the GNU general public license.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

   The author (Robert Clark) can be reached at: v00d00@hempseed.com

   This code is based on Irc Proxy (c)1997-98 James Seter
*/

#include "common.h"
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <pwd.h>

extern void putlog(char *format, ...);
extern char *gethost(struct in_addr *addr);
extern int nprintf(int s, char *format, ...);
extern int do_connect(char *vhostname, char *hostname, u_short port);

int sinlen,allbuflen,highfd,s_sock=0;
unsigned char allbuf[BUFLEN],linbuff[BUFLEN];
struct sockaddr_in mysin;
tconf *conf;

struct uconentry
{
   struct uconentry *next;
   char user[256];
   char nick[30];
   char vhost[HOSTLEN];
   char fromip[HOSTLEN];
   unsigned int
      u:1,
      p:1,
      n:1,
      s:1,
      b:1;
   int fd;
   char ident[9];
   fifobuffer biff;
};

struct conentry
{
    struct conentry *next;
    char nick[30];
    char fromip[HOSTLEN];
    int ifd;
    int ofd;
};

struct uconentry headucon;
struct conentry headcon;

struct uconentry *
adducon(struct uconentry *list_ptr)
{
   while (list_ptr->next!=NULL) list_ptr=list_ptr->next;
   list_ptr->next=(struct uconentry *) malloc (sizeof (struct uconentry));
   if (list_ptr->next!=NULL) {
      memset(list_ptr->next,0,sizeof(struct uconentry));
      (list_ptr->next)->next=(struct uconentry *) NULL;
   }
   return (list_ptr->next);
}

struct conentry *
addcon(struct conentry *list_ptr)
{
   while (list_ptr->next!=NULL) list_ptr=list_ptr->next;
   list_ptr->next=(struct conentry *) malloc (sizeof (struct conentry));
   if (list_ptr->next!=NULL) {
      memset(list_ptr->next,0,sizeof(struct conentry));
      (list_ptr->next)->next=(struct conentry *) NULL;
   }
   return (list_ptr->next);
}


struct uconentry *
delucon(struct uconentry *list_ptr,int nfd)
{
   struct uconentry *last_ptr;
   last_ptr=list_ptr;
   while ((list_ptr->fd!=nfd)) {
      last_ptr=list_ptr;
      if ((list_ptr=list_ptr->next)==NULL) break;
   }
   if (list_ptr!=NULL) {
      last_ptr->next=list_ptr->next;
      free(list_ptr);
   }
   return list_ptr;
}

struct conentry *
delcon(struct conentry *list_ptr,int nfd)
{
   struct conentry *last_ptr;
   
   last_ptr=list_ptr;
   while ((list_ptr->ifd!=nfd)) {
       if ((list_ptr->ofd==nfd)) break;
       last_ptr=list_ptr;
       if ((list_ptr=list_ptr->next)==NULL) break;
   
   }
   if(list_ptr != NULL) {
      last_ptr->next=list_ptr->next;
      free(list_ptr);
   }
   return list_ptr;
}

int
getother(struct conentry *list_ptr,int nfd)
{
   while (list_ptr!=NULL) {
      if (list_ptr->ofd==nfd) return list_ptr->ifd;
      else if (list_ptr->ifd==nfd) return list_ptr->ofd;
      list_ptr=list_ptr->next;
   }
   return -1;
}

struct conentry *
getcon(struct conentry *list_ptr,int nfd)
{
   while (list_ptr!=NULL) {
      if (list_ptr->ofd==nfd) return list_ptr;
      else if (list_ptr->ifd==nfd) return list_ptr;
      list_ptr=list_ptr->next;
   }
   return NULL;
}

int
countfds(struct uconentry *uclist_ptr, struct conentry *clist_ptr)
{
   int p=0;
   while(uclist_ptr!=NULL) {
      uclist_ptr=uclist_ptr->next;
      p++;
   }
   while(clist_ptr!=NULL) {
      clist_ptr=clist_ptr->next;
      p++;
   }
   return p;
}

int
handlepucon(struct uconentry *list_ptr, int pargc, char **pargv)
{
   int p,c,f,s;
   struct conentry *bob;
   struct hostent *he;
   struct in_addr addr;
   char *tmp;
   char *pasb;
   int cs,dport;
   FILE *fp;
   struct vhost *vhosts;

   s=list_ptr->fd;
   if (!strncasecmp(pargv[0],"quit",4)) {
      putlog("%s : Disconnecting",list_ptr->fromip);
      close(s);
      delucon(&headucon,s);
      return 100;
   } else if (!strncasecmp(pargv[0],"nick",4)) {
      if (pargc<2) return 0;
      strncpy(list_ptr->nick,pargv[1],sizeof(list_ptr->nick));
      putlog("%s : NICK '%s'",list_ptr->fromip,list_ptr->nick);
      list_ptr->n=1;
      if (list_ptr->p==0) {
         nprintf(s,":irc.ibnc.com 464 %s :Password incorrect\n",list_ptr->nick);
         putlog("%s : Disconnecting (pass failure)",list_ptr->fromip);
         close(s);
         delucon(&headucon,s);
         return 100;
      }
   } else if (!strncasecmp(pargv[0],"pass",4)) {
      if (pargc<2) return 0;
      if (!strncmp(pargv[1],conf->pass,strlen(conf->pass))) list_ptr->p=1;
      putlog("%s : PASS successful",list_ptr->fromip);
   } else if (!strncasecmp(pargv[0],"user",4)) {
      putlog("%s : USER %u",list_ptr->fromip,pargc);
      if (pargc<5) return 0;
      strncpy(list_ptr->user,pargv[1],sizeof(list_ptr->user));
      for(f=0;f<(pargc-2);f++) {
         strncat(list_ptr->user," ",sizeof(list_ptr->user));
         strncat(list_ptr->user,pargv[2+f],sizeof(list_ptr->user));
      }
      putlog("%s : USER '%s'",list_ptr->fromip,list_ptr->user);
      list_ptr->u=1;
   } else if (!strncasecmp(pargv[0],"ident",5)) {
      if (list_ptr->b==0) return 0;
      if (pargc<2) return 0;
      strncpy(list_ptr->ident,pargv[1],sizeof(list_ptr->ident));
      fp=fopen(conf->homedir,"w");
      if (fp!=NULL) {
         fprintf(fp,"%s\n",list_ptr->ident);
         fclose(fp);
         nprintf(s,"NOTICE AUTH :*** Your ident is now \002%s\002\n",list_ptr->ident);
         putlog("%s : IDENT '%s'",list_ptr->fromip,list_ptr->ident);
      } else nprintf(s,"NOTICE AUTH :*** \002Error setting ident!\002\n");
   } else if (!strncasecmp(pargv[0],"oper",4)) {
      if (list_ptr->b==0) return 0;
      if (pargc<2) return 0;
      if (!(*conf->oper)) return 0;
      if (pargc==2) tmp=pargv[1];
      else tmp=pargv[2];
      if (!strncmp(tmp,conf->oper,256)) {
         list_ptr->s=1;
         nprintf(s,":irc.ibnc.com 381 %s :You are now an iBNC operator\n",list_ptr->nick);
         putlog("%s : Successful OPER",list_ptr->fromip);
      } else {
         putlog("%s : Failed OPER",list_ptr->fromip);
         nprintf(s,":irc.ibnc.com 464 %s :Password incorrect\n",list_ptr->nick);
      }
   } else if (!strncasecmp(pargv[0],"vip",3)) {
      if (list_ptr->b==0) return 0;
      if (pargc>1) {
         he=gethostbyname(pargv[1]);
         if (he) {
            memcpy(&addr.s_addr,he->h_addr,sizeof(addr.s_addr));
            nprintf(s,"NOTICE AUTH :*** Your vhost is now \002%s\002 (\002%s\002)\n",pargv[1],inet_ntoa(addr));
            strncpy(list_ptr->vhost,pargv[1],HOSTLEN);
            putlog("%s : VIP '%s'",list_ptr->fromip,list_ptr->vhost);
         } else {
            nprintf(s,"NOTICE AUTH :*** Vhost \002%s\002 is invalid\n",pargv[1]);
         }
      } else {
         f=1;
         vhosts=&conf->vhosts;
         if (vhosts==NULL || !(*vhosts->host)) nprintf(s,"NOTICE AUTH :No Vhosts Defined\n");
         else {
            nprintf(s,"NOTICE AUTH :List of Vhost's:\n");
            while (vhosts!=NULL) {
               nprintf(s,"NOTICE AUTH :%3i: %s\n",f,vhosts->host);
               f++;
               vhosts=vhosts->next;
            }
         }
      }
   } else if (!strncasecmp(pargv[0],"conn",4)) {
      if (list_ptr->b == 0) return 0;
      if (!pargc<2) {
         if (pargc>2) dport=atoi(pargv[2]);
         else dport=conf->dport;
         nprintf(s,"NOTICE AUTH :Connecting to \002%s\002 (\002%i\002)\n",pargv[1],dport);
         cs=do_connect(list_ptr->vhost,pargv[1],dport);
         if (cs>0) {
            nprintf(s,"NOTICE AUTH :Successfully connected to \002%s\002\n",pargv[1]);
            putlog("%s : CONN '%s:%u'",list_ptr->fromip,pargv[1],dport);
            if (pargc>3) nprintf(cs,"PASS :%s\n",pargv[3]);
            nprintf(cs,"USER %s\n",list_ptr->user);
            nprintf(cs,"NICK %s\n",list_ptr->nick);
            if ((bob=addcon(&headcon))==NULL) {
               close(cs);
               close(s);
            } else {
               bob->ifd=s;
               bob->ofd=cs;
               strncpy(bob->nick,list_ptr->nick,sizeof(bob->nick));
               strncpy(bob->fromip,list_ptr->fromip,HOSTLEN);
               delucon(&headucon,s);
               return 100;
            }
         } else {
            if (cs==-1) nprintf(s,"NOTICE AUTH :Failed connection to \002%s\002 (Bad VHost)\n",pargv[1]);
            else nprintf(s,"NOTICE AUTH :Failed connection to \002%s\002\n",pargv[1]);
         }
      }
   } else if (!strncasecmp(pargv[0],"who",3)) {
      if(list_ptr->s==0) return 0;
      if (!(*conf->oper)) return 0;
      bob=headcon.next;
      while(bob!=NULL) {
         nprintf(s,"NOTICE AUTH :(%i) %s@%s\n",bob->ifd,bob->nick,bob->fromip);
         bob=bob->next;
      }
   } else if (!strncasecmp(pargv[0],"kill",4)) {
      if(list_ptr->s==0||pargc<2) return 0;
      if (!(*conf->oper)) return 0;
      p=atoi(pargv[1]);
      f=getother(headcon.next,p);
      if (f==-1) {
         nprintf(s,":irc.ibnc.com 401 %s :No such FD (\002%i\002)\n",list_ptr->nick,p);
         return 0;
      }
      nprintf(s,"NOTICE AUTH :Killed %i (%s@%s)\n",p,bob->nick,bob->fromip);
      bob=getcon(headcon.next,p);
      putlog("%s : KILL %i (%s@%s)",list_ptr->fromip,p,bob->nick,bob->fromip);
      close(p);
      close(f);
      delcon(&headcon,p);
   }

   if (list_ptr->n==0) return 0;
   if (list_ptr->u==0) return 0;
   if (list_ptr->p==0) return 0;
   if (list_ptr->b==0) {
      list_ptr->b=1;
      nprintf(s,"NOTICE AUTH :\002---------------------------------------------------\002\n");
      nprintf(s,"NOTICE AUTH :IRC Bounce %s (c)1998 v00d00 Net Services\n",VERSION);
      nprintf(s,"NOTICE AUTH :\002---------------------------------------------------\002\n");
      nprintf(s,"NOTICE AUTH :vhost: \002\"%s\"\002",(*conf->vhosts.host ? conf->vhosts.host : "default"));
      if (*conf->fakeid&&*list_ptr->ident) nprintf(s," ident: \002%s\002\n",list_ptr->ident);
      else nprintf(s," ident: \002default\002\n");
      if (*conf->fakeid) nprintf(s,"NOTICE AUTH :To change idents, type \002/quote ident [ident]\002\n");
      nprintf(s,"NOTICE AUTH :To change vhosts, type \002/quote vip [ip]\002\n");
      nprintf(s,"NOTICE AUTH :To connect, type \002/quote conn [server] <port> <pass>\002\n");
      nprintf(s,"NOTICE AUTH :\002---------------------------------------------------\002\n");
   }
}

int
handleucon(struct uconentry *list_ptr , char *buf)
{
    int p;
    char *pargv[100];
    char *tmp;
    
    p=0;
    tmp=strtok(linbuff," \n\r");
    if(tmp != NULL) {
        pargv[p++]=tmp;
        while ((tmp=strtok(NULL," \n\r"))!=NULL) {
            pargv[p++]=tmp;
            if(p>100) break;
        }
        return handlepucon(list_ptr,p,pargv);
    }
    return 0;
}

int
setucon(struct uconentry *list_ptr, fd_set *nation)
{ 
    while( list_ptr != NULL) {
       FD_SET(list_ptr->fd,nation);
       if( list_ptr->fd > highfd ) highfd = list_ptr->fd;
       list_ptr = list_ptr->next;
    }
}

int
setcon(struct conentry *list_ptr, fd_set *nation)
{ 
    while( list_ptr != NULL) {
       FD_SET(list_ptr->ifd,nation);
       if( list_ptr->ifd > highfd ) highfd = list_ptr->ifd;
       FD_SET(list_ptr->ofd,nation);
       if( list_ptr->ofd > highfd ) highfd = list_ptr->ofd;
       list_ptr = list_ptr->next;
    }
}

void
initucon(fd_set *nation)
{
   highfd=s_sock;
   FD_ZERO(nation);
   FD_SET(s_sock,nation);
   setucon(headucon.next,nation);
}


void
initcon(fd_set *nation)
{
   setcon(headcon.next,nation);
}

int
chkucon(struct uconentry *list_ptr, fd_set *nation)
{
   int p,c,f;
   while (list_ptr!=NULL) {
     if (FD_ISSET(list_ptr->fd,nation)) {
        allbuflen=recv(list_ptr->fd,allbuf,1024,0);
        if (allbuflen<=0) return list_ptr->fd;
        for (p=0;p<allbuflen;p++) {
           if ((pushchar_fifo(&list_ptr->biff,allbuf[p]))==-1)
              return list_ptr->fd;
           if (allbuf[p]=='\n') {
                memset(linbuff,0,1024);
                c=popline_fifo(&list_ptr->biff, linbuff);
                f=handleucon(list_ptr, linbuff);
                if (f==100) return 0;
           }
        }
     }
     list_ptr = list_ptr->next;
   }
   return 0;
}


int
chkcon(struct conentry *list_ptr, fd_set *nation)
{
   int p,c,f;
   while( list_ptr != NULL) {
     if(FD_ISSET(list_ptr->ifd,nation)) {
        allbuflen=recv(list_ptr->ifd,allbuf,1024,0);
        if(allbuflen <=0) return list_ptr->ifd;
        send(list_ptr->ofd,allbuf,allbuflen,0);
     }
     if(FD_ISSET(list_ptr->ofd,nation)) {
        allbuflen=recv(list_ptr->ofd,allbuf,1024,0);
        if(allbuflen <=0) return list_ptr->ofd;
        send(list_ptr->ifd,allbuf,allbuflen,0);
     }
     
     list_ptr = list_ptr->next;
   }
   return 0;
}

void
docon(fd_set *nation)
{
   int p,f;
   struct conentry *bob;
   if(p=chkcon(headcon.next,nation)) {
      f=getother(headcon.next,p);
      bob=getcon(headcon.next,p);
      putlog("%s : Disconnect",bob->fromip);
      close(p);
      close(f);
      delcon(&headcon,p);
   }
}

void
doucon(fd_set *nation)
{
   FILE *fp;
   int citizen,p;
   struct in_addr faddr;
   struct uconentry *bob;

   if (FD_ISSET(s_sock,nation)) {
      citizen=accept(s_sock,(struct sockaddr *)&mysin,&sinlen);
      if (citizen>=0) {
         if (conf->maxusers) {
            if (countfds(headucon.next,headcon.next)+1>conf->maxusers) {
               nprintf(citizen,"ERROR :Closing Link: Too many users connected\n");
               close(citizen);
               return;
            }
         }
         if ((bob=adducon(&headucon))==NULL) close(citizen);
         getpeername(citizen,(struct sockaddr *)&mysin,&sinlen);
         faddr=mysin.sin_addr;
         strncpy(bob->fromip,gethost(&faddr),HOSTLEN);
         putlog("%s : Connection",bob->fromip);
         bob->fd=citizen;
         if (!(*conf->pass)) bob->p=1;
         if (*conf->vhosts.host) strcpy(bob->vhost,conf->vhosts.host);
         if (conf->fakeid) {
            fp=fopen(conf->homedir,"r");
            if (fp!=NULL) {
               fgets(bob->ident,sizeof(bob->ident),fp);
               fclose(fp);
               if (*bob->ident)
                  strncpy(bob->ident,strtok(bob->ident,"\r\n "),sizeof(bob->ident));
            }
         }
         zero_fifo(&bob->biff);
      }
   }
   if (p=chkucon(headucon.next,nation)) {
      close(p);
      delucon(&headucon,p);
   }

}

void
initproxy(tconf *cnf)
{
   int parm=0;
   struct passwd *pwp;

   s_sock=socket(AF_INET,SOCK_STREAM,0);
   setsockopt(s_sock,SOL_SOCKET,SO_LINGER,(void *)&parm,sizeof(int));
   parm=1; setsockopt(s_sock,SOL_SOCKET,SO_KEEPALIVE,(void *)&parm,sizeof(int));
   parm=1; setsockopt(s_sock,SOL_SOCKET,SO_REUSEADDR,(void *)&parm,sizeof(int));

   if (s_sock<0) do_error("socket",errno);
   memset(&mysin,0,sizeof(struct sockaddr_in));
   mysin.sin_family=AF_INET;
   mysin.sin_port=htons(cnf->lport);
   mysin.sin_addr.s_addr = INADDR_ANY;
   if (bind(s_sock,(struct sockaddr *)&mysin,sizeof(struct sockaddr_in))<0)
      do_error("bind",errno);
   if (listen(s_sock,10)<0) do_error("listen",errno);

   pwp=getpwuid(getuid());
   cnf->homedir=pwp->pw_dir;
   strcat(cnf->homedir,"/");
   strcat(cnf->homedir,cnf->fakeid);
   sinlen=sizeof(mysin);
}

void
proxy(tconf *cnf)
{ 
   int p;
   fd_set nation;
   conf=cnf;
   for (;;) {
      initucon(&nation);
      initcon(&nation);
      if ((p=select(highfd+1,&nation,(fd_set *) 0,(fd_set *) 0,NULL))<0)
         do_error("select",errno);
      docon(&nation);
      doucon(&nation);
   }
}
