#ifndef LINT
static char *rcsid="serv_tcp.c,v 1.1.1.1 1999/08/12 19:36:46 woods Exp";
#endif

/*
	serv_tcp.c,v
	Revision 1.1.1.1  1999/08/12 19:36:46  woods
	whoson version 1.08
	
	Revision 1.9  1998/07/28 17:51:53  crosser
	make 64bit architecure happy

	Revision 1.8  1998/07/26 14:06:40  crosser
	change due to checkperm names

	Revision 1.7  1998/07/05 00:26:18  crosser
	Change copyright

	Revision 1.6  1998/07/03 09:32:48  crosser
	make persistant connections work

	Revision 1.5  1998/07/02 18:01:15  crosser
	change error reporting to syslog

	Revision 1.4  1998/07/02 15:37:07  crosser
	check perms

	Revision 1.3  1998/07/01 08:04:58  crosser
	stylistic

	Revision 1.2  1998/07/01 05:18:09  crosser
	minor warnings fix

	Revision 1.1  1998/07/01 05:01:22  crosser
	Initial revision

*/

/*
	WHAT IS IT:
		Implementation of experimental "whoson" protocol
	AUTHOR:
		Eugene G. Crosser <crosser@average.org>
	COPYRIGHT:
		Public domain
*/

#include "config.h"

#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <netdb.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <setjmp.h>
#include <stdio.h>
#include <ctype.h>

#include "whosond.h"
#include "rtconfig.h"
#include "serv_common.h"
#include "checkperm.h"
#include "report.h"

struct _tcp_serv_rec {
	struct _perm *perms;
	int port;
	time_t ttl;
};

struct _tcp_data_rec {
	int allow;
	time_t ttl;
	int off;
	char buf[512];
};

int tcp_serv_cfg_init(void **priv)
{
	struct _tcp_serv_rec *rec;

	if ((rec=(struct _tcp_serv_rec *)malloc
					(sizeof(struct _tcp_serv_rec)))) {
		memset(rec,0,sizeof(struct _tcp_serv_rec));
		(*priv)=(void*)rec;
		return 0;
	} else {
		ERRLOG((LOG_ERR,"allocating struct _tcp_serv_rec: %m"))
		return 1;
	}
}

int tcp_serv_cfg_next(char *key,char *val,void **priv)
{
	struct _tcp_serv_rec *rec=(struct _tcp_serv_rec *)(*priv);
	struct _perm *perms;

	if (strcasecmp(key,"port") == 0) {
		rec->port=atoi(val);
	} else if (strcasecmp(key,"keepalive") == 0) {
		rec->ttl=atoi(val);
	} else if (strcasecmp(key,"allow") == 0) {
		if ((perms=wso_perm_parse(1,val))) {
			perms->next=rec->perms;
			rec->perms=perms;
		} else {
			ERRLOG((LOG_ERR,"unparsable allow value \"%s\"\n",val))
		}
	} else if (strcasecmp(key,"deny") == 0) {
		if ((perms=wso_perm_parse(0,val))) {
			perms->next=rec->perms;
			rec->perms=perms;
		} else {
			ERRLOG((LOG_ERR,"unparsable deny value \"%s\"\n",val))
		}
	} else {
		ERRLOG((LOG_ERR,"bad keyword \"%s\"\n",key))
		return 1;
	}

	return 0;
}

int tcp_serv_cfg_end(void **priv)
{
	struct _tcp_serv_rec *rec=(struct _tcp_serv_rec *)(*priv);

	if (rec->port == 0) {
		ERRLOG((LOG_ERR,"bad port value \"%d\"\n",rec->port))
		return 1;
	}

	return 0;
}

static void search_request(int fd,struct _tcp_data_rec *data)
{
	char c,*p,*q;
	int crcount=0,lfcount=0;
	char retbuf[512];

	for (p=data->buf;*p;p=q) {
		for (q=p;*q;q++) {
			switch (*q) {
			case '\r':	crcount++;
					break;
			case '\n':	lfcount++;
					break;
			default:	crcount=0; lfcount=0;
					break;
			}
			if ((crcount > 1) || (lfcount > 1)) break;
		}
		if ((crcount > 1) || (lfcount > 1)) {
			while (*q && ((*q == '\r') || (*q == '\n'))) q++;
			c=*q;
			*q='\0';
			retbuf[0]='\0';
			if (data->allow)
				do_request(p,retbuf,sizeof(retbuf));
			else
				strcpy(retbuf,NOPERM);
			write(fd,retbuf,strlen(retbuf));
			*q=c;
		} else break;
	}
	if (*p) { /* move possible trailer to the start of buffer */
		q=data->buf;
		while (*p) *q++=*p++;
		data->off=strlen(data->buf);
	} else
		data->off=0;
}

