/************************************************************************
 *   psybnc2.3, src/p_peer.c
 *   Copyright (C) 2001 the most psychoid  and
 *                      the cool lam3rz IRC Group, IRCnet
 *			http://www.psychoid.lam3rz.de
 *
 *   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 1, 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.
 */

#ifndef lint
static char rcsid[] = "@(#)$Id: p_peer.c, v 2.3 2001/07/13 02:03:00 psychoid Exp $";
#endif

#define P_PEER

#include <p_global.h>

/* check, if host is already connected */

jmp_buf peertimeout;

void peeralarm(int sig)
{
    longjmp(peertimeout,0x0);
}

int checkpeerhostname (char *hostname)
{
   int apeer;
   int cnt=0;
   struct peernodes *th;
   pcontext;
   th=peernode;
   while (th!=NULL)
   {
       apeer=th->uid;
       if (newpeer(apeer)->state > STD_NOUSE) {
          if (strmcmp(newpeer(apeer)->host,hostname)) {
	     cnt++;
	     if(cnt>2) 
	         return -1;
          }
       }
       th=th->next;
   }
   return 0;
}

/* get a free peer descriptor */

int getnewpeer ()
{
   int apeer;
   pcontext;
   apeer = 1;
   P_CREATE=1;
   while (apeer < 255) 
   {
       if (newpeer(apeer)->state == STD_NOUSE) {
	  clearpeer(apeer);    
          return apeer;
       }
       apeer++;
   }
   return -1;
}

/* erase a connected peer */

int erasepeer(int npeer)
{
    pcontext;
    killsocket(newpeer(npeer)->insock);
    clearpeer(npeer);
    return 0x0;
}

#ifdef LINKAGE

/* linking a link (...) */

int linklink(int npeer)
{
    int lnk;
    char buf[200];
    struct socketnodes *lkm;
    char *pt;
    pcontext;
    lnk=getlink(npeer);
    if (lnk==0) return -1;
    if (getlinkbyname(newpeer(npeer)->name)!=lnk && newpeer(npeer)->type!=NP_RELAY) return -1;
    /* hostname, name of the bouncer, and port are correct */
    pcontext;
    if (*datalink(lnk)->pass==0 && newpeer(npeer)->type!=NP_RELAY)
    {
	strmncpy(datalink(lnk)->pass,newpeer(npeer)->pass,sizeof(datalink(lnk)->pass));
	writelink(lnk);
    }
    if (strmcmp(datalink(lnk)->pass,newpeer(npeer)->pass)==0) return -1;
    /* password is correct */	
    if (newpeer(npeer)->type==NP_LINK) 
    {
	if (datalink(lnk)->type!=LI_ALLOW)
	{
	    return -1; /* invalid link type, only 'allowed' links get accepted */
	}
#ifdef HAVE_SSL
	if(datalink(lnk)->cert[0]==0)
	{
	    lkm=getpsocketbysock(newpeer(npeer)->insock);
	    if(lkm!=NULL)
	    {
		if(lkm->sock->sslfd!=NULL)
		{
		    pt=sslgetcert(lkm->sock->sslfd);
		    if(pt!=NULL)
		    {
			strmncpy(datalink(lnk)->cert,pt,sizeof(datalink(lnk)->cert));
			log(LOG_INFO,-1,gettxt(664),lnk);
		    }
		}
	    }
	} else {
	    ap_snprintf(buf,sizeof(buf),gettxt(665),lnk);
	    if(sslcheckcert(newpeer(npeer)->insock,datalink(lnk)->cert,buf)==0)
		return -1;	    
	}
#endif
	ssnprintf(newpeer(npeer)->insock,gettxt(666),me,me);
#ifdef PARTYCHANNEL
	ssnprintf(newpeer(npeer)->insock,gettxt(667)); /* getting the 'WHO' */
	ap_snprintf(buf,sizeof(buf),"%s%s%s",gettxt(668),PARTYCHAN,gettxt(669));
	ssnprintf(newpeer(npeer)->insock,buf,me,me,partytopic);
#endif
	ssnprintf(newpeer(npeer)->insock,gettxt(670)); /* getting the 'WHO' */
	datalink(lnk)->insock=newpeer(npeer)->insock;
	datalink(lnk)->instate=STD_CONN;
	datalink(lnk)->outsock=0;
	datalink(lnk)->outsock=0;
	clearpeer(npeer);
#ifdef INTNET
	joinintnettolink(lnk);
#endif
	lkm=getpsocketbysock(datalink(lnk)->insock);
        pcontext;
	if(lkm!=NULL)
	{
	    lkm->sock->param=lnk;
	    lkm->sock->handler=checklinkdata;
	    lkm->sock->errorhandler=checklinkerror;
	    lkm->sock->destructor=checklinkkill;
	}
	log(LOG_INFO,-1,gettxt(671),datalink(lnk)->name);
	sysparty(gettxt(672),datalink(lnk)->name);
	addtopology(datalink(lnk)->name,me);
	return 0x0; /* done, linked */
    }
    pcontext;
    if (newpeer(npeer)->type==NP_RELAY)
    {
	if(datalink(lnk)->allowrelay!=1)
	{
	    ssnprintf(newpeer(npeer)->insock,gettxt(673));
	    return -1;
	}
	return linkrelay(npeer,lnk);
    }
}

