#include <stdio.h>
#include <dbm.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include "Error.h"
#include "Parse.h"
#include "server.h"
#include "authorize.h"
#define TRUE 1
#define FALSE 0
#define EXACT 2


Trap setUsers(), setFile(),setHosts();
Keywords authorizeWords[] =
{
  { "groupfile",	setFile },
  { "users",		setUsers },
  { "hosts",		setHosts },

/*
  { "platforms",	setPlatforms },
  { "failuremsg",	setMsg },
*/
  { NULL,		NULL }
};
extern int debug;
static DBM *d;
char *filename;
char *user;
char *hostname;
char *hostaddr;
u_long   addr;
datum userData;
ParseBlock *testblock;
typedef struct _authinfo
{
  char *s;
  int size;
  int length;
} authinfo;

/*
 * ERRORS:
 *
 * APP_DBMOPEN: FATAL
 * Couldn't open database.
 */
Trap setFile(block, line)
     MasterBlock *block;
     char *line;
{
  int len;

  while (isspace(*line)) line++;
  len = strlen(line);

  if (len)
    {
      filename = (char *)malloc(len + 1);
      if (filename != NULL)
	strcpy(filename, line);
      else
	Return(MEMORY_ALLOC, len + 1);
    }
  else
    Return(APP_BADSTRING, NULL);

  if (0 != dbminit(filename))
    Return(APP_DBMOPEN, filename);

  d = dbm_open("hostauth", O_RDONLY, 0644); /* open the host groups db */
  if (!d)
    Return(APP_DBMOPEN, "hostauth");

  return OK;
}

Trap setUsers(block, line)
     MasterBlock *block;
     char *line;
{
  authinfo *info;

  if (Parse_GetCurrentData(block, USERAUTHINFO,
			   (caddr_t *)&info, sizeof(authinfo)))
    {
      if (Error_Severity == S_FATAL)
	return CHECK;
      else
	Error_Pop();
    }
  testblock = Parse_CurrentBlock(block);
  while (isspace(*line)) line++;

  if (*line != '\0')
    {
      if (info->size == 0)
	{
	  info->length = strlen(line);
	  info->size = info->length + 1;
	  info->s = (char *)malloc(info->size);
	  if (info->s == NULL)
	    Return(MEMORY_ALLOC, info->size);
	  strcpy(info->s, line);
	}
      else
	{
	  info->length += strlen(line) + 1; /* +1 for space */
	  info->size = info->length + 1;
	  info->s = (char *)realloc(info->size);
	  if (info->s == NULL)
	    Return(MEMORY_ALLOC, info->size);
	  strcat(info->s, " ");
	  strcat(info->s, line);
	}
    }

  return OK;
}
Trap setHosts(block, line)
     MasterBlock *block;
     char *line;
{
  authinfo *info;

  if (Parse_GetCurrentData(block, HOSTAUTHINFO,
			   (caddr_t *)&info, sizeof(authinfo)))
    {
      if (Error_Severity == S_FATAL)
	return CHECK;
      else
	Error_Pop();
    }

  while (isspace(*line)) line++;

  if (*line != '\0')
    {
      if (info->size == 0)
	{
	  info->length = strlen(line);
	  info->size = info->length + 1;
	  info->s = (char *)malloc(info->size);
	  if (info->s == NULL)
	    Return(MEMORY_ALLOC, info->size);
	  strcpy(info->s, line);
	}
      else
	{
	  info->length += strlen(line) + 1; /* +1 for space */
	  info->size = info->length + 1;
	  info->s = (char *)realloc(info->size);
	  if (info->s == NULL)
	    Return(MEMORY_ALLOC, info->size);
	  strcat(info->s, " ");
	  strcat(info->s, line);
	}
    }

  return OK;
}
      
