/************************************************************************
 *   psybnc2.3, src/p_socket.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_socket.c, v 2.3 2001/07/13 02:03:00 psychoid Exp $";
#endif

#define P_SOCKET

#include <p_global.h>

#define MAX_SENDQ 1000

jmp_buf	alarmret;

/* gets a socketnode from the corresponding system socket number */

struct socketnodes *previous;

struct socketnodes *getpsocketbysock(int syssock)
{
    struct socketnodes *th;
    th=socketnode;
    previous=socketnode;
    while(th!=NULL)
    {
	if (th->sock!=NULL)
	{
	    if (th->sock->syssock==syssock) return th;
	}
	previous=th;
	th=th->next;
    }
    return NULL;
}

/* creates a socket */

int createsocket(int syssock,int type,int index,int(*constructor)(int),int(*constructed)(int),int(*errorhandler)(int,int),int(*handler)(int),int(*destructor)(int),int proto,int ssl)
{
    struct psockett *th;
    struct socketnodes *lkm;
    int flags,ret;
    int lsock;
    time_t tm;
    time(&tm);
    pcontext;
    lsock=syssock;
    if(syssock!=0)
    {
	lkm=getpsocketbysock(lsock);
	if (lkm!=NULL) return lsock; /* already existent.. so why the hell... */
    } else 
    	lsock = socket (proto, SOCK_STREAM, IPPROTO_TCP);
    if(lsock<=0)
    {
	log(LOG_ERROR,-1,gettxt(790));
	return 0x0;
    }
    flags = fcntl(lsock,F_GETFL,0);
    ret = fcntl(lsock,F_SETFL,flags | O_NONBLOCK);
    lkm=socketnode;
    while (lkm!=NULL)
    {
	if (lkm->next==NULL || lkm->sock==NULL)
	{
	    if(lkm->sock!=NULL)
	    {
		lkm->next=(struct socketnodes *) pmalloc(sizeof(struct socketnodes));
		lkm=lkm->next;
	    }
	    lkm->sock=(struct psockett *) pmalloc(sizeof(struct psockett));
	    lkm->next=NULL;
	    th=lkm->sock;
	    th->type=type;
	    th->protocol=proto;
	    th->flag=SOC_NOUSE;
	    th->syssock=lsock;
#ifdef HAVE_SSL
	    th->ssl=ssl;
#endif
	    th->constructor=constructor;
	    th->constructed=constructed;
	    th->errorhandler=errorhandler;
	    th->handler=handler;
	    th->destructor=destructor;
	    th->commbuf=(char *)pmalloc(8192);
	    th->bytesin=0;
	    th->bytesout=0;
	    th->param=index;
	    strmncpy(th->since,ctime(&tm),sizeof(th->since));
	    break;
	}
	lkm=lkm->next;
    }
    if(lkm==NULL)
    {
	log(LOG_ERROR,-1,gettxt(791));
	exit(0x0);
    }
    return lsock;
}

/* kill a socket. used instead of close. possibly called iterative */

int killsocket(int syssock)
{
    struct socketnodes *lkm;
    struct socketnodes *siccur=currentsocket;
    int(*caller)(int);
    int rc,i;
    lkm=getpsocketbysock(syssock);    
    if(lkm==NULL || lkm==socketnode) return 0x0;
    if(lkm->sock!=NULL)
    {
	if(lkm->sock->destructor!=NULL)
	{
	    caller=lkm->sock->destructor;
	    lkm->sock->destructor=NULL;
	    lkm->sock->errorhandler=NULL;
	    currentsocket=lkm;
	    rc=(*caller)(lkm->sock->param);
	    currentsocket=siccur;
	    lkm=getpsocketbysock(syssock); /* if we are destroyed.. */
	    if(lkm==NULL) return 0x0;
	}
	lkm->sock->serverstoned=0; /* would loop infinitely if */
	lkm->sock->serversocket=0;
        for(i=0;i<MAX_SENDQ;i++)
		flushsendq(lkm->sock->syssock,Q_FORCED);
#ifdef HAVE_SSL
	if(lkm->sock->ssl==SSL_ON)
	{
	    if(lkm->sock->sslfd!=0)
	    {
		SSL_shutdown(lkm->sock->sslfd);
		SSL_free(lkm->sock->sslfd);
		lkm->sock->sslfd=0;
	    }
	}
#endif
	free(lkm->sock->commbuf);
	free(lkm->sock);
	previous->next=lkm->next;
	free(lkm);
    }
    shutdown(syssock,2);
    close(syssock);
    return 0x0;
}

void gotalarm(int sig)
{
    longjmp(alarmret,0x0);
}

/* create a single listener */

