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

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

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

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

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

	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

	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 _udp_serv_rec {
	struct _perm *perms;
	int port;
};

struct _udp_data_rec {
	struct _perm *perms;
	char buf[512];
};

int udp_serv_cfg_init(void **priv)
{
	struct _udp_serv_rec *rec;

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

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

	if (strcasecmp(key,"port") == 0) {
		rec->port=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 udp_serv_cfg_end(void **priv)
{
	struct _udp_serv_rec *rec=(struct _udp_serv_rec *)(*priv);

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

	return 0;
}

static struct _evdesc udp_read_evproc(int fd,void *priv)
{
	struct _udp_data_rec *data=(struct _udp_data_rec *)priv;
	struct _evdesc evdesc;
	int len,slen;
	struct sockaddr_in frominet;
	char retbuf[512];

DPRINT(("udp_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 {
		slen = sizeof(frominet);
		if ((len=recvfrom(fd,data->buf,sizeof(data->buf)-1,
				0,(struct sockaddr *)&frominet,&slen)) > 0) {
DPRINT(("UDP message (%d bytes) from %s\n",len,inet_ntoa(frominet.sin_addr)))
			retbuf[0]='\0';
			if (wso_perm_check(data->perms,
					ntohl(frominet.sin_addr.s_addr)))
				do_request(data->buf,retbuf,sizeof(retbuf));
			else
				strcpy(retbuf,NOPERM);
			len=strlen(retbuf);
			slen = sizeof(frominet);
			if (sendto(fd,retbuf,len,0,
				(struct sockaddr *)&frominet,slen) != len) {
				ERRLOG((LOG_ERR,"sendto: %m"))
			}
		} else {
			ERRLOG((LOG_ERR,"recvfrom: %m"))
		}
	}

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

struct _evdesc udp_serv_init(void *priv)
{
	struct _udp_serv_rec *rec=(struct _udp_serv_rec *)priv;
	struct _evdesc evdesc;
	struct _udp_data_rec *data;
	struct sockaddr_in server;
	struct protoent *proto;
	int protonum;
	int msgsock=-1;
	int tries,on=1;

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

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

exit:
	memset(&evdesc,0,sizeof(struct _evdesc));
	evdesc.fd=msgsock;
	evdesc.evproc=udp_read_evproc;
	evdesc.ttl=0;
	evdesc.priv=priv;
	if ((data=(struct _udp_data_rec *)malloc
			(sizeof(struct _udp_data_rec)))) {
		memset(data,0,sizeof(struct _udp_data_rec));
		data->perms=rec->perms;
		evdesc.priv=data;
	} else {
		ERRLOG((LOG_ERR,"allocating struct _udp_data_rec: %m"))
		evdesc.fd=-1;
		evdesc.evproc=NULL;
	}
DPRINT(("udp_serv_init returns fd=%d evproc=%p\n",evdesc.fd,evdesc.evproc))
	return evdesc;
}
