/*
 *      @(#) hostacc.c  -       Implementation of host access for the
 *                              experimental FTP daemon developed at
 *                              Washington University.
 *  $Id: hostacc.c,v 1.17 1999/02/26 16:08:50 sob RELEASE sob $
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * AUTHOR
 *      Bart Muijzer    <bartm@cv.ruu.nl>
 *
 * HISTORY
 *      930316  BM      Created
 *      930317  BM      Converted to local naming convention;
 *                      added rhost_ok(), cleanup code in enghacc()
 *      930318  BM      Ported to BSD; fixed memory leaks
 *      930322  BM      Changed algorithm: not in configfile =  allow
 *                                         in configfile and match = allow|deny
 *                                         in configfile and no match = deny
 *
 */

#include "config.h"

#ifdef  HOST_ACCESS

#include "hostacc.h"

static  char    linbuf[MAXLEN];  /* Buffer to hold one line of config-file  */
static  char    unibuf[MAXLEN];  /* Buffer to hold unified line             */
static  hacc_t  *ha_arr; 	 /* Array with host access information      */

static  FILE    *ptFp;           /* FILE * into host access config file     */
static  int     iHaInd = 0;      /* Index in ha_arr                         */
static  int     iHaSize;         /* Will hold actual #elems in ha_arr       */
static  int     iFirstTim = 1;   /* Used by gethacc() to see if index in    */
                                 /* ha_arr needs to be reset                */

/* ------------------------------------------------------------------------ *\
 * FUNCTION  : rhost_ok                                                     *
 * PURPOSE   : Check if a host is allowed to make a connection              *
 * ARGUMENTS : Remote user name, remote host name, remote host address      *
 * RETURNS   : 1 if host is granted access, 0 if not                        *
\* ------------------------------------------------------------------------ */

int     rhost_ok(pcRuser, pcRhost, pcRaddr)
char    *pcRuser,
        *pcRhost,
        *pcRaddr;
{
        hacc_t  *ptHtmp;
        char    *pcHost;
        char    *ha_login;
        int     iInd, iLineMatch = 0, iUserSeen = 0;

        switch(sethacc()){
        case 1:
                /* no hostaccess file; disable mechanism */
                return(1);
                break;
        case -1:
                syslog(LOG_INFO, "rhost_ok: sethacc failed");
                endhacc();
                return(0);
                break;
        default:
                break;
        }

        /* user names "ftp" and "anonymous" are equivalent */
        if (!strcasecmp(pcRuser, "anonymous"))
                pcRuser = "ftp";

        while (((ptHtmp = gethacc()) != (hacc_t *)NULL) && !iLineMatch)
        {
                if (strcasecmp(ptHtmp->ha_login, "anonymous"))
                        ha_login = ptHtmp->ha_login;
                else
                        ha_login = "ftp";

                if ((strcasecmp(pcRuser, ha_login)) && strcmp(ha_login, "*"))
                        /* wrong user, check rest of file */
                        continue;

                /*
                 * We have seen a line regarding the current user.
                 * Remember this.
                 */
                iUserSeen = 1;

                for(iInd=0, pcHost=ptHtmp->ha_hosts[0];
                    ((pcHost != NULL) && !iLineMatch);
                    pcHost=ptHtmp->ha_hosts[++iInd])
                {
                        if (isdigit(*pcHost))
                        {
                                iLineMatch = !fnmatch(pcHost, pcRaddr, NULL);
                        }
                        else
                        {
                                iLineMatch = !fnmatch(pcHost, pcRhost, NULL);
                        }
                        if (iLineMatch)
                        {
                                iLineMatch = (ptHtmp->ha_type == ALLOW) ? 1 : 0;
                                goto match;
                        }
                }
        }

match:
        /*
         * At this point, iUserSeen == 1 if we've seen lines regarding
         * the current user, and 0 otherwise. If we reached the end of
         * the config file without a match we allow. Else, we allow or
         * deny according to the rule found.
         */

        if (endhacc())
        {
                syslog(LOG_INFO, "rhost_ok: endhacc failed");
                return(0);
        }

        if (iUserSeen)
                return(ptHtmp == NULL) ? 0 : iLineMatch;
        else
                /* Nothing at all about user in configfile, allow */
                return(1);
}