int createlistener(char *host,int listenport,int proto,int pending, int(*listenhandler)(int), int(*errorhandler)(int,int), int(*datahandler)(int), int(*closehandler)(int))
{
#ifdef IPV6
  struct sockaddr_in6 listen_sa6;
#endif
  struct hostent *he;
  struct sockaddr_in listen_sa;
  struct socketnodes *lkm;
  int sopts = 1;
  int issl=SSL_OFF;
  char vsl[10];
  int opt;
  char *ho;
  int listensocket;
  int rc;
  const char *pt;
  vsl[0]=0;
#ifdef HAVE_SSL
  if(strstr(host,"S=")==host)
  {
    ho=host+2;
    issl=SSL_ON;
    strcpy(vsl,gettxt(792));
  } else
#endif
    ho=host;
  listensocket = socket (proto, SOCK_STREAM, IPPROTO_TCP);
  listensocket = createsocket(listensocket,ST_LISTEN,0,NULL,listenhandler,errorhandler,datahandler,closehandler,proto,issl);
  lkm=getpsocketbysock(listensocket);
  if(lkm==NULL || listensocket==0)
  {
      if(pending==0)
          log(LOG_ERROR,-1,gettxt(793),ho,listenport,vsl);
      return 0;
  }
  strmncpy(lkm->sock->source,host,sizeof(lkm->sock->source));
  strcpy(lkm->sock->dest,"*");    
  lkm->sock->sport=listenport;
  lkm->sock->dport=0;
  lkm->sock->flag=SOC_SYN; /* we are open */
  highestsocket = listensocket;

  opt=sizeof(int);
  setsockopt (listensocket, SOL_SOCKET, SO_REUSEADDR, &sopts, opt);
#ifdef IPV6
  if(proto==AF_INET6)
     memset (&listen_sa6, 0, sizeof (struct sockaddr_in6));
  else
#endif
     memset (&listen_sa, 0, sizeof (struct sockaddr_in));

#ifdef IPV6
  if(proto==AF_INET6)
  {
      listen_sa6.sin6_port = htons (listenport);
#ifdef SUNOS
      he=getipnodebyname(ho,AF_INET6,AI_DEFAULT,&error_num);
#else
      he=gethostbyname2(ho,AF_INET6);
#endif
      if(*host=='*')
      {
          memcpy(&listen_sa6.sin6_addr,&in6addr_any,16);
	  listen_sa6.sin6_family=AF_INET6;
      } else {
          if(!he)
          {
              killsocket(listensocket);
              if(pending==0)
                  log(LOG_ERROR,-1,gettxt(794),ho,listenport,vsl);
	      return 0x0;
          }                   
          listen_sa6.sin6_family=he->h_addrtype;
          memcpy(&listen_sa6.sin6_addr,he->h_addr,he->h_length);
      }
      pt=inet_ntop(AF_INET6,&listen_sa6,lkm->sock->source,sizeof(lkm->sock->source));
/* ipv6 dcc not yet specified
      if(dcc6host[0]==0) strmncpy(dcc6host,lkm->sock->source,sizeof(dcc6host)); */
  } else {
#endif
      listen_sa.sin_port = htons (listenport);
      if(*ho=='*')
      {
          listen_sa.sin_addr.s_addr=htonl(INADDR_ANY);
	  listen_sa.sin_family=AF_INET;
      } else {
          he=gethostbyname(ho);
          if(!he)
          {
              killsocket(listensocket);
              if(pending==0)
                  log(LOG_ERROR,-1,gettxt(795),host,listenport,vsl);
	      return 0x0;
          }
          listen_sa.sin_family=he->h_addrtype;
          memcpy(&listen_sa.sin_addr,he->h_addr,he->h_length);
      }
      strmncpy(lkm->sock->source,inet_ntoa(listen_sa.sin_addr),sizeof(lkm->sock->source));
      if(dcchost[0]==0)
	  strmncpy(dcchost,lkm->sock->source,sizeof(dcchost));
#ifdef IPV6
  }
  if(proto==AF_INET6)
    rc=bind(listensocket, (struct sockaddr *) &listen_sa6, sizeof(listen_sa6));
  else  
#endif
    rc=bind(listensocket, (struct sockaddr *) &listen_sa, sizeof (struct sockaddr_in));

  if (rc < 0)
  {	
      killsocket(listensocket);
      if(pending==0)
          log(LOG_ERROR,-1,gettxt(796),ho,listenport,vsl);
      return 0; /* cannot create socket */
  }	
  if ((listen (listensocket, 1)) == -1)
  {	
      killsocket(listensocket);
      if(pending==0)
          log(LOG_ERROR,-1,gettxt(797),ho,listenport,vsl);
      return 0; /* cannot create socket */
  }
  if(pending==0)
  {
      printf(gettxt(798),lkm->sock->source,listenport,vsl);
      log(LOG_INFO,-1,gettxt(799),lkm->sock->source,listenport,vsl);
  }
  return listensocket;
}

/* conntectto - builds a connection to a host and port using a given vhost */

int rsock;

