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

/*
	clnt_tcp.c,v
	Revision 1.1.1.1  1999/08/12 19:36:46  woods
	whoson version 1.08
	
	Revision 1.11  1999/01/30 14:41:05  crosser
	fix use of saved errno

	Revision 1.10  1998/07/26 14:06:40  crosser
	fix for multiline responces

	Revision 1.9  1998/07/12 16:43:57  crosser
	Change protocol: responce now is terminated with empty line

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

	Revision 1.7  1998/07/03 11:10:39  crosser
	setup for _REENTRANT mode

	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
	make right return if req invalid or disallowed

	Revision 1.3  1998/07/01 21:55:16  crosser
	forgot to close fd in normal case

	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

	Revision 1.1  1998/05/05 19:08:16  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 <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 <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "whoson.h"
#include "rtconfig.h"
#include "clnt_common.h"
#include "report.h"

#define MAXTRIES 3
#define MAXREQL 1024

struct _tcp_serv_rec {
#ifndef _REENTRANT
	int fd;
#define FD (rec->fd)
#endif
	int port;
	char addr[16];
};

int wso_tcp_serv_connect(void *priv,char *buf)
{
	struct _tcp_serv_rec *rec=(struct _tcp_serv_rec *)(priv);
	struct sockaddr_in server;
	struct protoent *pe;
	int protonum;
	int len,off,left,crcount,lfcount,saveerr;
	char *p;
	int tries=0;
	RETSIGTYPE (*savepipe)(int);
#ifdef _REENTRANT
	int fd=-1;
#define FD fd
#endif

	if (FD >= 0) goto session;
reconnect:
	memset((char *)&server,0,sizeof(server));
	server.sin_family = AF_INET;
	if ((pe=getprotobyname("tcp"))) protonum=pe->p_proto;
	else protonum=6;
	server.sin_port=htons(rec->port);
	server.sin_addr.s_addr=inet_addr(rec->addr);
	if ((FD=socket(AF_INET,SOCK_STREAM,protonum)) < 0) {
		ERRLOG((LOG_ERR,"[WHOSON] server socket: %m"))
		return -1;
	}
	if (connect(FD,(struct sockaddr *)&server,sizeof(server)) < 0) {
		ERRLOG((LOG_ERR,"[WHOSON] connect server: %m"))
		close(FD);
		FD=-1;
		return -1;
	}
session:
	savepipe=signal(SIGPIPE,SIG_IGN);
	len=write(FD,buf,strlen(buf));
	saveerr=errno;
	(void)signal(SIGPIPE,savepipe);
	errno=saveerr;
	if (len != strlen(buf)) {
		if ((errno == EPIPE) && (++tries < MAXTRIES)) {
			DPRINT(("server gone (write), reconnecting %d\n",tries))
			close(FD);
			goto reconnect;
		}
		ERRLOG((LOG_ERR,"[WHOSON] write to server: %m"))
		close(FD);
		FD=-1;
		return -1;
	}
	off=0;
	left=MAXREQL-1;
	crcount=0;
	lfcount=0;
	while (left) {
		if ((len=read(FD,buf+off,left)) < 0) {
			if ((errno == EPIPE) && (++tries < MAXTRIES)) {
				DPRINT(("server gone (read), reconnect %d\n",
					tries))
				close(FD);
				goto reconnect;
			}
			ERRLOG((LOG_ERR,"[WHOSON] read from server: %m"))
			close(FD);
			FD=-1;
			return -1;
		}
		if (len == 0) break;
		for (p=buf+off;
		     (p<buf+MAXREQL) && (crcount<2) && (lfcount<2);
		     p++)
		switch (*p) {
		case '\r':	crcount++;
				break;
		case '\n':	lfcount++;
				break;
		default:	crcount=0; lfcount=0;
				break;
		}
		off+=len;
		left-=len;
		if ((crcount > 1) || (lfcount > 1)) break;
	}
	buf[off]='\0';

#ifdef _REENTRANT
	close(FD);
	FD=-1;
#endif

	return 0;
}

int wso_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));
#ifndef _REENTRANT
		rec->fd=-1;
#endif
		(*priv)=(void*)rec;
		return 0;
	} else {
		ERRLOG((LOG_ERR,"[WHOSON] allocating struct _tcp_serv_rec: %m"))
		return 1;
	}
}

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

	if (strcasecmp(key,"port") == 0) {
		rec->port=atoi(val);
	} else if (strcasecmp(key,"address") == 0) {
		strncpy(rec->addr,val,sizeof(rec->addr)-1);
		rec->addr[sizeof(rec->addr)-1]='\0';
	} else {
		ERRLOG((LOG_ERR,"[WHOSON] bad keyword \"%s\"\n",key))
		return 1;
	}

	return 0;
}

int wso_tcp_serv_cfg_end(void **priv)
{
	struct _tcp_serv_rec *rec=(struct _tcp_serv_rec *)(*priv);
	int d1,d2,d3,d4;

	if (rec->port == 0) {
		ERRLOG((LOG_ERR,"[WHOSON] bad port value \"%d\"\n",rec->port))
		return 1;
	} else if (sscanf(rec->addr,"%d.%d.%d.%d",&d1,&d2,&d3,&d4) != 4) {
		ERRLOG((LOG_ERR,"[WHOSON] bad addr value \"%s\"\n",rec->addr))
		return 1;
	} else
		return 0;
}
