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

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

	Revision 1.9  1999/06/02 22:10:36  crosser
	deal with UNIX_PATH_MAX in a better way

	Revision 1.8  1998/07/28 17:51:53  crosser
	make 64bit architecure happy

	Revision 1.7  1998/07/26 14:06:40  crosser
	create pipe with 666 mode for compatibility

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

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

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

	Revision 1.3  1998/07/02 15:51:24  crosser
	change DPRINT

	Revision 1.2  1998/07/02 15:37:07  crosser
	make right responce if req invalid

	Revision 1.1  1998/07/01 21:55: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 <sys/stat.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>
#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 "report.h"

struct _unix_serv_rec {
	char port[UNIX_PATH_MAX];
	time_t ttl;
};

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

int unix_serv_cfg_init(void **priv)
{
	struct _unix_serv_rec *rec;

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

int unix_serv_cfg_next(char *key,char *val,void **priv)
{
	struct _unix_serv_rec *rec=(struct _unix_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,"keepalive") == 0) {
		rec->ttl=atoi(val);
	} else {
		ERRLOG((LOG_ERR,"bad keyword \"%s\"\n",key))
		return 1;
	}

	return 0;
}

int unix_serv_cfg_end(void **priv)
{
	struct _unix_serv_rec *rec=(struct _unix_serv_rec *)(*priv);

	if ((rec->port)[0] == '\0') {
		ERRLOG((LOG_ERR,"missing port value\n"))
		return 1;
	}

	return 0;
}

static void search_request(int fd,struct _unix_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';
			do_request(p,retbuf,sizeof(retbuf));
			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 unix_read_evproc(int fd,void *priv)
{
	struct _unix_data_rec *data=(struct _unix_data_rec *)priv;
	struct _evdesc evdesc;
	int len;

DPRINT(("unix_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=unix_read_evproc;
			evdesc.ttl=data->ttl;
			evdesc.priv=data;
		} else {
			if (len < 0) ERRLOG((LOG_ERR,"read from socket: %m"))
			evdesc.fd=fd;
			close(fd);
			free(data);
		}
	}

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

static struct _evdesc unix_ctl_evproc(int fd,void *priv)
{
	struct _unix_serv_rec *rec=(struct _unix_serv_rec *)priv;
	struct _unix_data_rec *data;
	struct _evdesc evdesc;
	struct sockaddr_un from;
	int flen=sizeof(from);
	int msgsock;

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

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

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

struct _evdesc unix_serv_init(void *priv)
{
	struct _unix_serv_rec *rec=(struct _unix_serv_rec *)priv;
	struct _evdesc evdesc;
	struct sockaddr_un server;
	int ctlsock=-1;
	int tries,on=1;
	int savemask;

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

	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';
	(void)unlink(server.sun_path);
	if ((ctlsock=socket(AF_UNIX,SOCK_STREAM,0)) < 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;
	}
	savemask=umask(0);
	for (tries=0;;tries++) {
		if (bind(ctlsock,(struct sockaddr*)&server,
			sizeof(server)-sizeof(server.sun_family)
					+strlen(server.sun_path)) < 0) {
			if ((errno == EADDRINUSE) && (tries < 10)) {
				sleep(tries);
				continue;
			}
			ERRLOG((LOG_ERR,"bind: %m"))
			ctlsock=-1;
			goto exit;
		} else break;
	}
	(void)umask(savemask);
	if (listen(ctlsock,1) < 0) {
		ERRLOG((LOG_ERR,"listen: %m"))
	}

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