int connectto(int sockt,char *host,int port, char *vhost)
{
    int l, error;
    struct socketnodes *lkm;
#ifdef IPV6
    struct sockaddr_in6 sin6;
#endif
    struct sockaddr_in sin;
    struct hostent *he;
#ifdef IPV6
    char myhost[60];
    char hishost[60];
#else
    char myhost[15];
    char hishost[15];
#endif
    int flags, ret;
    pcontext;
    memset( &sin, 0, sizeof(sin));
#ifdef IPV6
    memset( &sin6, 0, sizeof(sin6));
#endif
    rsock = sockt;
    strcpy(myhost,"*");
    myhost[0]=0;
    hishost[0]=0;
    if (rsock < 1) return 0x0;
    lkm=getpsocketbysock(rsock);
    if(lkm==NULL)
	return 0x0;
    if (vhost!=NULL) {
#ifdef IPV6
	if(lkm->sock->protocol==AF_INET6)
	{
	    signal(SIGALRM,gotalarm);
	    if(setjmp(alarmret)==0x0)
	    {
		alarm(10);
#ifdef SUNOS
		he=getipnodebyname(vhost,AF_INET6,AI_DEFAULT,&error_num);
#else
	        he=gethostbyname2(vhost,AF_INET6);
#endif
		signal(SIGALRM,SIG_IGN);
		alarm(0);
	    } else
		he=NULL;
	    signal(SIGALRM,SIG_IGN);
	    if(he) {
		memcpy(&sin6.sin6_addr,he->h_addr,he->h_length);
		sin6.sin6_family=he->h_addrtype;
		inet_ntop(AF_INET6,&sin6,myhost,sizeof(myhost));
		if(bind(rsock, (struct sockaddr *)&sin6, sizeof(sin6)) <0)
		{
		    /* ! */
		}
	    }
	} else {
#endif
	    signal(SIGALRM,gotalarm);
	    if(setjmp(alarmret)==0x0)
	    {
		alarm(10);
		he=gethostbyname(vhost);
		signal(SIGALRM,SIG_IGN);
		alarm(0);
	    } else
		he=NULL;
	    signal(SIGALRM,SIG_IGN);
	    if(he) {
		memcpy(&sin.sin_addr,he->h_addr,he->h_length);
		sin.sin_family = he->h_addrtype;
		strmncpy(myhost,inet_ntoa(sin.sin_addr),sizeof(myhost));
		if(bind(rsock, (struct sockaddr *)&sin, sizeof(sin)) <0)
		{
		    /* ! */
		}
	    }
#ifdef IPV6
	}
#endif
    }
    memset(&sin,0,sizeof(sin));
#ifdef IPV6
    memset( &sin6, 0, sizeof(sin6));
    if(lkm->sock->protocol==AF_INET6)
    {
	sin6.sin6_port = htons(port);
	signal(SIGALRM,gotalarm);
	if(setjmp(alarmret)==0x0)
	{
	    alarm(10);
#ifdef SUNOS
	    he=getipnodebyname(host,AF_INET6,AI_DEFAULT,&error_num);
#else
	    he=gethostbyname2(host,AF_INET6);
#endif
	    signal(SIGALRM,SIG_IGN);
	    alarm(0);
	} else
	    he=NULL;
        signal(SIGALRM,SIG_IGN);
	if(!he)
	    return 0x0;
	sin6.sin6_family=he->h_addrtype;
	memcpy(&sin6.sin6_addr,he->h_addr,he->h_length);
	inet_ntop(AF_INET6,&sin6,hishost,sizeof(hishost));
	ret=connect(rsock,(struct sockaddr *)&sin6, sizeof(sin6));
    } else {
#endif
	sin.sin_port = htons(port);
	signal(SIGALRM,gotalarm);
	if(setjmp(alarmret)==0x0)
	{
	    alarm(10);
	    he=gethostbyname(host);
	    signal(SIGALRM,SIG_IGN);
	    alarm(0);
	} else
	    he=NULL;
	signal(SIGALRM,SIG_IGN);
	if(!he)
	    return 0x0;
	sin.sin_family=he->h_addrtype;
	memcpy(&sin.sin_addr,he->h_addr,he->h_length);
	strmncpy(hishost,inet_ntoa(sin.sin_addr),sizeof(hishost));
	ret =connect(rsock, (struct sockaddr *)&sin, sizeof(sin));
#ifdef IPV6
    }
#endif
    if (ret < 0) {
        if (errno != EINPROGRESS && ret != -EINPROGRESS)
	{
	    killsocket(rsock);
	    return 0x0;
	}
    }
    if(lkm!=NULL)
    {
	lkm->sock->flag=SOC_SYN;
	lkm->sock->delay=0;
	if(socketnode->sock!=NULL)
	    lkm->sock->sport=socketnode->sock->sport;
	lkm->sock->dport=port;
	replace(myhost,'%',127);
	replace(hishost,'%',127);
	strmncpy(lkm->sock->source,myhost,sizeof(lkm->sock->source));
	strmncpy(lkm->sock->dest,hishost,sizeof(lkm->sock->dest));
    }
    return rsock;
}

int urgent=0;

/* flush the queue */

int flushsendq(int socket, int forced)
{
    struct socketnodes *lkm;
    struct sendqt *msq;
    lkm=getpsocketbysock(socket);
    if(lkm==NULL) return 0x0; /* no socket, no queue */
    msq=lkm->sock->sendq;
    if(msq==NULL) return 0x0; /* nothing to flush */
    if(lkm->sock->flag<SOC_CONN) return 0x0; /* not yet connected.. no flush */
    if(forced!=Q_FORCED)
    {
	if(msq->delay>0)
	{
	    msq->delay-=delayinc;
	    return 0x0;
	}
    }
    if(lkm->sock->serversocket==1 && lkm->sock->flag==SOC_CONN)
    {
	if(lkm->sock->serverstoned>0) return 0x0;
	if(lkm->sock->serverbytes+strlen(msq->data)>700)
	{
	    if(strlen(msq->data)<700)
	    {
		lkm->sock->serverstoned=20;
		if(lkm->sock->flag==SOC_CONN)
		{
#ifdef HAVE_SSL
		    if(lkm->sock->ssl==SSL_ON)
			SSL_write(lkm->sock->sslfd,gettxt(800),9);
		    else
#endif
			write(socket,gettxt(801),9);
		}
		return 0x0;		
	    }
	}
	lkm->sock->serverbytes+=strlen(msq->data);
    }
    urgent=1;
    if(lkm->sock->dataflow==SD_STREAM)
        writesock_STREAM(socket,msq->data,msq->len);
    else
        writesock(socket,msq->data);
    free(msq->data);
    msq=msq->next;
    free(lkm->sock->sendq);
    lkm->sock->sendq=msq;
    lkm->sock->entrys--;
    return 0x0;
}

/* add data to a queue */

