/*
 * Copyright (c) 2002-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

/*
 * OpenFWTK project authmgr client protocol functions
 *
 * (C) Copyright 2002 ArkanoiD <ark@eltex.ru>
 */

/*
 * This module implements legacy protocol supported by TIS authsrv
 * shipped with Gauntlet and FWTK. 
 *
 * Some important security notes:
 *
 * 1) It is _strongly_ recommended not to use it without encryption.
 * With encryption turned off any proxy can mimick authsrv communications
 * using access to loopback interface. It is _extremely_ dangerous if
 * you run ssl proxy that supports CONNECT method.
 *
 * 2) Even worse, encryption is not designed properly and uses weak DES
 * algorithm. So the whole protocol is flawed. Standard authsrv banner
 * repeats on every session and may be used for brute force attack.
 *
 * 3) Gauntlet and exportable fwtk version do not support even that.
 */

#include <ctype.h>
#include <stdio.h>
#include <syslog.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/signal.h>
#include "firewall.h"
#include "firewall2.h"
#include "fwfunc.h"
#include "auth.h" 
#ifdef OpenBSD
#include <des.h>
#else
#include <openssl/des.h>
#endif

static	int		authfd = -1;

static des_key_schedule	deskey;
static 	int		auth_encrypt = 0;

static void
tohex(raw,hex,len)
char    *raw;
char    *hex; 
int     len;
{
        char *hdig="0123456789ABCDEF";

        while(len > 0){
                len--;
                *hex++ = hdig[((*raw)>>4)&0xf];
                *hex++ = hdig[(*raw++)&0xf];
        }
        *hex++ = '\0';
        return;
}

static void
fromhex(hex,raw,len)
char	*hex;
char    *raw;
int     len;
{
	int	x;
	char	*s;

	s = raw;
	for (x = 0; x < len; x+=2) {
		*(s++) = ((isdigit(hex[x]) ? hex[x] - '0' : hex[x] - 'A' + 10) << 4) +
		         (isdigit(hex[x+1]) ? hex[x+1] - '0' : hex[x+1] - 'A' + 10);
	}
	*s = '\0';
}
		


int
auth_open(cfp)
Cfg	*cfp;
{
	Cfg		*cf;
	char		*authserver = AUTH_DEFAULTSERVER;
	int		authport    = AUTH_DEFAULTPORT;
	des_cblock	cblock;

	if (authfd != -1)
		return(0);

    if ((cf = cfg_get("authserver",cfp))) {
		if (cf->argc < 1) {
			syslog(LLEV,"fwtkcfgerr: authserver not provided, line %d",cf->ln);
			return(1);
		}
		authserver = cf->argv[0];

        /* Check whether UNIX socket is specified */
        if(!strncasecmp(authserver, "unix:", strlen("unix:"))) {
            authport = UNIX_SOCK;
        }

        /* Don't try read a port number if UNIX socket has been specified */
		if (authport != UNIX_SOCK && cf->argc >= 2)
			authport = str_to_port(cf->argv[1]);
		if (!auth_encrypt && cf->argc >= 3) {
			auth_encrypt = 1;
			des_string_to_key(cf->argv[2],&cblock);
			bzero(cf->argv[2],strlen(cf->argv[2]));
			des_set_key(&cblock,deskey);
		}
	}

	if (!authserver) {
		syslog(LLEV,"fwtkcfgerr: no authserver defined! cannot authenticate!");
		return(1);
	}

    if (authport <= 0 && strncasecmp(authserver, "unix:", strlen("unix:"))) {
		syslog(LLEV,"fwtkcfgerr: no authport defined! cannot authenticate!");
		return(1);
	}
    else if(!strncasecmp(authserver, "unix:", strlen("unix:"))) {

        if (((authfd = conn_server_unixsock(authserver,NULL)) < 0)) {
            syslog(LLEV,"fwtksyserr: cannot connect to authentication server via unix socket %s, %s", authserver, strerror(errno));
            return(-1);
        }
    }
    else {

        if (((authfd = conn_server(authserver,authport,0,NULL)) < 0)) {
            syslog(LLEV,"fwtksyserr: cannot connect to authentication server, %s", strerror(errno));
            return(-1);
        }
    }

	signal(SIGPIPE,SIG_IGN);
	return(0);
}

int
auth_close()
{
	if (authfd >= 0) {
		close(authfd);
		authfd = -1;
		return(0);
	}
	return(0);
}

int
auth_send(buf)
char	*buf;
{
	int	len;
	int	old_linestyle;
	char	xbuf[MAX_STR * 2];
	char	cbuf[MAX_STR * 2];
	char	ebuf[MAX_STR * 2];
	char	*p;

	if (authfd < 0) {
		syslog(LLEV,"fwtksyserr: no connection to authentication server");
		return(1);
	}

	if (!buf || !*buf)
		return(1);

	bzero(xbuf,sizeof(xbuf));
	strlcpy(xbuf,buf,sizeof(xbuf));

	for (p = xbuf; *p; p++) {
		if ((*p == '\n') || (*p == '\r')) {
			syslog(LLEV,"securityalert: possible command injection attack in authsrv stream");
			syslog(LLEV,"fwtksyserr: proxy logic error - unsafe data in auth_send()");
			return(1);
		}
		if ((!isprint(*p) && (*p != '\t')) || !isascii(*p)) {
			syslog(LLEV,"securityalert: non-printable character %x in authsrv interaction", *p);
			*p = '\?';
		}
	}

	if (auth_encrypt) {
		des_cblock	iv;

		bzero(iv,sizeof(iv));	/* Sic! 	*/

		if ((len = strlen(buf) + 1) % 8)
			len += 8 + (len % 8);
		if ( len > (sizeof(cbuf) / 2)) {
			syslog(LLEV,"fwtksyserr: buffer overrun in auth_send()");
			return(1);
		}
#ifdef OpenBSD
		des_cbc_encrypt((des_cblock*) xbuf, (des_cblock*) cbuf,len,
				deskey,&iv,DES_ENCRYPT);
#else
		des_cbc_encrypt((const unsigned char*) xbuf, 
 				(unsigned char*) cbuf,len, 
				deskey,&iv,DES_ENCRYPT);
#endif
		tohex(cbuf,ebuf,len);
		p = ebuf;
	} else
		p = xbuf;

	old_linestyle = proxy_linestyle;
	sosetline(SO_LLF);
	if (sosay(authfd,p)) {
		syslog(LLEV,"fwtksyserr: error writing to authentication server");
		sosetline(old_linestyle);
		return(1);
	} else {
		sosetline(old_linestyle);
		return(0);
	}
}

