/*
 *	Milter client-side API, a library to talk to Milter filters
 *	Supplementary functions to allow build without OpenFWTK
 *
 *	(C) Copyright 2003 ADVA Research Center
 *	(C) Copyright 2003 ArkanoiD <ark@eltex.net>
 */

#include "ci_milter.h"
#include <sys/file.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>

#define	SO_LNONE	0
#define	SO_LCRLF	1
#define	SO_LCR		2
#define	SO_LLF		3


int proxy_timeout = 300;

void *xmalloc(size_t);
void *xrealloc(void*,size_t);
char *xstrdup(const char*);
void sosetline(int);
time_t sotimeout(int);
int soread(int,char*,int);
int sowrite(int,char*,int);
int sogets(int,char*,int);
int sosayn(int,char*,int);
int sosay(int,char*);
int conn_server(char*,int,int,char*);
void addlist(char*,char***);
void freelist(char***);
int searchlist(char*,char**);
int searchlistnc(char*,char**);

void *
xmalloc (request)
    size_t request;
{
    void *p = malloc(request);

    if(!p) {
      	syslog(LOG_ERR, "<%d>%.128s[%d]: FATAL: xalloc: request for %lu bytes failed %m",
			 LOG_SYSLOG|LOG_INFO, __FILE__, getpid(), (unsigned long) request);
	abort();
    }
    bzero(p, request);
    return p;
}

void *
xrealloc (ptr,request)
    void*  ptr;
    size_t request;
{
    void *p = realloc(ptr,request);

    if(!p) {
      	syslog(LOG_ERR, "<%d>%.128s[%d]: FATAL: xrealloc: request for %lu bytes failed %m",
			 LOG_SYSLOG|LOG_INFO, __FILE__, getpid(), (unsigned long) request);
	abort();
    }
    return p;
}

char*
xstrdup(const char *str)
{
	char *p = strdup(str);
    	if(!p) {
      		syslog(LOG_ERR, "<%d>%.128s[%d]: xstrdup failed %m",
			 LOG_SYSLOG|LOG_INFO, __FILE__, getpid());
		abort();
    	}
	return(p);
}

static struct timeval sotimo = { 300L, 0L };
static char crlf[2] = "\r\n";
int    proxy_linestyle = SO_LCRLF;

void
sosetline(t)
int t;
{
	bzero(crlf, sizeof(crlf));
	switch(t) {
	case SO_LNONE:	break;
	case SO_LCRLF:	(void)strcpy(crlf, "\r\n"); break;
	case SO_LCR:	(void)strcpy(crlf, "\r"); break;
	case SO_LLF:	(void)strcpy(crlf, "\n"); break;
	}
	proxy_linestyle = t;
	return;
}


time_t
sotimeout(sec)
int sec;
{
	time_t oldtim = sotimo.tv_sec;
	if(sec) {
		sotimo.tv_sec = (sec == -1) ? 0L : (time_t)sec;
		sotimo.tv_usec = 0L;
	}
	return oldtim;
}

int
soread(fd, buf, siz)
int	fd, siz;
char *buf;
{
	fd_set	r;
	struct	timeval timo;
	int	x;

	if (!sotimo.tv_sec) 
		sotimeout(300);

	timo = sotimo;
		
	while (1) {
		FD_ZERO(&r);
		FD_SET(fd, &r);
		x = select(fd + 1, &r, (fd_set *)0, (fd_set *)0, &timo);
		if (x == 0) {
			syslog(LOG_ERR, "Network read timeout signal after %lu seconds",
				sotimo.tv_sec);
			return(-1);
		}
		if (x < 0) {
			if (errno == EINTR) continue;
			return -1;
		}
		break;
	}
	x = read(fd, buf, siz);
	return((x == 0) ? -1 : x);
}

int
sowrite(fd,s,n)
int	fd, n;
char *s;
{
	char	*buffer;
	int	x, nleft;
	fd_set	w;
	struct	timeval timo;

	buffer = s;
	nleft  = n;

	if (!sotimo.tv_sec)
		sotimeout(300);

	timo = sotimo;

        while ( nleft > 0 ) {
		if ((x = write(fd, buffer, nleft)) <= 0 ) {
		    if ( errno == EINTR )
		  	x = 0;
	    	    else if (errno == EAGAIN) {
			FD_ZERO(&w);
			FD_SET(fd, &w);
			switch(select(fd+1,NULL,&w,NULL,&timo)) {
				case	-1:	return(-1);
				case	 0:	syslog(LOG_ERR, 
					"Network write timeout signal after %lu seconds",
					sotimo.tv_sec);
						return(-1);
			}
		    } else
			return(-1);
	    	}
		nleft  -= x;
		buffer += x;
	}
    	return n;
}