int addq(int socket, char *data, size_t len, int sqdelay)
{
    struct socketnodes *lkm;
    struct sendqt *msq;
    lkm=getpsocketbysock(socket);
    if(lkm==NULL) /* no socket descriptor, URGENT sent */
    {
	urgent=1;
	return writesock(socket,data);
    }
    lkm->sock->entrys++;
    if(lkm->sock->entrys>MAX_SENDQ && lkm->sock->serverstoned==0)
	flushsendq(socket,Q_FORCED); /* too many entries -> flushing */
    if (lkm->sock->sendq==NULL)
    {
	lkm->sock->sendq=(struct sendqt *)pmalloc(sizeof(struct sendqt));
	msq=lkm->sock->sendq;
    } else {
	msq=lkm->sock->sendq;
	while(msq->next!=NULL) msq=msq->next;
	msq->next=(struct sendqt *)pmalloc(sizeof(struct sendqt));
	msq=msq->next;
    }
    msq->data=(char *)pmalloc(len+2);
    msq->len=len;
    msq->delay=sqdelay;
    memcpy(msq->data,data,len);
    return 0x0;
}

/* write data to a binary socket */

int writesock_STREAM(int socket, unsigned char *data, unsigned int size)
{
    int rc;
    struct socketnodes *lkm;
    lkm=getpsocketbysock(socket);
    if(lkm==NULL) return 0x0;
    if(urgent!=0)
    {
	addq(socket,data,size,0);
	return 0x0;
    } else
    if(lkm->sock->flag==SOC_CONN)
    {
#ifdef HAVE_SSL
	if(lkm->sock->ssl==SSL_ON)
	{
	    rc=SSL_write(lkm->sock->sslfd,data,size);
	    switch(SSL_get_error(lkm->sock->sslfd,rc))
	    {
	        case SSL_ERROR_NONE:
		    break;
		case SSL_ERROR_WANT_WRITE:
		case SSL_ERROR_WANT_READ:
		case SSL_ERROR_WANT_X509_LOOKUP:
		    /* back onto the queue */
		    addq(lkm->sock->syssock,data,size,0);
		    return 0x0;
		default:
		    return -1;
	    }
	} else
#endif
	    write(socket,data,size);
    }
    lkm->sock->bytesout+=size;
    urgent=0;
    return 0x0;
}

/* write data to a socket */

int writesock (int socket, char *data)
{
    static char buf[8200];
    char sbuf[8200];
    static char kbuf[30];
    char *po;
    int rc;
    struct socketnodes *lkm;
    lkm=getpsocketbysock(socket);
    if(lkm==NULL) return 0x0;
    if(lkm->sock->flag<SOC_CONN) urgent=0;
    if(urgent==0 && lkm->sock->nowfds !=1)
    {
	addq(socket,data,strlen(data)+1,0);
	return 0x0;
    }
    if (socket == 0) return -1;
    strmncpy(buf,data,sizeof(buf));
    po=strchr(buf,'\n');
    if (po == NULL) strcat(buf,"\r\n");
    if(po!=NULL)
    {
        po--;
        if (*po!='\r')
        {
    	    po++;
	    *po='\r';
	    po++;
	    *po='\n';
	    po++;
	    *po=0;
	}
    }
    errn=0;
    if(lkm!=NULL)
    {
	if(lkm->sock!=NULL)
	{
	    lkm->sock->bytesout+=strlen(buf);
	}
    }
    if(urgent==1 || lkm->sock->nowfds == 1)
    {
	replace(buf,127,'%');
	if(lkm->sock->flag==SOC_CONN)
	{
#ifdef HAVE_SSL
	    if(lkm->sock->ssl==SSL_ON)
	    {
		strcpy(sbuf,buf);
		rc=SSL_write(lkm->sock->sslfd,sbuf,strlen(sbuf));
		switch(SSL_get_error(lkm->sock->sslfd,rc))
		{
		    case SSL_ERROR_NONE:
			break;
		    case SSL_ERROR_WANT_WRITE:
		    case SSL_ERROR_WANT_READ:
		    case SSL_ERROR_WANT_X509_LOOKUP:
			/* put it back on the queue, dont block here */
			addq(lkm->sock->syssock,sbuf,strlen(sbuf)+1,0);
			break;
		    default:
			break;
		}
	    } else
#endif
		write(socket,buf,strlen(buf));
	}
	lkm->sock->delay=300;
	urgent=0;	
    }
    if (errn == 1) {
       /* heavy error on writing */
       return -1;
    }
    return 0x0;
}

/* urgent writes */

int writesock_URGENT (int socket, char *data)
{
    urgent=1;
    return writesock(socket,data);
}

/* write in a delay */

int writesock_DELAY (int socket, char *data, int delay)
{
    return addq(socket,data,strlen(data)+1,delay);
}

/* write with format */

int 
ssnprintf(int sock, char *format,...)
{
    va_list va;
    static char buf[8192];
    va_start(va,format);
    ap_vsnprintf(buf,sizeof(buf),format,va);
    writesock(sock,buf);
    va_end(va);
    return strlen(buf);
}

/* define the protocol based on a given host */

int getprotocol(char *host)
{
#ifndef IPV6
    return AF_INET;
#else
    struct hostent *he;
    signal(SIGALRM,gotalarm);
    if(setjmp(alarmret)==0x0)
    {
	alarm(10);
#ifdef SUNOS
	he=getipnodebyname(host,AF_INET6,AI_DEFAULT,&error_num);
#else
	he=gethostbyname2(host,AF_INET6);
#endif
	signal(SIGALRM,SIG_IGN);
	alarm(0);
    } else
	he=NULL;
    signal(SIGALRM,SIG_IGN);
    if(!he)
    {
	return AF_INET; /* we check no resolve on IPv4 and assume it, if v6 does not work */
    }
    return AF_INET6;
#endif
}