#endif

/* linking the new peer */

int linkpeer(int npeer)
{
    int rc;
    int rc2;
    struct usernodes *th;
    struct socketnodes *lkm;
    int sck;
    char buf[200];
    char *pt;
    pcontext;
#ifdef ANONYMOUS
#ifdef MULTIUSER
    rc=checkuser(newpeer(npeer)->login);
#else
    rc=1;
#endif
    if (rc==0x0) nousers=1;
#endif
    if (nousers==1) {
       nousers = 0;
       rc = firstuser(npeer);
       pcontext;
       if(rc==-1) return -1;
       if (rc) {
	  strmncpy(user(rc)->nick,newpeer(npeer)->nick,sizeof(user(rc)->nick));
	  strmncpy(user(rc)->wantnick,newpeer(npeer)->nick,sizeof(user(rc)->wantnick));
	  strmncpy(user(rc)->user,newpeer(npeer)->user,sizeof(user(rc)->user));
	  strmncpy(user(rc)->host,newpeer(npeer)->host,sizeof(user(rc)->host));
          user(rc)->insock = newpeer(npeer)->insock;
	  user(rc)->instate = STD_CONN;
	  lkm=getpsocketbysock(user(rc)->insock);
	  pcontext;
	  if(lkm!=NULL)
	  {
	    lkm->sock->param=rc;
	    lkm->sock->handler=userinbound;
	    lkm->sock->errorhandler=userinerror;
	    lkm->sock->destructor=userinkill;
	  }
#ifdef PARTYCHANNEL
	  joinparty(rc);
#endif
	  writeuser(rc);
          clearpeer(npeer);
	  pcontext;
	  memset(nulluser,0x0,sizeof(struct usert));
	  memset(dummyuser,0x0,sizeof(struct usert));
          return 0x0;
       }
    }
#ifdef MULTIUSER
    rc=checkuser(newpeer(npeer)->login);
#else
    rc=1;
#endif
    pcontext;
    if (rc==0x0) return -1;
    ap_snprintf(buf,sizeof(buf),"%s%s",slt1,slt2);
    pt=BLOW_stringencrypt(buf,newpeer(npeer)->pass);
    pcontext;
    ap_snprintf(newpeer(npeer)->pass,sizeof(newpeer(npeer)->pass),"=%s",pt);
    free(pt);
    if (strmcmp(newpeer(npeer)->pass,user(rc)->pass)==0x0) return -1;
#ifdef HAVE_SSL
    if(user(rc)->cert[0]==0)
    {
	lkm=getpsocketbysock(newpeer(npeer)->insock);	
	if(lkm!=NULL)
	{
	    if(lkm->sock->sslfd!=NULL)
	    {
		pt=sslgetcert(lkm->sock->sslfd);
		if(pt!=NULL)
		{
		    strmncpy(user(rc)->cert,pt,sizeof(user(rc)->cert));
		    log(LOG_INFO,-1,gettxt(674),user(rc)->login);
		    writeuser(rc);
		    flushconfig();
		}
	    }
	}
    } else {
	ap_snprintf(buf,sizeof(buf),gettxt(675),user(rc)->login);
	if(sslcheckcert(newpeer(npeer)->insock,user(rc)->cert,buf)==0)
	    return -1;	
    }
#endif
#ifndef PARTYCHANNEL
    sysparty(gettxt(676),newpeer(npeer)->login);
#endif
    log(LOG_INFO,-1,gettxt(677),newpeer(npeer)->login);
    lkm=getpsocketbysock(newpeer(npeer)->insock);
    pcontext;
    if(lkm!=NULL)
    {
	lkm->sock->param=rc;
	lkm->sock->handler=userinbound;
	lkm->sock->errorhandler=userinerror;
	lkm->sock->destructor=userinkill;
    }
    /* authentification proceeded */
    if (user(rc)->insock > 0) {
       ssnprintf(user(rc)->insock,gettxt(678),user(rc)->nick,newpeer(npeer)->host);
       killsocket(user(rc)->insock);	
    }
#ifdef PARTYCHANNEL
    if(user(rc)->sysmsg==0)
	ap_snprintf(buf,sizeof(buf),gettxt(679),newpeer(npeer)->login);
#else
    if(user(rc)->sysmsg==0)
	ap_snprintf(buf,sizeof(buf),gettxt(680),newpeer(npeer)->login);
#endif
    pcontext;
    th=usernode;
    while (th!=NULL)
    {
	if (th->user!=NULL)
	{
	    if (th->user->parent==rc || th->uid==rc)
	    {
#ifdef DYNAMIC
		cmdbconnect(th->uid);
#endif
		user(th->uid)->delayed=0; /* if a user is not connected, he will get an addserver message at login */
		user(th->uid)->insock = newpeer(npeer)->insock;
#ifdef SCRIPTING
		startdialogues(th->uid);
#endif
		strmncpy(user(th->uid)->host,newpeer(npeer)->host,sizeof(user(th->uid)->host));
		strmncpy(user(th->uid)->wantnick,newpeer(npeer)->nick,sizeof(user(th->uid)->wantnick));
		if (user(th->uid)->outstate == STD_CONN) {
		    user(th->uid)->instate = STD_CONN;
		    if (user(th->uid)->parent==0) repeatserverinit(rc);
		    if (user(th->uid)->parent == 0)
			ssnprintf(user(rc)->insock,gettxt(681),user(rc)->firstnick,user(rc)->login,user(rc)->host,user(rc)->nick);
		    if(user(th->uid)->leavequit==1)
			rejoinchannels(th->uid);
		    else
			rejoinclient(th->uid);
#ifdef PARTYCHANNEL
		    if(user(th->uid)->sysmsg==1 || th->user->parent==0)
		    {
			user(th->uid)->sysmsg=0;
			strcpy(irccontent,PARTYCHAN);
			cmdjoin(th->uid);
			user(th->uid)->sysmsg=1;
		    }
#endif
		    ap_snprintf(buf,sizeof(buf),gettxt(682),user(th->uid)->wantnick);
		    writesock_URGENT(user(th->uid)->outsock,buf);
		} else {
		    strmncpy(user(th->uid)->nick,newpeer(npeer)->nick,sizeof(user(th->uid)->nick));
		    user(th->uid)->instate = STD_CONN;
		    if (user(th->uid)->parent==0)
		    {
			if (checkforlog(th->uid))
                           ssnprintf(user(th->uid)->insock,gettxt(683),user(th->uid)->nick);
			else
                           ssnprintf(user(th->uid)->insock,gettxt(684),user(th->uid)->nick);
		    }
#ifdef PARTYCHANNEL
		    if(user(th->uid)->sysmsg==1 || th->user->parent==0)
		    {
			user(th->uid)->sysmsg=0;
			strcpy(irccontent,PARTYCHAN);
			cmdjoin(th->uid);
			user(th->uid)->sysmsg=1;
		    }
#endif
		}
	    }
	}
	th=th->next;
    }
    clearpeer(npeer);
#ifdef INTNET
    rejoinintchannels(rc);
#endif
    return 0x0;
}

