/*
**	auth.c	- 
**
**
** Copyright (c) 1995-96  Hughes Technologies Pty Ltd
**
** Permission to use, copy, and distribute for non-commercial purposes,
** is hereby granted without fee, providing that the above copyright
** notice appear in all copies and that both the copyright notice and this
** permission notice appear in supporting documentation.
**
** This software is provided "as is" without any expressed or implied warranty.
**
**
*/


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <common/portability.h>
#include <msql/msql.h>
#include "lite.h"

static	int	sock;
static	char	*url,
		*hostName,
		*hostAddr,
		qBuf[255];

int HTUU_decode();
void sendAuthHeader();


/**********************************************************************
** Area matching routines
**
*/

int findArea(area,namespace)
	char	*area,
		*namespace;
{
	m_result *res;
	m_row	row;
	char	*cp1,
		*cp2;
	int	matchLength,
		newLength;

	if (msqlQuery(sock,"select name,url,namespace from areas order by url")<0)
	{
		runError(msqlErrMsg);
		exit(1);
	}
	res = msqlStoreResult();
	row = msqlFetchRow(res);
	matchLength = 0;
	while(row)
	{
		cp1 = url;
		cp2 = row[1];
		while(*cp1 && *cp2)
		{
			if (*cp1 == *cp2)
			{
				cp1++;
				cp2++;
				continue;
			}
			break;
		}

		/*
		** Did we match to the end of the area URL?
		*/
		if (*cp2 == 0)
		{
			newLength =  cp2 - row[1];
			if (newLength > matchLength)
			{
				strcpy(area,row[0]);
				strcpy(namespace,row[2]);
				matchLength = newLength;
			}
		}
		row = msqlFetchRow(res);
	}
	msqlFreeResult(res);
	if (matchLength == 0)
		return(-1);
	return(0);
}



httpAuth(uname, passwd, area, file)
	char	*uname,
		*passwd,
		*area,
		*file;
{
	char	*authInfo,
		authBuf[100],
		*tok;
	int	length;

        /*
        ** Get the username & passwd
        */
        authInfo = (char *)getenv("HTTP_AUTHORIZATION");
        if (!authInfo)
        {
                sendAuthHeader(area, "Missing Authentication Details",file);
                exit(1);
        }
        authInfo = (char *)index(authInfo,' ');
        if (!authInfo)
        {
                sendAuthHeader(area, "Malformed Authentication Details",file);
                exit(1);
        }
        authInfo++;
        bzero(authBuf,100);
        length = HTUU_decode(authInfo, authBuf, 100);
        *(authBuf+length) = 0;
        tok = (char *)strtok(authBuf,":");
	if (tok)
		strcpy(uname,tok);
	else
		*uname = 0;
        tok = (char *)strtok(NULL,"\n");
	if (tok)
		strcpy(passwd,tok);
	else
		*passwd = 0;
	if (!(*uname) || !(*passwd))
	{
		sendAuthHeader(area, "Missing Username or Password",file);
		exit(1);
	}
}




char *fcrypt();

void checkUser(group, area, namespace)
	char	*group,
		*area,
		*namespace;
{
        m_result *res;
        m_row   row;
        char    username[64],
		passwd[64],
		*cryptpw,
		*curpw,
		salt[3];



	if (strcmp(group,"** Public **") == 0)
		return;

	httpAuth(username, passwd, area, NULL);

	/*
	** Is the user in the correct group?
	*/
	if (strcmp(group,"** No Group **") != 0)
	{
		sprintf(qBuf,
	    		"select uname from group_members where group='%s' and uname='%s' and namespace='%s'",
			group, username, namespace);
        	if (msqlQuery(sock,qBuf) < 0)
        	{
                	runError(msqlErrMsg);
                	exit(1);
        	}
        	res = msqlStoreResult();
		if (msqlNumRows(res) == 0)
		{
			sendAuthHeader(area, "Bad Group Membership",NULL);
			exit(1);
		}
	}

	/*
	** Is the passwd correct?
	*/
	sprintf(qBuf, 
		"select passwd from users where uname='%s' and namespace='%s'",
		username, namespace);
        if (msqlQuery(sock,qBuf) < 0)
        {
                runError(msqlErrMsg);
                exit(1);
        }
        res = msqlStoreResult();
	row = msqlFetchRow(res);
	if (!row)
	{
		sendAuthHeader(area, "Invalid Username or Password",NULL);
		exit(1);
	}

	curpw = row[0];
	salt[0] = *curpw;
	salt[1] = *(curpw + 1);
	salt[2] = 0;
	cryptpw = (char *)fcrypt(passwd,salt);
	if (strcmp(cryptpw,row[0]) != 0)
	{
		sendAuthHeader(area, "Invalid Username or Password",NULL);
		exit(1);
	}
	symCreateCharGlobal("$USERNAME",username);
	symCreateCharGlobal("$NAMESPACE",namespace);
}