/* recv from a socket */

int receivesock(struct psockett *sock)
{
    int ret=1,i;
    int sz=8191;
    int rc;
    char *br,*ebr;
    int kln;
    int esck;
    char *puk,*pt,*eh;
    static char buf[4096],kbuf[20];
    pcontext;
    sz-=sock->bytesread;
    ircbuf[0]=0;
    if(sz>0)
    {
	errno=0;
#ifdef HAVE_SSL
	if(sock->ssl==SSL_ON)
	{
	    if(sock->sslfd==NULL)
		ret=0;
	    else
		ret=SSL_read((SSL *)sock->sslfd,sock->commbuf+sock->bytesread,sz);
	    if(ret==-1 && (rc = SSL_get_error((SSL *)sock->sslfd,ret)) == SSL_ERROR_WANT_READ)
	    {
		*ircbuf=0;
		return 1;
	    }
	    if(ret<=0) return ret;
	    sock->bytesread+=ret;
	} else {
#endif
	    ret=recv(sock->syssock,sock->commbuf+sock->bytesread,sz,0);
	    if (ret>0) sock->bytesread+=ret;
	    if (ret==-1 && ((errno == EWOULDBLOCK) || (errno == EAGAIN))) { *ircbuf=0; return 1; }
	    if (ret<=0) return ret;
#ifdef HAVE_SSL
	}
#endif
    } else {
	*ircbuf=0;
	return 0x1;
    }
    if (ret>0) sock->bytesin+=ret;
    if(sock->dataflow==SD_STREAM)
    {
	if(ret<=0 || ret>sizeof(ircbuf)) return 1;
	memcpy(&ircbuf,sock->commbuf,ret);
	sock->datalen=ret;
	sock->bytesread=0;
	if (sock->handler!=NULL)
	{
	    rc=(*sock->handler)(sock->param);
	}
	return ret;
    }
    esck=sock->syssock;
    br=strchr(sock->commbuf,'\n');
    if(br==NULL) br=strchr(sock->commbuf,10); /* nulline, ignore */
    while(br!=NULL && getpsocketbysock(esck)!=NULL && ret>0)
    {
    	br++; /* :) */
	memset(ircbuf,0x0,sizeof(ircbuf));
	memcpy((void *)ircbuf,(void *)sock->commbuf,(br-sock->commbuf));
	memcpy((void *)sock->commbuf,(void *)br,8192-((br-sock->commbuf)));
	sock->bytesread-=((br-sock->commbuf));
	memset((void *)sock->commbuf+sock->bytesread,0x0,(8191-sock->bytesread));
	replace(ircbuf,'%',127);
	ebr=strchr(ircbuf,'\r');
	if(ebr==NULL) ebr=strchr(ircbuf,'\n');
	esck=sock->syssock;
	if (sock->serversocket==1)
	{
	    pt=strchr(ircbuf,' ');
	    if(pt!=NULL)
	    {
		pt++;
		if(strstr(pt,gettxt(802))==pt) /* received PONG, resetting stone */
		{
		    sock->serverstoned=0;
		    sock->serverbytes=0;
		    ircbuf[0]=0;
		}
	    }
	}
	if (sock->handler!=NULL && strlen(ircbuf)>1)
	{
	    rc=(*sock->handler)(sock->param);
	}
        if (getpsocketbysock(esck)==NULL) { ret=-1;break; }
	br=strchr(sock->commbuf,'\n');
	if(br==NULL) br=strchr(sock->commbuf,10);
    }
    return ret;
}

unsigned long oldsec=0;

int socket_connect()
{
    int ern,rc;
    ern=0;
#ifdef HAVE_SSL
    if(currentsocket->sock->ssl==SSL_ON && currentsocket->sock->sslfd!=NULL)
    {
	rc=SSL_set_fd(currentsocket->sock->sslfd,currentsocket->sock->syssock);
	if(rc==-1)
	{
	    ern=SSL_get_error((SSL *)currentsocket->sock->sslfd,rc);
	    if(ern!=SSL_ERROR_WANT_READ && ern!=SSL_ERROR_WANT_WRITE && ern!=SSL_ERROR_NONE)
	    {
    		log(LOG_ERROR,-1,gettxt(803),currentsocket->sock->syssock,currentsocket->sock->param);
    		ern=-1;
	    }
	    else
    		ern=0;
	}
	if(ern==0)
	{					
	    /* blocking ? i hope not */
	    SSL_set_connect_state(currentsocket->sock->sslfd);
	    rc=SSL_connect(currentsocket->sock->sslfd);
	    if(rc==-1)
	    {
    		ern=SSL_get_error((SSL *)currentsocket->sock->sslfd,rc);
    		if(ern!=SSL_ERROR_WANT_READ && ern!=SSL_ERROR_WANT_WRITE && ern!=SSL_ERROR_NONE)
    		{
    		    log(LOG_ERROR,-1,gettxt(804),currentsocket->sock->syssock,currentsocket->sock->param);
		    ern=-1;
	        }
		else
		    ern=0;
	    }																	
	}
    }
#endif
    return ern;
}

#ifdef HAVE_SSL

char sgcert[1024];