int
auth_recv(buf,size)
char	*buf;
int	size;
{
	int	old_linestyle;
	int	len;
	char	xbuf[MAX_STR * 2];
	char	ebuf[MAX_STR * 2];

	if (authfd < 0) {
		syslog(LLEV,"fwtksyserr: no connection to authentication server");
		return(1);
	}

	old_linestyle = proxy_linestyle;
	sosetline(SO_LLF);
	
	if (((len = sogets(authfd,xbuf,sizeof(xbuf))) <= 1)) {
		syslog(LLEV,"fwtksyserr: auth_recv: read error %s", strerror(errno));
		sosetline(old_linestyle);
		return(1);
	}
	sosetline(old_linestyle);
	if (xbuf[len-1] == '\n')
		xbuf[len-1] = '\0';
	else {
		syslog(LLEV,"fwtksyserr: unterminated line from authsrv");
		return(1);
	}
	if (auth_encrypt) {
		des_cblock	iv;

		fromhex(xbuf,ebuf,len);
		bzero(iv,sizeof(iv));	/* Sic!		*/
#ifdef	OpenBSD	
		des_cbc_encrypt((des_cblock*) ebuf,(des_cblock*) xbuf,len,
				deskey,&iv,DES_DECRYPT);
#else
		des_cbc_encrypt((const unsigned char*) ebuf,
				(unsigned char*) xbuf,len,
				deskey,&iv,DES_DECRYPT);
#endif
	}
	if (strlen(xbuf) >= size) {
		syslog(LLEV,"fwtksyserr: auth_recv: buffer overflow");
		return(1);
	}
	strlcpy(buf,xbuf,size);
	return(0);
}

char*
sanitize_parm(parm)
char 	*parm;
{
	static  char buf[MAX_STR * 2];
	char	*pi,*po;
	int	needquotes = 0;
        char *hdig="0123456789ABCDEF";

	*(po = buf) = '\'';
	po++;

/*
 *	Here we do some ugly escaping for unwanted characters
 */
	for(pi = parm; *pi; pi++) {
		if (po > buf + sizeof(buf) - 5) {
			syslog(LLEV,"fwtksyserr: buffer overrun in parameter processing");
			break;
		}
		needquotes |= ((*pi == '\t') || (*pi == '\"') || (*pi == ' '));
		if (!isascii(*pi) || (*pi == '\'')) {
			*(po++) = '%';
                	*(po++) = hdig[((*pi)>>4)&0xf];
                	*(po++) = hdig[(*pi)&0xf];
			continue;
		}
		if (!isprint(*pi)) {
			if (*pi == '\r' || *pi == '\n') 
				syslog(LLEV,"securityalert: possible command injection attack in authsrv stream");
			syslog(LLEV,"fwtksyserr: proxy logic error - unsafe auth_perm value");
			*(po++) = '?';
			continue;
		}
		*(po++) = *pi;
	}
	if (needquotes) {
		*(po++) = '\'';
		*(po++) = '\0';
		return(buf);
	} else {
		*(po++) = '\0';
		return(buf + 1);
	}
}

int
auth_perm(cfp,name,oper,dest,op)
Cfg	*cfp;
char	*name;
char	*oper;
char	*dest;
char	**op;
{
	char	buf[MAX_STR];

	if (authfd < 0) {
		if (auth_open(cfp))
			return(1);
		if(auth_recv(buf,sizeof(buf))) {
			auth_close();
			return(-1);
		}
		if(strncmp(buf,"Authsrv ready",13)) {
			syslog(LLEV,"fwtksyserr: bad response from authentication server");
			auth_close();
			return(1);
		}
	}

	bzero(buf,sizeof(buf));

	/* Be compatible */

	if (*proxy_stats.riaddr)
		snprintf(buf,sizeof(buf),"operation user %.24s %.100s %.128s %.512s",name,oper,proxy_stats.riaddr,dest);
	else
		snprintf(buf,sizeof(buf),"operation user %.24s %.100s 'unknown' %.512s",name,oper,dest);
	if (op) {
		char	**opp;
		for (opp = op; *opp; opp++) {
			if ((strlen(buf) + strlen(*opp) + 1) >= sizeof(buf)) {
				syslog(LLEV,"fwtksyserr: auth_perm operation string buffer overflow");
				auth_close();
				return(-1);
			}
			strlcat(buf," ",sizeof(buf));
			strlcat(buf,sanitize_parm(*opp),sizeof(buf));
		}
	}
	if(auth_send(buf)) {
		auth_close();
		return(-1);
	}
	if(auth_recv(buf,sizeof(buf))) {
		auth_close();
		return(-1);
	}
	auth_close();

	if(!strncmp(buf,"ok",2))
		return(0);

    if(!strncmp(buf,"Perm",4))
		return(1);

	return(-1);
}