int checkAccess(area, namespace, group)
	char	*area,
		*namespace,
		*group;
{
	m_result *res;
	m_row	row;
	char	*cp,
		*cp2;
	int	tokLen;

	/*
	** Get the area info
	*/
	sprintf(qBuf,"select offset, group, acl from area_acl where name='%s' and namespace='%s' order by offset",area,namespace);
	if (msqlQuery(sock,qBuf) < 0)
	{
		runError(msqlErrMsg);
		exit(1);
	}
	res = msqlStoreResult();
	row = msqlFetchRow(res);
	if (msqlNumRows(res) == 0)
		return(0);

	/*
	** Look for a matching ACL entry.  It could be an address or
	** domain mask!
	*/
	while(row)
	{
		cp = row[2];
		while (isdigit(*cp)||*cp=='.'||*cp=='*')
			cp++;
		if (*cp == 0)
		{
			/* 
			** It's an address.
			** Check from the left end upto the first wildcard
			*/
			cp = (char *)index(row[2],'*');
			if (cp)
			{
				if (strncmp(hostAddr,row[2],(cp-row[2])) == 0)
				{
					strcpy(group,row[1]);
					return(0);
				}
				row = msqlFetchRow(res);
				continue;
			}
			else
			{
				/* No wildcard - check everything */
				if (strcmp(hostAddr,row[2]) == 0)
				{
					strcpy(group,row[1]);
					return(0);
				}
				row = msqlFetchRow(res);
				continue;
			}
		}
		else
		{
			/* 
			** It's a hostname.
			** If we don't have a hostname, bail out
			*/
			if (!hostName)
			{
				runError(
				"Can't get your hostname from your address");
				exit(1);
			}
			if (strlen(hostName) == 0)
			{
				runError(
				"Can't get your hostname from your address");
				exit(1);
			}

			/*
			** Take everything to lower case
			*/
			cp = row[2];
			while(*cp)
			{
				*cp = tolower(*cp);
				cp++;
			}

			/*
			** OK, we have a hostname.  Check from the last
			** wildcard to the right end of the string
			*/
			cp = (char *)rindex(row[2],'*');
			if (cp)
			{
				cp++;
				tokLen = strlen(row[2]) - (cp - row[2]);
				cp2 = hostName + (strlen(hostName) - tokLen);
				if (cp2 > hostName)
				{
					if (strcmp(cp2,cp) == 0)
					{
						strcpy(group,row[1]);
						return(0);
					}
				}
			}
			else
			{
				if (strcmp(row[2],hostName) == 0)
				{
					strcpy(group,row[1]);
					return(0);
				}
			}
		}
		row = msqlFetchRow(res);
	}
	return(-1);
}




void checkAuth()
{
	char	area[80],
		namespace[80],
		group[80],
		*cp,
		*authHost;
	struct	stat sbuf;

	/*
	** First see if we need to do the authentication
	*/
	if (stat(".w3-auth",&sbuf) < 0)
	{
		return;
	}

	/*
	** Get everything we might need organised
	*/
	authHost = msqlGetCharConf("w3-msql","auth_host");
	sock = msqlConnect(authHost);
	if (sock < 0)
	{
		runError(msqlErrMsg);	
		exit(1);
	}
	if (msqlSelectDB(sock,"w3-msql") < 0)
	{
		runError(msqlErrMsg);	
		exit(1);
	}
	url = (char *)getenv("PATH_INFO");
	if (!url)
	{
		runError("Missing URL data");
		exit(1);
	}
	hostName = (char *)getenv("REMOTE_HOST");
	hostAddr = (char *)getenv("REMOTE_ADDR");
	if (!hostAddr)
	{
		runError("Missing Host Address data");
		exit(1);
	}

	/*
	** Make sure the hostname is in lowercase for matching
	*/
	if (hostName)
	{
		if (*hostName != 0)
		{
			hostName = (char *)strdup(hostName);
			cp = hostName;
			while(*cp)
			{
				*cp = tolower(*cp);
				cp++;
			}
		}
	}

	/*
	** Find the area for this URL.  If it isn't covered by an area
	** let the access continue
	*/
	if (findArea(area,namespace) < 0)
		return;

	/*
	** Check the access control info for this area
	*/

	strcpy(group,"** No Group **");
	if (checkAccess(area,namespace,group) < 0)
	{
		sendAuthHeader(area, "Bad Access Location",NULL);
		exit(0);
	}

	/*
	** We got a match.  Check out the access group info.  This only
	** returns if the user was OK.
	*/
	checkUser(group, area, namespace);
}