char *sslgetcert(SSL *ssls)
{
    struct X509 *x5r;
    char *str;
    x5r=(struct X509 *)SSL_get_peer_certificate((SSL *)ssls);
    if(x5r!=NULL)
    {
	str=X509_NAME_oneline(X509_get_subject_name((X509 *)x5r),0,0);
	if(str!=NULL)
	{
	    strmncpy(sgcert,str,sizeof(sgcert));
	    free(str);
	    str=X509_NAME_oneline(X509_get_issuer_name((X509 *)x5r),0,0);
	    if(str!=NULL)
	    {
		strncat(sgcert," ",sizeof(sgcert));
		sgcert[sizeof(sgcert)-1]=0;
		strncat(sgcert,str,sizeof(sgcert));
		sgcert[sizeof(sgcert)-1]=0;
		free(str);
		str=sgcert;
	    } else
		str=NULL; /* no issuer */
	} else
	    str=NULL; /* no subject */
	X509_free((X509 *)x5r);        
    } else
	str=NULL; /* no cert. */
    
    return str;
}

/* check the cert
 * logitem should be "Link #n" or "User FooBar"
 */

int sslcheckcert(int socket, char *cert, char *logitem)
{
    struct socketnodes *ps;
    char *ccert;
    int rc;
    ps=getpsocketbysock(socket);
    if(ps!=NULL) 
    {
	if(ps->sock->sslfd!=NULL) 
	{
	    ccert=sslgetcert(ps->sock->sslfd);
	    if(ccert==NULL)
	    {
		if(SSLSEC==0)
		{
		    log(LOG_INFO,-1,gettxt(805),logitem);
		    rc=1;
		} else {
		    log(LOG_ERROR,-1,gettxt(806),logitem);
		    rc=0;
		}
	    } else {
		if(strmcmp(ccert,cert))		
		{
		    log(LOG_INFO,-1,gettxt(807),logitem);
		    rc=1;
		} else {
		    if(SSLSEC==0)
		    {
			log(LOG_INFO,-1,gettxt(810),logitem,ccert);
			rc=1;
		    }
		    else
		    {
			log(LOG_INFO,-1,gettxt(811),logitem,ccert);
			rc=0;
		    }
		}
	    }
	} else
	    rc=-1;
    } else
	rc=-1;
    return rc;
}



#endif

/* accept incoming call on listener */

int p_accept( int lsock)
{
#ifdef IPV6
   struct sockaddr_in6 addr6;
#endif
   struct sockaddr_in addr;
   struct socketnodes *lkm;
   int tm;
   int str;
   pcontext;
   lkm=getpsocketbysock(lsock);
   if(lkm==NULL)
      return -1;
#ifdef IPV6
   if(lkm->sock->protocol==AF_INET6)
   {
      tm = sizeof(addr6);
      str = accept(lsock, ( struct sockaddr *)&addr6, &tm);
      if(str==-1) return -1;
      signal(SIGALRM,gotalarm);
      if(setjmp(alarmret)==0x0)
      {
	  alarm(10);
          hostinfo = gethostbyaddr( (char * )&addr6.sin6_addr.s6_addr, 16, AF_INET6);
	  signal(SIGALRM,SIG_IGN);
    	  alarm(0);
      } else
          hostinfo=NULL;
      signal(SIGALRM,SIG_IGN);
      if(hostinfo)
         strmncpy(accepthost,hostinfo->h_name,sizeof(accepthost));
      else
         inet_ntop(AF_INET6,&addr6,accepthost,sizeof(accepthost));
      inet_ntop(AF_INET6,&addr6,acceptip,sizeof(acceptip));
      acceptport=ntohs(addr6.sin6_port);
   }
   else
#endif
   {
       tm = sizeof(addr);
       str = accept(lsock, ( struct sockaddr * )&addr, &tm);
       if (str==-1) return -1;
       signal(SIGALRM,gotalarm);
       if(setjmp(alarmret)==0x0)
       {
	   alarm(10);
           hostinfo = gethostbyaddr( ( char * )&addr.sin_addr.s_addr, sizeof( struct in_addr), AF_INET);
	   signal(SIGALRM,SIG_IGN);
	   alarm(0);
       } else
           hostinfo=NULL;
       signal(SIGALRM,SIG_IGN);
       if (hostinfo) 
          strmncpy(accepthost,hostinfo->h_name,sizeof(accepthost));
       else 
          strmncpy(accepthost,inet_ntoa( addr.sin_addr ),sizeof(accepthost));
       strmncpy(acceptip,inet_ntoa(addr.sin_addr),sizeof(acceptip));
       acceptport=ntohs(addr.sin_port);
   }
   return str;
}