/* ------------------------------------------------------------------------ *\
 * FUNCTION  : sethacc                                                      *
 * PURPOSE   : Initialize data structures for host access                   *
 * ARGUMENTS : None                                                         *
 * RETURNS   : -1 on failure, 1 if host access file doesn't exist,          *
 *             0 otherwise                                                  *
\* ------------------------------------------------------------------------ */

static  int     sethacc()
{
        int     iHaHind = 0;                    /* Index in list of hosts   */
        char    *pcBegin, *pcEnd, *pcColon;
        char    *pcTmp1, *pcTmp2;
	int     iHaMalloc=0;			/* how many elem malloced */

        iHaInd = 0;
        iFirstTim = 1;
        /* Open config file */
        if ((ptFp = fopen(_PATH_FTPHOSTS, "r")) == NULL)
        {
                if (errno == ENOENT)
                        return(1);
                else {
                        fatal("Can't open host access file");
                        iHaSize = iHaInd;
                        return (-1);
                }
        }
        ha_arr=(hacc_t*)malloc((iHaMalloc=10)*sizeof(hacc_t));

        while (fgets(linbuf, MAXLEN, ptFp) != NULL)
        {
                iHaHind = 0;

                /* Find first non-whitespace character */
                for (pcBegin=linbuf;
                     ((*pcBegin == '\t') || (*pcBegin == ' '));
                     pcBegin++)
                        ;
                
                /* Get rid of comments */
                if ((pcEnd = strchr(linbuf, '#')) != NULL)
                        *pcEnd = '\0';
                

                /* Skip empty lines */
                if ((pcBegin == pcEnd) || (*pcBegin == '\n'))
                        continue;
                
                /* Substitute all whitespace by a single ":" so we can
                 * easily break on words later on. The easiest way is 
                 * to copy the result into a temporary buffer (called
                 * the "unified buffer" because it will store a line in 
                 * the same format, regardless of the format the original
                 * line was in).
                 * The result will look like: "allow:name:host:host:host"
                 */
                for (pcTmp1=pcBegin, pcTmp2=unibuf; *pcTmp1; pcTmp1++)
                {
                        if (*pcTmp1 != '\t' && *pcTmp1 != ' ' && *pcTmp1 != '\n')
                                *pcTmp2++ = *pcTmp1;
                        else
                                /* whitespace */
                                if (*(pcTmp2-1) == ':')
                                        continue;
                                else
                                        *pcTmp2++ = ':';
                }

                /* Throw away trailing whitespace, now indicated by
                 * the last character of the unified buffer being a 
                 * colon. Remember where the news string ends.
                 */
                pcEnd = (*(pcTmp2 - 1) == ':') ? (pcTmp2 - 1) : pcTmp2;
                *pcEnd = '\0';          /* Terminate new string */

                /*
                 * Check if we need to expand the array with
                 * host access information
                 */
                if (iHaInd >= iHaMalloc)
                {
                        ha_arr=(hacc_t*)realloc(ha_arr,(iHaMalloc+=10)*sizeof(hacc_t));
                        if (!ha_arr)
                        {
                                fatal("Failed to realloc host access array");
                                iHaSize = iHaInd;
                                return(-1);
                        }
                }

                /* Store what's left of the line into the
                 * hacc_t structure. First the access type,
                 * then the loginname, and finally a list of
                 * hosts to which all this applies.
                 */
                pcBegin = unibuf;
                if (!strncmp(pcBegin, "deny", 4))
                {
                        ha_arr[iHaInd].ha_type = DENY;
                        pcBegin += 5;
                } else 
                        if (!strncmp(pcBegin, "allow", 5))
                        {
                                ha_arr[iHaInd].ha_type = ALLOW;
                                pcBegin += 6;
                        }
                        else {
                                fatal("Format error in host access file");
                                iHaSize = iHaInd;
                                return(-1);
                        }

                if((pcColon = strchr(pcBegin, ':')) != NULL)
                        ha_arr[iHaInd].ha_login =
                                strnsav(pcBegin, (pcColon-pcBegin));
                else
                {
                        fatal("Format error in host access file");
                        iHaSize = iHaInd;
                        return(-1);
                }

                pcBegin = pcColon+1;
                while ((pcColon = strchr(pcBegin, ':')) != NULL)
                {
                        ha_arr[iHaInd].ha_hosts[iHaHind++] =
                                strnsav(pcBegin, (pcColon-pcBegin));
                        pcBegin = pcColon+1;
                        if (iHaHind >= MAXHST)
                        {
                                fatal("Line too long");
                                iHaSize = iHaInd;
                                return(-1);
                        }
                }
                ha_arr[iHaInd].ha_hosts[iHaHind++] =
                                strnsav(pcBegin, (pcEnd-pcBegin));
                ha_arr[iHaInd].ha_hosts[iHaHind] = NULL;
                iHaInd++;
        }
        iHaSize = iHaInd;               /* Record current size of ha_arr */
        return ((feof(ptFp)) ? 0 : -1);
}