Trap Authorize_LoadInfo(what)
     Adr *what;
{
  datum key;
  struct in_addr add;

  user = what->user;
  hostname = what->hostname;
  addr = what->ip;
  add.s_addr = what->ip;
  hostaddr = inet_ntoa(add);
  printf("add = %s\n",hostaddr);
  key.dptr = what->user;
  key.dsize = strlen(what->user) + 1;
  userData = fetch(key);

  return OK;
}

int checkList(what, where)
     char *what, *where;
{
  int len,rc;
  char *cp,teststr[30];
  cp = index(what,' ');
  if (cp)
    len = cp - what;
  else
  len = strlen(what);
  strncpy(teststr,what,len);
  printf("teststr = <%s>, len = %d\n",teststr,len);
  if (debug)
    printf("in checklist\n");
  while (1)
    {
      while (isspace(*where)) where++;
      rc = 1;
      if (*where == '-') 
	{
	  rc = -1; /* negative acl */
	  where++;
	}
      if ((!strncasecmp(what, where, len)) &&
	(where[len] == '\0' || where[len] == ' ')) /* no partial matches */
	return rc;
      if (debug)
	printf("what = <%s>, where = <%s>\n",what,where);
      while (!isspace(*where) && *where != '\0') where++;
      if (*where == '\0')
	return 0;
    }
}

/* for now an "*" means match any number of tokens and a "$" means match one */
 /* assumes the matched part of the host & wildexpr have been removed */
/* can change logic so that if wild expr has a "*" return true otherwise the
token counds must be == */

int right_num_tokens(char *host, char *wildexpr)
{ 
char *cp;   
int hdots = 0, xdots = 0, xlen;

xlen = strlen(wildexpr);
if ((xlen == 1) && (*wildexpr == '*'))  /* single * matches anything */
  return TRUE;
cp = host;
while (cp = index(cp,'.'))	         /* count dots in hostname */
	  hdots++,cp++;
cp = wildexpr;
while (cp =  index(cp,'.'))	 /* count dots in wildexpr */
	  xdots++,cp++;
if (debug)
  printf("before case hdots = %d, xdots = %d\n",hdots,xdots);
switch (hdots) 
  {
  case 0:          /* either & or * will match 1 token */
    if (xlen == 1)
      return TRUE;
    else
      return FALSE;
  break;

  case 1:
    if (xdots == 1)
      return TRUE;
    else
      return FALSE;
  break;

  case 2:
    if (xdots == 2)
      return TRUE;
    else if (strchr(wildexpr,'*') && (xdots < 2))
      return TRUE;
    else 
      return FALSE;
  break;

  case 3:
    if (xdots == 3)
      return TRUE;
    else if (strchr(wildexpr,'*') && (xdots < 3))
      return TRUE;
    else 
      return FALSE;
  break;

  default:
    return FALSE; /* error , or maybe we have to do 1 more num */
  break;
  }

}