int socket_accept()
{
    int ret,rc;
    int asocket;
    int npeer;
    int issl=0;
    int listensocket;
    int p_proto;
#ifdef HAVE_SSL
    int sslerr;
    char sebuf[1000];
#endif
    pcontext;
    p_proto=currentsocket->sock->protocol;
    listensocket=currentsocket->sock->syssock;
    asocket = p_accept(listensocket);
    pcontext;
    mastersocket=currentsocket;
    if(mastersocket!=NULL)
    {
	mastersocket->sock->flag=SOC_SYN; /* resetting the listener to listen again */
#ifdef HAVE_SSL
	issl=mastersocket->sock->ssl; /* inherit ssl flag */
#endif
    } else {
	log(LOG_ERROR,-1,gettxt(812));
	shutdown(asocket,2);
	close(asocket);
	return -1;
    }
    if(checkhostallows(acceptip)==-1 && checkhostallows(accepthost)==-1)
    {
	log(LOG_ERROR,-1,gettxt(813),accepthost);
	shutdown(asocket,2);
	close(asocket);
	return -1;
    }
    if(asocket<=0) return -1;    
    log(LOG_WARNING,-1,gettxt(814),accepthost);
    /* inherit handlers */
    asocket=createsocket(asocket,ST_LISTEN,0,NULL,NULL,mastersocket->sock->errorhandler,mastersocket->sock->handler,mastersocket->sock->destructor,p_proto,issl);
    pcontext;
    if (asocket==-1) return -1;
    currentsocket=getpsocketbysock(asocket);
#ifdef HAVE_SSL
    if(currentsocket!=NULL && mastersocket!=NULL) /* inherit ssl-setup */
    {
	currentsocket->sock->ssl=mastersocket->sock->ssl;
	currentsocket->sock->sslfd=mastersocket->sock->sslfd;
	mastersocket->sock->sslfd=NULL;
	if(currentsocket->sock->ssl==SSL_ON && currentsocket->sock->sslfd!=NULL)
	{
	    SSL_set_fd(currentsocket->sock->sslfd,asocket);
	    SSL_set_accept_state(currentsocket->sock->sslfd);
	    rc=SSL_accept(currentsocket->sock->sslfd);
	    if(rc==-1)
	    {
		rc=SSL_get_error(currentsocket->sock->sslfd,rc);
		switch(rc)
		{
		    case SSL_ERROR_NONE:
			break;
		    case SSL_ERROR_WANT_WRITE:
		    case SSL_ERROR_WANT_READ:
		    case SSL_ERROR_WANT_X509_LOOKUP:
			break;
		    default:
			ERR_error_string(rc,sebuf);
			log(LOG_ERROR,-1,gettxt(815),accepthost,sebuf);
			killsocket(asocket);
			return -1;
		}
	    }
	    log(LOG_INFO,-1,gettxt(816),accepthost);
	}
    }
#endif
    strmncpy(currentsocket->sock->source,acceptip,sizeof(currentsocket->sock->source));
    strmncpy(currentsocket->sock->dest,mastersocket->sock->source,sizeof(currentsocket->sock->dest));
    currentsocket->sock->param=mastersocket->sock->param;
    currentsocket->sock->sport=acceptport;
    currentsocket->sock->dport=mastersocket->sock->sport;
    currentsocket->sock->flag=SOC_CONN;
    return 0x0;
}

/* central socketdriver event routine */