int
sogets(fd, buf, siz)
int fd, siz;	
char *buf;
{
	int		x = 0;

	while(1) {
		if(soread(fd, (char *)&buf[x], 1) != 1)
			return(-1);

		/* some telnets send NULL-terminated lines in NOECHO :( */
		if(buf[x] == '\0')
			continue;

		if(buf[x] == '\n') {
			buf[++x] = '\0';
			return(x);
		}

		if(++x >= siz) {
			syslog(LOG_ERR, "fwtksyserr: sogets(): buffer overrun");
			return(-1);
		}
	}
}

int
sosayn(fd, s, n)
int	fd, n;
char *s;
{
	int tl = strlen(crlf);
	if(sowrite(fd, s, n) != n)
		return(1);
	
	return(tl ? sowrite(fd, crlf, tl) != tl : 0);
}

int
sosay(fd, s)
int	fd;
char *s;
{
	return(sosayn(fd, s, strlen(s)));
}


int
conn_server(srv,portnum,dummy,rbuf)
char	*srv;
int	portnum;
int	dummy;
char	*rbuf;
{
	struct	sockaddr_in	addr;
	struct	hostent		*hp = 0;
	int			fd;
	char			*p;
	char			**ap;

	p = srv;
	while(*p != '\0' && (*p == '.' || isdigit(*p)))
		p++;

	ap = 0;
	/* not all digits or dots */
	if(*p != '\0') {
		if((hp = gethostbyname(srv)) == (struct hostent *)0) {
			if(rbuf != (char *)0)
				strcpy(rbuf,"hostname unknown");
			return(-1);
		}

		ap = hp->h_addr_list;
	} else {
		unsigned long	f;

		if((f = inet_addr(srv)) == -1L) {
			if(rbuf != (char *)0)
				strcpy(rbuf,"hostname unparsable");
			return(-1);
		}

		(void)bcopy((char *)&f,(char *)&addr.sin_addr,sizeof(f));
	}

newaddr:

	if (hp) {
		if (hp->h_length > sizeof(addr.sin_addr.s_addr)) {
			syslog(LOG_ERR,"securityalert: invalid host address length (%d) hostname %.512s", hp->h_length, srv);
			strcpy(rbuf, "hostname invalid");
			return (-1);
		}
		(void)bcopy(*ap,(char *)&addr.sin_addr,hp->h_length);
	}

	addr.sin_port = htons(portnum);
	addr.sin_family = AF_INET;

	fd = socket(AF_INET,SOCK_STREAM,0);
	
	if(fd < 0) {
		if(rbuf != (char *)0)
			sprintf(rbuf,"socket: %s",strerror(errno));
		return(-2);
	}

	if(connect(fd,(struct sockaddr *)&addr,sizeof(addr)) < 0) {
		if (hp && *++ap)
			goto newaddr;
		if(rbuf != (char *)0)
			sprintf(rbuf,"connect: %s",strerror(errno));
		return(-3);
	}
	return(fd);
}


void
addlist(entry,list)
char	*entry;
char	***list;
{
	int	entries = 0;

	if (!*list) 
		*list = (char **) xmalloc (sizeof(char *) *2);
	else {
		for (entries=0; (*list)[entries]; entries++)
			;
		*list = (char **) xrealloc (*list,(entries+2)*sizeof(char*));
	}
	if (!*list)
		return;
	(*list)[entries] = xstrdup(entry);
	(*list)[entries+1] = NULL;
}

void
freelist(list)
char	***list;
{
	char	**xp;

	if (!*list)
		return;

	for (xp = *list; *xp; xp++) 
		free(*xp);

	free(*list);
	*list = NULL;
}

int
searchlist(entry,list)
char	*entry;
char	**list;
{

	char	**xp;

/*
 *	Empty list means no restrictions and thus always matches
 */

	if (!list)
		return(0);

	for (xp = list; *xp; xp++) {
		if (**xp == '!' && !strcmp(*xp + 1,entry)) 
			return(1);
		if (!strcmp(*xp,entry) || !strcmp(*xp,"*"))
			return(0);
	}
	return(1);
}

int
searchlistnc(entry,list)
char	*entry;
char	**list;
{

	char	**xp;

/*
 *	Empty list means no restrictions and thus always matches
 */

	if (!list)
		return(0);

	for (xp = list; *xp; xp++) {
		if (**xp == '!' && !strcasecmp(*xp + 1,entry)) 
			return(1);
		if(!strcasecmp(*xp,entry) || !strcasecmp(*xp,"*"))
			return(0);
	}
	return(1);
}



