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

/*
	clnt_unixd.c,v
	Revision 1.1.1.1  1999/08/12 19:36:46  woods
	whoson version 1.08
	
	Revision 1.3  1999/06/03 09:23:10  crosser
	fix size of sockaddr_un for freebsd (crazy!)

	Revision 1.2  1999/01/30 16:44:17  crosser
	add unlink()

	Revision 1.1  1999/01/30 15:43:40  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/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 "checkperm.h"
#include "clnt_common.h"
#include "report.h"

#define MAXREQL 1024

#define INITTIMEOUT 100000
#define MAXTRIES 5
#define MAXREREADS 20

struct _unixd_serv_rec {
	char port[UNIX_PATH_MAX];
	unsigned long inittimeout;
	int maxtries;
};

int wso_unixd_serv_connect(void *priv,char *buf)
{
	struct _unixd_serv_rec *rec=(struct _unixd_serv_rec *)(priv);
	struct sockaddr_un server,me,frominet;
	int fd;
	int len,slen;
	fd_set rfds,wfds,efds;
	struct timeval seltimer;
	unsigned long timeout;
	int tries,rereads,rc=0;
	char wbuf[MAXREQL];

	memset((char *)&server,0,sizeof(server));
	server.sun_family = AF_UNIX;
	strncpy(server.sun_path,rec->port,sizeof(server.sun_path)-1);
	server.sun_path[sizeof(server.sun_path)-1]='\0';
	if ((fd=socket(AF_UNIX,SOCK_DGRAM,0)) < 0) {
		ERRLOG((LOG_ERR,"[WHOSON] socket: %m"))
		return -1;
	}
	/* This may sound stupid, but when using UNIX DGRAM soket, we
	   must explicitely bind() it.  "man 2 socket" says that a unique
	   address will be assigned automatically, but apparently this
	   ony works for INET sockets... */
	memset((char *)&me,0,sizeof(me));
	me.sun_family = AF_UNIX;
	if (tmpnam(me.sun_path) == NULL) {
		ERRLOG((LOG_ERR,"[WHOSON] cannot create temporary socket address: %m"))
		return -1;
	}
	if (bind(fd,(struct sockaddr*)&me,
                        sizeof(me)-sizeof(me.sun_path)
				+strlen(me.sun_path)) < 0) {
		ERRLOG((LOG_ERR,"[WHOSON] bind: %m"))
		return -1;
	}

	strncpy(wbuf,buf,sizeof(wbuf)-1);
	wbuf[sizeof(wbuf)-1]='\0';
	timeout=rec->inittimeout;
	for (tries=0;tries<(rec->maxtries);tries++) {
		len=strlen(wbuf);
		if (sendto(fd,wbuf,len,0,(struct sockaddr *)&server,
				sizeof(server)-sizeof(server.sun_path)
					+strlen(server.sun_path)) != len) {
			ERRLOG((LOG_ERR,"[WHOSON] sendto: %m"))
			close(fd);
			(void)unlink(me.sun_path);
			return -1;
		}

		rereads=0;
reread:

DPRINT(("unixd waiting try=%d(%d max) timeout=%lu (init %lu)\n",
			tries,rec->maxtries,timeout,rec->inittimeout))

		seltimer.tv_sec=timeout/1000000L;
		seltimer.tv_usec=timeout%1000000L;
DPRINT(("seltimer.tv_sec=%lu, seltimer.tv_usec=%lu\n",
			(long)seltimer.tv_sec,
			(long)seltimer.tv_usec))
		FD_ZERO(&rfds);
		FD_ZERO(&wfds);
		FD_ZERO(&efds);
		FD_SET(fd,&rfds);

		rc=select(fd+1,&rfds,&wfds,&efds,&seltimer);
		if (rc < 0) {
			ERRLOG((LOG_ERR,"[WHOSON] select: %m"))
			close(fd);
			(void)unlink(me.sun_path);
			return -1;
		} else if (rc > 0) {
			slen=sizeof(frominet);
			if ((len=recvfrom(fd,buf,MAXREQL-1,0,
					(struct sockaddr *) &frominet, &slen)) < 0) {
				ERRLOG((LOG_ERR,"[WHOSON] recvfrom: %m"))
				close(fd);
				(void)unlink(me.sun_path);
				return -1;
			}
			buf[len]='\0';
DPRINT(("recvfrom returned %d bytes: \"%s\"\n",len,buf))
			if (strcmp(frominet.sun_path,server.sun_path) == 0)
				break;
DPRINT(("did not pass address check: from %s, dest was %s\n",
				frominet.sun_path,server.sun_path))
			ERRLOG((LOG_ERR,"[WHOSON] ignore reply from from %s (dest was %s)",
					frominet.sun_path,server.sun_path))
			if (++rereads < MAXREREADS)
				goto reread;
			else
				sprintf(buf,"*Ignoring reply from %s, sent to %s\r\n\r\n",
					frominet.sun_path,server.sun_path);
		}

		timeout *= 2;
	}

	if (rc == 0) {
		ERRLOG((LOG_ERR,"[WHOSON] unixd excessive retries\n"))
		close(fd);
		(void)unlink(me.sun_path);
		return -1;
	}

	close(fd);
	(void)unlink(me.sun_path);
	return 0;
}

int wso_unixd_serv_cfg_init(void **priv)
{
	struct _unixd_serv_rec *rec;

	if ((rec=(struct _unixd_serv_rec *)malloc
				(sizeof(struct _unixd_serv_rec)))) {
		memset(rec,0,sizeof(struct _unixd_serv_rec));
		(*priv)=(void*)rec;
		rec->inittimeout=INITTIMEOUT;
		rec->maxtries=MAXTRIES;
		return 0;
	} else {
		ERRLOG((LOG_ERR,"[WHOSON] allocating struct _unixd_serv_rec: %m"))
		return 1;
	}
}

int wso_unixd_serv_cfg_next(char *key,char *val,void **priv)
{
	struct _unixd_serv_rec *rec=(struct _unixd_serv_rec *)(*priv);

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

	return 0;
}

int wso_unixd_serv_cfg_end(void **priv)
{
	struct _unixd_serv_rec *rec=(struct _unixd_serv_rec *)(*priv);

	if (rec->port == 0) {
		ERRLOG((LOG_ERR,"[WHOSON] bad port value \"%d\"\n",rec->port))
		return 1;
	} else if (rec->inittimeout == 0L) {
		ERRLOG((LOG_ERR,"[WHOSON] bad init timeout value \"%lu\"\n",
					rec->inittimeout))
		return 1;
	} else if (rec->maxtries == 0) {
		ERRLOG((LOG_ERR,"[WHOSON] bad init maxtries value \"%d\"\n",
					rec->maxtries))
		return 1;
	} else
		return 0;
}