unsigned long socketdriver()
{
    fd_set rfds;
    fd_set wfds;
    struct socketnodes *th,*par;
    int rc,altsock;
    int goterr=0;
    int fdscnt=0,wfdscnt=0;
    int sockit=0,sockat=9999,ret,noadv,ln,opt;
    int tt,optbuf;
    unsigned long sec;
    int sck,ssck;
#ifdef HAVE_SSL
    SSL_CIPHER *c;
    int bits;
#endif
    long otm;
    struct tm *xtm;
    int issl=SSL_OFF;
    int ssl_fd;
    struct socketnodes *lkm;
    static struct timeval tv;
    int nowf=0;
    time_t tm,em;
    /* checking the advancing timer */
    delayinc=0;
    time(&tm);
    if (tm!=oldsec) delayinc=1;
    oldsec=tm;
    th=socketnode;
    par=th;
    /* constructors / socket set / selects */
    FD_ZERO( &rfds);
    FD_ZERO( &wfds);
    while(th!=NULL)
    {
	rc=0;noadv=0;
	if(th->sock!=NULL)
	{
	    if(th->sock->serversocket)
	    {
		if(th->sock->serverstoned>0)
		{
		    th->sock->serverstoned-=delayinc;
		    /* if the socket was stoned, we just go on sending */
		    if(th->sock->serverstoned==0) th->sock->serverbytes=0;
		}
	    }
	    if (th->sock->syssock>sockit) sockit=th->sock->syssock;
	    if (th->sock->syssock<sockat) sockat=th->sock->syssock;
	    if (th->sock->flag==SOC_NOUSE)
	    {
		currentsocket=th;
		altsock=th->sock->syssock;
		if(th->sock->constructor!=NULL)
		    rc=(*th->sock->constructor)(th->sock->param);
		th=par->next;
		if(th!=NULL)
		{
		    if(altsock==th->sock->syssock)
			th->sock->flag=SOC_SYN;    
		    else
			noadv=1;
		} else
		    noadv=1;
	    } else {
		if (th->sock->flag==SOC_SYN || th->sock->flag==SOC_CONN && th->sock->syssock>0)
		{
#ifdef HAVE_SSL
		    if(th->sock->ssl==SSL_ON && th->sock->sslfd!=NULL)
		    {
			ssl_fd=SSL_get_fd(th->sock->sslfd);
			if(ssl_fd>0)
			{
			    if(ssl_fd<sockat) sockat=ssl_fd;
			    if(ssl_fd>sockit) sockit=ssl_fd;
			    FD_SET(ssl_fd,&rfds);
			}
		    } else
#endif
			FD_SET(th->sock->syssock, &rfds);
		    fdscnt++;
		    if(th->sock->flag==SOC_SYN || (th->sock->sendq!=NULL && th->sock->serverstoned==0))
		    {
#ifdef HAVE_SSL
			if(th->sock->ssl==SSL_ON && th->sock->sslfd!=NULL)
			{
			    ssl_fd=SSL_get_fd(th->sock->sslfd);
			    if(ssl_fd>0)
			    {
				if(ssl_fd<sockat) sockat=ssl_fd;
				if(ssl_fd>sockit) sockit=ssl_fd;
				FD_SET(ssl_fd,&wfds);
			    }
			} else
#endif
			    FD_SET(th->sock->syssock, &wfds);
			wfdscnt++;
		    }
		}
	    }
	}
	if(noadv==0)
	{
	    par=th;
	    th=th->next;
	}
    }
    if(sockat>sockit) sockat=sockit;
    /* selecting */
    if (fdscnt==0) 
    {
	sleep(1);
	return 0x0;
    }
    pcontext;
    tv.tv_usec = 0;
    tv.tv_sec = 3;
    if(wfdscnt>0)
    	ln=select(sockit +1, &rfds, &wfds,NULL,&tv);
    else
    	ln=select(sockit +1, &rfds, NULL,NULL,&tv);
    time(&em);
    if(ln<=0) return em-tm;
    th=socketnode;
    par=th;
    if(sockit<=0) sockit=1;
    /* reading, connecting done, errorhandling */
    for(sck=sockit;sck>=sockat;sck--)
    {
	noadv=0;
	ssck=sck;
	th=getpsocketbysock(sck);
	if(th!=NULL)
	{
	    currentsocket=th;
#ifdef HAVE_SSL
	    if(th->sock->ssl==SSL_ON && th->sock->sslfd!=NULL)
		ssck=SSL_get_fd(th->sock->sslfd);
	    if(ssck<=0) ssck=sck;
#endif
	    altsock=ssck;
	    nowf=th->sock->nowfds;
	    if(th->sock->flag==SOC_SYN)
	    {
		if(FD_ISSET(ssck,&rfds) || FD_ISSET(ssck,&wfds))
		{
    		    opt=sizeof(optbuf);
    		    if (getsockopt(ssck, SOL_SOCKET, SO_ERROR, &optbuf,&opt) >=0) 
		    {
			if(optbuf==0)
			{
			    /* connected */
			    altsock=th->sock->syssock;
#ifdef HAVE_SSL
			    if(th->sock->ssl==SSL_ON)
			    {
				if(th->sock->type==ST_CONNECT)
				    th->sock->sslfd=SSL_new(clnctx);
				else
				    th->sock->sslfd=SSL_new(srvctx);
				if(th->sock->sslfd==0)
				{
				    log(LOG_ERROR,-1,gettxt(817),th->sock->syssock,th->sock->param);
				    optbuf=-1;
				}
			    }
#endif
			    if(optbuf==0)
			    {
				if(th->sock->type==ST_CONNECT)
				    rc=socket_connect();
				else
				    rc=socket_accept();
				if(rc==0)
				{	
				    if(th->sock->type==ST_CONNECT)
					th->sock->flag=SOC_CONN;
				    th->sock->delay=300;
				    if(th->sock->constructed!=NULL)
				    {
					pcontext;
					rc=(*th->sock->constructed)(th->sock->param);
					pcontext;
				    }
				}
			    }
			}
		    } else {
		        optbuf==-1;
		    }
		    if(optbuf!=0) {
			/* error */
			th=getpsocketbysock(altsock);
			if(th!=NULL)
			{
			    if(th->sock->errorhandler!=NULL)
			    {
				pcontext;
				rc=(*th->sock->errorhandler)(th->sock->param,SERR_REFUSED);
				pcontext;
			    }
			    goterr=1;
			    if(getpsocketbysock(altsock)!=NULL)
			    {
				killsocket(th->sock->syssock);
			    }
			}
		    }	
		} else {
		    if(th->sock->flag==SOC_SYN && th->sock->type==ST_CONNECT)
		    {
			th->sock->delay+=delayinc;
			if(th->sock->delay>SOC_TIMEOUT)
			{
			    altsock=th->sock->syssock;
			    /* timed out, terminating socket and calling error */
			    if(th->sock->errorhandler!=NULL)
			    {
				pcontext;
				rc=(*th->sock->errorhandler)(th->sock->param,SERR_TIMEOUT);
				pcontext;
			    }
			    goterr=1;
			    if(getpsocketbysock(altsock)!=NULL)
			    {
				killsocket(th->sock->syssock);
			    }
			}
		    }
		}
	    } else 
	    if (th->sock->flag==SOC_CONN) {
		noadv=0;
		if(FD_ISSET(ssck,&rfds))
		{
		    if (th->sock->flag==SOC_CONN)
		    {
			altsock=th->sock->syssock;
			rc=receivesock(th->sock);
			th=par->next;
			if (getpsocketbysock(altsock)==NULL) rc=-1;
			if (rc<=0)
			{
			    killsocket(altsock);
			    noadv=1;
			}
		    }
		} else {
		    /* disconnection sensing at outgoing sockets */
		    if(th->sock->flag==SOC_CONN && th->sock->type==ST_CONNECT && th->sock->dataflow!=SD_STREAM)
		    {
			th->sock->delay-=delayinc;
			if(th->sock->delay<=0)
			{
			    writesock_URGENT(th->sock->syssock,"\r\n");
			    th->sock->delay=300;
			}
		    }
		}
		if(FD_ISSET(ssck,&wfds) && noadv==0 && th->sock->serverstoned==0)
		{
#ifdef HAVE_SSL
		    if(th->sock->ssl==SSL_OFF)
			flushsendq(sck,Q_NEXT);
		    else
		        if(th->sock->ssl==SSL_ON && th->sock->sslfd!=NULL)
			{
			    c=SSL_get_current_cipher(th->sock->sslfd);
			    SSL_CIPHER_get_bits(c,&bits);
			    if(bits!=0) /* if handshake is done.. */
				flushsendq(sck,Q_NEXT);
			}
#else
			flushsendq(sck,Q_NEXT);
#endif
		}
	    }
	}
	if (goterr==1) break;
    }
    return em-tm;
}