static struct _evdesc tcp_read_evproc(int fd,void *priv)
{
	struct _tcp_data_rec *data=(struct _tcp_data_rec *)priv;
	struct _evdesc evdesc;
	int len;

DPRINT(("tcp_read_evproc(%d,%p)\n",fd,priv))

	memset(&evdesc,0,sizeof(struct _evdesc));
	evdesc.fd=-1;
	if (fd < 0) {
		fd=-fd;
		close(fd);
		free(data);
	} else {
		if ((len=read(fd,(data->buf)+(data->off),
				sizeof(data->buf)-(data->off)-1)) > 0) {
			(data->off) += len;
			(data->buf)[data->off]='\0';
			search_request(fd,data);
			evdesc.fd=fd;
			evdesc.evproc=tcp_read_evproc;
			evdesc.ttl=data->ttl;
			evdesc.priv=data;
		}
		if (len <= 0) {
			if (len < 0) ERRLOG((LOG_ERR,"read from socket: %m"))
			evdesc.fd=fd;
			close(fd);
			free(data);
		}
	}

DPRINT(("tcp_read_evproc returns fd=%d evproc=%p\n",evdesc.fd,evdesc.evproc))
	return evdesc;
}

static struct _evdesc tcp_ctl_evproc(int fd,void *priv)
{
	struct _tcp_serv_rec *rec=(struct _tcp_serv_rec *)priv;
	struct _tcp_data_rec *data;
	struct _evdesc evdesc;
	struct sockaddr_in from;
	int flen=sizeof(from);
	int allow;
	int msgsock;

DPRINT(("tcp_ctl_evproc(%d,%p)\n",fd,priv))

	msgsock=accept(fd,(struct sockaddr*)&from,&flen);
	if (msgsock < 0) {
		ERRLOG((LOG_ERR,"accept: %m"))
	}

	allow=wso_perm_check(rec->perms,ntohl(from.sin_addr.s_addr));

	memset(&evdesc,0,sizeof(struct _evdesc));
	evdesc.fd=-1;
	if (msgsock >= 0) {
		if ((data=(struct _tcp_data_rec *)malloc
				(sizeof(struct _tcp_data_rec)))) {
			memset(data,0,sizeof(struct _tcp_data_rec));
			data->allow=allow;
			data->ttl=rec->ttl;
			evdesc.fd=msgsock; /* accept real socket */
			evdesc.evproc=tcp_read_evproc;
			evdesc.ttl=rec->ttl;
			evdesc.priv=data;
		} else {
			ERRLOG((LOG_ERR,"allocating struct _tcp_serv_rec: %m"))
			close(msgsock);
		}
	}
DPRINT(("tcp_ctl_evproc returns fd=%d evproc=%p\n",evdesc.fd,evdesc.evproc))
	return evdesc;
}

struct _evdesc tcp_serv_init(void *priv)
{
	struct _tcp_serv_rec *rec=(struct _tcp_serv_rec *)priv;
	struct _evdesc evdesc;
	struct sockaddr_in server;
	struct protoent *proto;
	int protonum;
	int ctlsock=-1;
	int tries,on=1;

DPRINT(("tcp_serv_init(%p)\n",priv))

	memset((char *)&server,0,sizeof(server));
	server.sin_family = AF_INET;
	if ((proto=getprotobyname("tcp"))) protonum=proto->p_proto;
	else protonum=6;
	server.sin_port=htons(rec->port);
	server.sin_addr.s_addr=INADDR_ANY;
	if ((ctlsock=socket(AF_INET,SOCK_STREAM,protonum)) < 0) {
		ERRLOG((LOG_ERR,"socket: %m"))
		goto exit;
	}
	if (setsockopt(ctlsock,SOL_SOCKET,SO_REUSEADDR,
				(char*)&on,sizeof(on)) < 0) {
		ERRLOG((LOG_ERR,"setsockopt: %m"))
		ctlsock=-1;
		goto exit;
	}
	for (tries=0;;tries++) {
		if (bind(ctlsock,(struct sockaddr*)&server,
						sizeof(server)) < 0) {
			if ((errno == EADDRINUSE) && (tries < 10)) {
				sleep(tries);
				continue;
			}
			ERRLOG((LOG_ERR,"bind: %m"))
			ctlsock=-1;
			goto exit;
		} else break;
	}
	if (listen(ctlsock,1) < 0) {
		ERRLOG((LOG_ERR,"listen: %m"))
	}

exit:
	memset(&evdesc,0,sizeof(struct _evdesc));
	evdesc.fd=ctlsock;
	evdesc.evproc=tcp_ctl_evproc;
	evdesc.ttl=0;
	evdesc.priv=priv;
DPRINT(("tcp_serv_init returns fd=%d evproc=%p\n",evdesc.fd,evdesc.evproc))
	return evdesc;
}