/* if an new peer gets killed */

int killoldlistener(int npeer)
{
    pcontext;
    if(npeer==0) return 0x0;
    log(LOG_WARNING,-1,gettxt(685),newpeer(npeer)->host,newpeer(npeer)->login);
    erasepeer(npeer);
    return;	  
}

/* if an new peer gets killed */

int erroroldlistener(int npeer,int errn)
{
    pcontext;
    if(npeer==0) return 0x0;
    log(LOG_ERROR,-1,gettxt(686),newpeer(npeer)->host,newpeer(npeer)->login);
    currentsocket->sock->destructor=NULL;
    erasepeer(npeer);
    return;	  
}

/* checking data coming on a peer */

int checkoldlistener(int npeer)
{
    int rc;
    int i;
    struct socketnodes *lkm;
    char buf[200];
    char *po;
    pcontext;
    if (newpeer(npeer)->state != STD_NOUSE) {
          newpeer(npeer)->delayed++;
	  if (newpeer(npeer)->delayed > 8) {
	     /* all your bases are belong to us */
	     writesock(newpeer(npeer)->insock,gettxt(687));
	     erasepeer(npeer);
	     return;
	  }
	  parse();
	  if (ifcommand("USER")) {
	     strmncpy(newpeer(npeer)->user,irccontent,sizeof(newpeer(npeer)->user));
	     po=ircto;
	     po=strchr(ircto,' ');
	     if (po==NULL) return 0x0;
	     *po=0;
	     strmncpy(newpeer(npeer)->login,ircto,sizeof(newpeer(npeer)->login));
	  }
#ifdef LINKAGE
	  if (ifcommand(gettxt(688))) {
	     newpeer(npeer)->lnkport=atoi(irccontent);
	     strmncpy(newpeer(npeer)->name,ircto,sizeof(newpeer(npeer)->name));
	     newpeer(npeer)->type=NP_LINK; /* this is a bouncer */
	  }
	  if (ifcommand(gettxt(689))) {
	     strmncpy(newpeer(npeer)->vhost,irccontent,sizeof(newpeer(npeer)->vhost));
	  }
	  if (ifcommand(gettxt(690))) {
	     strmncpy(newpeer(npeer)->server,ircto,sizeof(newpeer(npeer)->server));	    
	     newpeer(npeer)->port=atoi(irccontent);
	  }
	  if (ifcommand(gettxt(691))) {
	     newpeer(npeer)->lnkport=atoi(irccontent);
	     strmncpy(newpeer(npeer)->name,ircto,sizeof(newpeer(npeer)->name));
	     newpeer(npeer)->type=NP_RELAY; /* this is a relay */
	  }
#endif
	  if (ifcommand("NICK")) {
	     strmncpy(newpeer(npeer)->nick,irccontent,sizeof(newpeer(npeer)->nick));
	     if (strlen(newpeer(npeer)->pass)==0 && newpeer(npeer)->type==0) {
	         ssnprintf(newpeer(npeer)->insock,gettxt(692),newpeer(npeer)->nick);
	     }
	  }
	  if (ifcommand("PASS")) {
	     if (*irccontent=='+') *irccontent='-';
	     strmncpy(newpeer(npeer)->pass,irccontent,sizeof(newpeer(npeer)->pass));
#ifdef LINKAGE
	     if (newpeer(npeer)->type!=NP_USER)
	     {
	         if (linklink(npeer)==-1)
		 {
		     log(LOG_ERROR,-1,gettxt(693),newpeer(npeer)->host,newpeer(npeer)->name);
	             erasepeer(npeer);
		     return 0x0;		     
		 }
		 return 0x0;
	     }
#endif
	  }
	  if (strlen(newpeer(npeer)->nick) != 0) {
  	    if (strlen(newpeer(npeer)->login) != 0) {
	      if (strlen(newpeer(npeer)->pass) != 0) {
	          if (linkpeer(npeer) ==-1) {
		     ssnprintf(newpeer(npeer)->insock,gettxt(694),newpeer(npeer)->nick);
		     log(LOG_ERROR,-1,gettxt(695),newpeer(npeer)->login,newpeer(npeer)->host);
	             erasepeer(npeer);
		  }
	      }
	    }  
	  }
    }
}

