#include "global.h"
#include "commands.h"
#include "mbuf.h"
#include "netuser.h"
#include "tcp.h"
#include "domain.h"
#include "md5.h"
#include "files.h"
#ifndef MSDOS
#include <time.h>
#endif

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: rtserv.c,v 1.1 1997/09/14 14:37:46 root Exp $";
#endif

#ifdef ROUTESERVER

static int Sroute = -1;
static const char routelistener[] = "Encap Route listener";
static const char routeserver[] = "Encap Route server";

static void routeserv (int s, void *ds, void *p);
static int routechk (char *routestr, char *response, char *challenge);
static char * routeaddrchk (int s);

extern struct cmds Cmds[];


struct routelst	{
	char		*routestr;
	struct routelst *next;
};
#define NULLROUTELST ((struct routelst *)0)




int
route0 (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
	return (deleteserver (&Sroute));
}



int
route1 (int argc, char *argv[], void *p OPTIONAL)
{
int port = IPPORT_ROUTE;

	if (argc > 1)	{
		port = atoi (argv[1]);
		tprintf ("Starting route server on port %d\n", port);
	}

	return (installserver (argc, argv, &Sroute, routelistener, port,
		INADDR_ANY, routeserver, routeserv, 512, NULL));

}



static void
routeserv (int s, void *ds OPTIONAL, void *p OPTIONAL)
{
struct mbuf *bp;
struct routelst *rl = NULLROUTELST, *it, *tmp;
char *hoststr;
time_t now;
char challenge[80];
char *routestr;
char *response;
char *cp;
int done = 0;
char cmdbuf[80];

	/* handle the administrivia on sockets */
	server_disconnect_io ();
	(void) sockowner(s, Curproc);	/* We own it now */
	close_s (Curproc->output);
	close_s (Curproc->input);
	Curproc->output = Curproc->input = s;

	/* let everyone know we're here! */
	log(s, "open Route Server");
	(void) time (&now);
	sprintf (challenge, "<%lu@%s>", now, Hostname);
	tprintf ("+OK Encap Route Server - %s - ready %s\n", VERSION, challenge);

	/* get the remote's IP address and validate it */
	hoststr = routeaddrchk (s);
	if (hoststr == NULLCHAR)	{
		tputs ("-ERR Problem determining address!\n");
		done = 1;
	} else if (!strcmp (hoststr, "127.0.0.1"))	{
		tprintf ("-ERR Can't add encaps to %s!\n", hoststr);
		tcmdprintf ("-ERR Can't add encaps to %s!\n", hoststr);
		done = 1;
	}



	/* Process input on the connection */
	while (!done)	{
		/* this will receive one or more route entries, and
		   after the last will end up blocking until the
		   connection is broken, at which time we remove the
		   routes added.

		   Alternately, the client may send a 'quit' command.
		 */
		if (recv_mbuf(s,&bp,0,NULLCHAR,0) == -1)
			break;

		if (!bp || !bp->data || !strnicmp ((char *)bp->data, "quit", 4))
			break;

		/* a NOOP command is allowed for the client to use
		   as a keepalive.
		 */
		if (!strnicmp ((char *)bp->data, "noop", 4))	{
			tputs ("+OK NOOP\n");
			continue;
		}

		/* if not a 'route' command, it's an error! */
		if (strnicmp ((char *)bp->data, "route", 5))
			break;

		/* format of a route line is 'route xxxxxx yyyyyyy\n'
		   where 'xxxxxx' is a route/bits string, and the
		   'yyyyyyy' is an encrypted response. The response
		   is the MD5 of three pieces of data, (1) the
		   route/bits string on this line, (2) the challenge
		   sent by this server during the signon message, and
		   (3) the password defined in the etc/routesvr.dat
		   file.
		 */
		routestr = skipnonwhite ((char *)bp->data);
		routestr = skipwhite (routestr);
		response = skipnonwhite (routestr);
		if (response == NULLCHAR || !*response)
			break;			/* format error - abort */
		*response++ = 0;
		response = skipwhite (response);
		rip (response);
		cp = skipnonwhite (response);
		if (*cp)
			*cp = 0;

		if (strlen (routestr) > 18)	{
			tputs ("-ERR Invalid route string!\n");
			break;
		}
		
		switch (routechk (routestr, response, challenge))	{
			case -1:	/* file not found */
					done = 1;
					tputs ("-ERR File I/O error authenticating!\n");
					break;
			case 1:		/* invalid password */
					done = 1;
					tputs ("-ERR Invalid authentication!\n");
					break;
			case 2:		/* route not defined */
					done = 1;
					tputs ("-ERR Route unknown!\n");
					break;
			default:	/* passed authentication! Add the route */
					sprintf (cmdbuf, "route addp %s encap %s", routestr, hoststr);
					(void) cmdparse (Cmds, cmdbuf, NULL);

					/* add it also to a list for later removal */
					tmp = callocw (1, sizeof (struct routelst));
					tmp->routestr = strdup (routestr);
					tmp->next = rl;
					rl = tmp;
					tputs ("+OK Encap route added!\n");
		}
	}
	/* remove all routes added in this session */
	it = rl;
	while (it != NULLROUTELST)	{
		sprintf (cmdbuf, "route drop %s", it->routestr);
		(void) cmdparse (Cmds, cmdbuf, NULL);
		tmp = it;
		it = it->next;
		free (tmp->routestr);
		free (tmp);
	}

	close_s (s);
	s = -1;	
}