int hostMatches(char *host,char *realaddr, char *hostexr)
{        /* we are altering the strings here, either make copys to work with
	    or do it before calling */

char  *cp, *wildpart, *explpart,hostexpr[50],realhost[50];
int  len;

strcpy(hostexpr,hostexr);
strcpy(realhost,host);
cp = index(hostexpr,' ');  /* null terminate after 1st token */
if (cp)
  *cp = '\0';

if (debug)
  printf("checking hostexpr = <%s>, realhost = <%s>\n",hostexpr,realhost);
cp = hostexpr;
if (isdigit((int) *cp))  /* ip addr */
  {
    cp = hostexpr + strlen(hostexpr) - 1;
     if (*cp == '*' || *cp == '$')       /* wild card */
      {
	while ((cp >= hostexpr) && (*cp == '*' || *cp == '$' 
	     || *cp == '.' )) cp--;    /* move cp to something explicit */
	 cp++;                         /* move to dot */
	if ((cp) && (*cp == '.')) {    /* break into wildpart, explpart */
	  *cp = '\0';
	  cp++;
	  explpart = hostexpr;
	  wildpart = cp;
	}   
	else
	  return FALSE; 	/* bad expression */

	cp = strstr(realaddr,explpart); /* see if the expicit part matches
					   last part of real name */
if (debug)
  printf("explpart = <%s>\n",explpart);
	if (!cp)
	  return FALSE; 
	cp = realaddr + strlen(explpart);
/* need to find the correct place to break */ 

	*cp = '\0'; /* chop off the part of realaddr already matched */
	cp++;
	/* count tokens in realaddr not already matched */
if (debug)
  printf("realleft = <%s>, wildpart = <%s>\n",cp,wildpart);
	if (right_num_tokens(cp,wildpart))
	  return TRUE;
	else 
	  return FALSE;
      }
    else                       /* exact match */
      {
	if (!strcasecmp(realhost,hostexpr))
	  return TRUE;
	else
	  return FALSE;
      }
  }
else             /* hostname */
  {
    if (strcmp(hostname,"") == 0)
      return FALSE;
    if (*cp == '*' || *cp == '$')         /* wild card */
      {
	while (cp && (*cp == '*' || *cp == '$' 
	     || *cp == '.')) cp++;     /* move cp to something explicit */
	if (cp != hostexpr)             /* we moved */
	  cp--; 	                /* now move back to the dot */
	if (*cp == '.') { 	        /* break into wildpart, explpart */
	  *cp = '\0';
	  cp++;
	  explpart = cp;
	  wildpart = hostexpr;
	}  
	else
	  {
	    printf("cp = <%s>\n",cp);
	    return FALSE;  	/* bad expression */
	  }
	  
	cp = strstr(realhost,explpart); /* see if the expicit part matches
					   last part of real name */
if (debug)
  printf("explpart = <%s>\n",explpart);
	if (!cp)
	  return FALSE;
	cp--;
	if (*cp != '.')
	  return FALSE;  	/* bad expression */	  
	*cp = '\0';        /* chop off the part of realhost already matched */
	                   /* count tokens in realhost not already matched */
if (debug)
  printf("realleft = <%s>, wildpart = <%s>\n",realhost,wildpart);
	if (right_num_tokens(realhost,wildpart))
	  return TRUE;
	else 
	  return FALSE;
      }
    else                       /* exact match */
      {
	cp = index(hostexpr,'.');
	if (!cp)                  /* tack on mit.edu  */
	  {
	    strcat(hostexpr,DEFAULT_DOMAIN);
	  }
	if (!strcasecmp(realhost,hostexpr))
	  return EXACT;
	else
	  return FALSE;
      }
  }
}

static loop_cnt = 0;

/* expanding groups in groups here might start to become time consuming if
   the nesting of groups becomes large. If this happens we probably will need 
   to go back and expand the groups completely before loading in the db. This
   is a little nasty if we continue to allow exceptions to groups */

int in_Group(char *group) /* see if host is in group -- recursive */
{
char *cp,thegroup[30];
int cnt = 0, except = FALSE, rc = FALSE;
datum key, group_rec;

loop_cnt++;
/* if loop_cnt > 10 return error & set loop_cnt back to 0 */
cp = group;
while (!isspace(*cp) && (*cp != '\0'))
  cnt++,  cp++; 

strncpy(thegroup,group + 1,--cnt); 
key.dptr = thegroup;
key.dsize = strlen(key.dptr) + 1; /* null char too */
if (debug)
  printf("key = <%s>, size = %d\n",key.dptr,key.dsize);
group_rec = dbm_fetch(d,key);                  /* get group line from db */
if (!group_rec.dptr)           /* group not found  - XXX should be error */
  goto LEAVE;
if (debug)
  printf("the group <%s> = <%s>\n",key.dptr,group_rec.dptr);

/* for each token in group 
   see if its an exception
   then do a hostmatch */

cp = group_rec.dptr;
while (*cp != '\0') {
  while (isspace(*cp)) cp++;
  if (*cp == '-')
    {
      except = TRUE;
      cp++;
    }
  else
    except = FALSE;
  if (*cp == '#')  /* its a group */
    {
      if (rc = in_Group(cp))
	goto LEAVE;
    }
  else           /* not a group */
    {
      if (rc =hostMatches(hostname,hostaddr,cp))
	goto LEAVE;
    }
  while (!isspace(*cp) && (*cp != '\0')) cp++;
}
 LEAVE:
 loop_cnt--;

if (debug)
  printf("loop_cnt = %d\n",loop_cnt);
if (rc && (!except))
    return TRUE;
return FALSE;
}