/* checking a hosts allow */

int checkhostallows(char *host)
{
    char buf[350];
    char buf2[350];
    struct stringarray *th;
    char *pt;
    ap_snprintf(buf,sizeof(buf),gettxt(696),host);
    th=hostallows;
    while(th!=NULL)
    {
	ap_snprintf(buf2,sizeof(buf2),gettxt(697),th->entry);
	pt=strchr(buf2,';');
	if(pt!=NULL) *pt=0;
	if(wild_match(buf2,buf)) return 1;
	th=th->next;
    }
    return -1;
}

/* check a connected peer structure */

int checknewlistener(int i)
{
    int ret,rc;
    int asocket;
    int npeer;
    int issl=0;
    struct socketnodes *lkm,*akm;
    int listensocket;
    pcontext;
    asocket = currentsocket->sock->syssock;
    if (checkpeerhostname(accepthost) == -1) {
	write(asocket,gettxt(698),39);
	killsocket(asocket);
	return;
    }
    npeer=getnewpeer();
    if (npeer == -1) {
	log(LOG_ERROR,-1,gettxt(699),accepthost);
        writesock(asocket,gettxt(700));
	killsocket(asocket);
	return;
    }
    ssnprintf(asocket,"%s%s%s",gettxt(701),APPNAME,APPVER);
    currentsocket->sock->param=npeer;
    clearpeer(npeer);	
    pcontext;
    newpeer(npeer)->insock = asocket;
    strmncpy(newpeer(npeer)->host,accepthost,sizeof(newpeer(npeer)->host));
    newpeer(npeer)->state = STD_NEWCON;
    newpeer(npeer)->delayed = 0;
    return;
}
/* create listening sock */