static char *
routeaddrchk (int s)
{
struct sockaddr fsocket;
int i, trans;
char *retval = NULLCHAR;
char *cptr, *cptr2;

	i = SOCKSIZE;
	trans = DTranslate; /* Save IP address translation state */
	DTranslate = 0;     /* Force output to be numeric IP addr*/
	if (getpeername(s,(char *)&fsocket,&i) != -1)	{
		cptr = psocket(&fsocket);
		if ((cptr2 = strchr(cptr, ':')) != NULLCHAR)
			*cptr2 = 0;
		retval = strdup (cptr);
	}
	DTranslate = trans;             /* Restore original state */
	return retval;
}



static int
routechk (char *routestr, char *response, char *challenge)
{
FILE *fp;
char buf[80], *rt, *auth, *cp;
MD5_CTX mdContext;
char missive[80];
unsigned char *dp, *ep;

	/* this function looks up the given route in the 'etc/routesvr.dat'
	   file. The format of each line in that file is:

	   routestring		authenticationstring

	 */
	if ((fp = fopen (RouteServer, READ_TEXT)) == NULLFILE)
		return -1;

	while (fgets (buf, 80, fp))	{
		rip (buf);
		rt = skipwhite (buf);
		if (!*rt || *rt == '#')
			continue;		/* skip comment lines */
		auth = skipnonwhite (rt);
		if (!*auth)
			continue;
		*auth++ = 0;

		/* is this the entry we are looking for? */
		if (strcmp (routestr, rt))
			continue;
		
		auth = skipwhite (auth);
		if (!*auth)
			continue;

		/* now look for trailing white space and remove if found */
		cp = skipnonwhite (auth);
		if (*cp)
			*cp = 0;

		/* now calculate the MD5 string that we SHOULD have received */
		MD5Init (&mdContext);
		MD5Update (&mdContext, (unsigned char *) challenge, strlen (challenge));
		MD5Update (&mdContext, (unsigned char *) routestr, strlen (routestr));
		MD5Update (&mdContext, (unsigned char *) auth, strlen (auth));
		MD5Final (&mdContext);

		cp = missive;
		dp = mdContext.digest;
		ep = dp + sizeof (mdContext.digest);
		for (; dp < ep; cp += 2)
			(void) sprintf (cp, "%02x", *dp++ & 0xff);
		*cp = '\0';

		if (strcmp (missive, response) != 0) 	{
			/* Password required, but wrong one given */
			(void) fclose (fp);
			return 1;
		}

		/* whew! finally made it!! */

		(void) fclose (fp);
		return 0;
	}

	(void) fclose (fp);
	return 2;
}

#endif /* ROUTESERVER */