int userAuthorized(mb, pb)
     MasterBlock *mb;
     ParseBlock *pb;
{

  authinfo *info;
  char *ptr,*cp;
  int rc, negacl;


  Parse_SetCurrentBlock(mb, pb);

  if (Parse_GetCurrentData(mb, USERAUTHINFO, (caddr_t *)&info, 0))
    return 1; /* XXX think a little more before giving access? */

  rc = checkList(user, info->s);
  if (rc == -1)  return FALSE; /* found a specific negative acl */
  if (rc == 1)   return TRUE; /* found a specific positive acl */

  ptr = userData.dptr;
  if (ptr == NULL)
    return FALSE;

  cp = info->s;
  while (*cp != '\0') {
    while (isspace(*cp)) cp++;
    if (*cp == '-') {
      cp++;
      negacl = TRUE;
    }
    else
      negacl = FALSE;
    if (rc = checkList(cp,userData.dptr))
      {
	if (negacl)
	  return FALSE;
	else
	  return TRUE;
      }   
     while (!isspace(*cp) && *cp != '\0') cp++;
  }
  return FALSE;
}

int hostAuthorized(mb, pb)
     MasterBlock *mb;
     ParseBlock *pb;
{
int gotresult = 0,rc,negative = FALSE, positive = FALSE,negacl;
authinfo *info;
char *cp,aclline[1024];
 
Parse_SetCurrentBlock(mb, pb);
if (Parse_GetCurrentData(mb, HOSTAUTHINFO, (caddr_t *)&info, 0))
  return 1; /* XXX think a little more before giving access? */

bcopy(info->s,aclline,info->size);
cp = aclline;

while (*cp != '\0') {
  while (isspace(*cp)) cp++;
  if (*cp == '-')
    {
      negacl = TRUE;
      cp++;
    }
  else
    negacl = FALSE;

  if (*cp != '#')   /* if not a group */
    {
      if (rc = hostMatches(hostname,hostaddr,cp))
	{
/* if explicit return immediately else wait until all tokens are parsed */
	  if ((negacl) && (rc == EXACT))
	    return FALSE; 
	  else if ((negacl) && (!gotresult))
	    negative = TRUE, gotresult++; 
	  else if (rc == EXACT)
	    return TRUE;
	  else if (!gotresult)
	    positive = TRUE, gotresult++;
	}
    }
   if ((*cp == '#') && (!gotresult))  /* if a group */
    {
      if (rc = in_Group(cp)) /* in group */
	{
	  if ((negacl == TRUE) && (!gotresult))
	    negative = TRUE,gotresult++;
	  else if (!gotresult)
	    positive = TRUE,gotresult++;
	}
    }
  while (!isspace(*cp) && (*cp != '\0')) cp++;
}

if (negative)
  return FALSE;
return positive;
}

/*
 * ERRORS:
 *
 * APP_USERAUTH: INFO
 * User authorization failed.
 *
 * APP_HOSTAUTH: INFO
 * Host authorization failed.
 */
Trap Authorized(mb, pb)
     MasterBlock *mb;
     ParseBlock *pb;
{

if (!userAuthorized(mb,pb))
  Return(APP_USERAUTH,NULL);

if (!hostAuthorized(mb,pb))
  Return(APP_HOSTAUTH,NULL);

return OK;
}
/********************************************************************/
/*   PRECEDENCE ETC  ----

     An explicit reference to a particular user or host on an acl line
     has the highest precendence. After that it is strictly RIGHT to LEFT.
     The same hold true for memberships in groups.

*/
/*******************************************************************/