void killed ()
{
   log(LOG_ERROR,-1,gettxt(702));
   exit(0x0);
}

/* error routine */

void errored ()
{
   errn = 1;
   return;
}

/* creating listening ports on the hosts given in the config for the demon */

int createlisteners ()
{
  struct sigaction sv;
  struct sigaction sx;
  struct socketnodes *lkm;
  int proto;
  int rc;
  char entry[40];
  char host[200];
  int listenport;
  int cntlisten;
  int successes=0;
  dcchost[0]=0;
  dcc6host[0]=0;
  sigemptyset(&sv.sa_mask);
  sv.sa_handler = killed;
  sigemptyset(&sx.sa_mask);
  sx.sa_handler = errored;
  sigaction( SIGTERM, &sv, NULL);
  sigaction( SIGINT, &sx, NULL);
  sigaction( SIGKILL, &sv, NULL);
  sigaction( SIGHUP, &sx, NULL);
  sigaction( SIGUSR1, &sx, NULL);
  sigaction( SIGPIPE, &sx, NULL);
  umask( ~S_IRUSR & ~S_IWUSR );
  srand( time( NULL) ); /* here we randomize to the timer (for randstring) */
  srandom(rand()); /* salt of random bases on rand of time */
  if(getini(gettxt(703),gettxt(704),gettxt(705))==0)
  {
      if(inet_addr(value)<=0)
      {
          log(LOG_ERROR,0,gettxt(706));
      } else {
	  if(strstr(value,"S=")==value)
              strmncpy(dcchost,value+2,sizeof(dcchost));
	  else	  
              strmncpy(dcchost,value,sizeof(dcchost));
      }	
  }
  for(cntlisten=0;cntlisten<100;cntlisten++)
  {
      ap_snprintf(entry,sizeof(entry),gettxt(707),cntlisten);
      if(getini(gettxt(708),entry,gettxt(709))==0)
      {
	  listenport=atoi(value);
	  ap_snprintf(entry,sizeof(entry),gettxt(710),cntlisten);
	  rc=getini(gettxt(711),entry,gettxt(712));
	  if(rc==0)
	  {
	      strmncpy(host,value,sizeof(host));
	  } else {
	      strcpy(host,"*");
	  }
	  if(strmcmp(host,"*"))
	  {
#ifdef IPV6
	      if(createlistener("*",listenport,AF_INET,0,checknewlistener,erroroldlistener,checkoldlistener,killoldlistener)>0) successes++;

	      proto=AF_INET6;
#else
	      proto=AF_INET;
#endif
	  } else {
	      proto=getprotocol(host);
	  }      
	  if(createlistener(host,listenport,proto,0,checknewlistener,erroroldlistener,checkoldlistener,killoldlistener)>0) successes++;
      }	
  }
  if(successes!=0)
  {
      if(dcchost[0]==0)
          log(LOG_ERROR,-1,gettxt(713));
  }
  return successes; /* success */
}