/* ------------------------------------------------------------------------ *\
 * FUNCTION  : gethacc                                                      *
 * PURPOSE   : return pointer to the next host_access structure             *
 * ARGUMENTS : None                                                         *
 * RETURNS   : NULL on failure, pointervalue otherwise                      *
\* ------------------------------------------------------------------------ */

static  hacc_t  *gethacc()
{
        static  int     iHaInd;
        static  hacc_t  ptTmp;

        if (iFirstTim)
        {
                iFirstTim = 0;
                iHaInd = 0;
        }
        if (iHaInd >= iHaSize)
                return ((hacc_t *)NULL);
        else {
#ifdef USG
                memmove(&ptTmp, &(ha_arr[iHaInd]), sizeof(hacc_t));
#else
                bcopy(&(ha_arr[iHaInd]), &ptTmp, sizeof(hacc_t));
#endif
                iHaInd++;
                return(&ptTmp);
        }
}

/* ------------------------------------------------------------------------ *\
 * FUNCTION  : endhacc                                                      *
 * PURPOSE   : Free allocated data structures for host access               *
 * ARGUMENTS : None                                                         *
 * RETURNS   : -1 on failure, 0 otherwise                                   *
\* ------------------------------------------------------------------------ */

static  int     endhacc()
{
        int     iInd;
        hacc_t  *ptHtmp;

	if (ha_arr == (hacc_t*)NULL)
		return(0);

	for (ptHtmp = ha_arr;
	     ptHtmp < ha_arr + iHaSize && ptHtmp->ha_type;
	     ptHtmp++)
	{
		ptHtmp->ha_type = 0;
		if (ptHtmp->ha_login) {
			free(ptHtmp->ha_login);
			ptHtmp->ha_login = NULL;
		}
		for(iInd=0;
		    iInd < MAXHST && ptHtmp->ha_hosts[iInd];
		    iInd++) {
			free(ptHtmp->ha_hosts[iInd]);
			ptHtmp->ha_hosts[iInd] = NULL;
		}
	}
	free(ha_arr);
	ha_arr = NULL;

        if (ptFp && fclose(ptFp))
                return (-1);
        return (0);
}

/* ------------------------------------------------------------------------ */

static  void    fatal(pcMsg)
char    *pcMsg;
{
        syslog(LOG_INFO, "host_access: %s", pcMsg);
}

static  char    *strnsav(pcStr,iLen)
char    *pcStr;
int     iLen;
{
        char    *pcBuf;

        if ((pcBuf = (char *)malloc(iLen+1)) == NULL)
                return(NULL);
        strncpy(pcBuf,pcStr,iLen);
        pcBuf[iLen] = '\0';
        return(pcBuf);
}

#endif  /* HOST_ACCESS */
