/*
 * http_auth_ip: authentication by ip address
 *
 * #include <APACHE_LICENSE>
 * @(#) Tullio Andreatta,  2000
 * http://freepage.logicom.it/ta/
 *
 * Thanks to Logicom S.r.l. http://www.logicom.it/
 */

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"

typedef struct auth_ip_struct {
    char *user;			/* Username assigned on match */
    struct in_addr network;	/* Network */
    struct in_addr netmask;	/* Netmask */
    int  check_method;
#	define	IP_MATCH	0
#	define	IP_NOMATCH	1
#	define	IP_RANGE	2
#	define	IP_NOTRANGE	3

} auth_ip_rec ;

typedef struct auth_ip_config_struct {
    array_header *auth_ip;
} auth_ip_config_rec;

static void *create_auth_ip_dir_config(pool *p, char *d)
{
    auth_ip_config_rec *sec =
    (auth_ip_config_rec *) ap_pcalloc(p, sizeof(auth_ip_config_rec));

    sec->auth_ip = ap_make_array(p, 4, sizeof(auth_ip_rec));
    return sec;
}

/*
 * convert_string_to_network:
 *	convert a string argument to a network/netmask pair.
 *	Allowed formats for network argument are:
 *	  212.38.32.31              Single IP (= 212.38.32.31/32)
 *	  212.38.32.0/22            Network (22 bits netmask)
 *	  212.38.32.0/255.255.254.0 Network (23 bits netmask)
 *        212.38.32                 Network (24 bits netmask)
 *        212.38.                   Network (16 bits netmask)
 *        212.38.32.4/.252          Network (30 bits netmask)
 *        212.38.32.8-212.38.32.11  IP range
 *	  !212.38.32.31             Reverse IP/Network/Range
 */
static char *convert_string_to_network(char *str, auth_ip_rec *net)
{
    int a, b, c, d;

    if (*str == '!')
    {
	net->check_method = IP_NOMATCH;
	str++;
    }
    else
    {
	net->check_method = IP_MATCH;
    }

    a = b = c = d = -1;

    while (*str == ' ' || *str == '\t' || *str == '\n')
    {
	str++;
    }
    
    while (*str >= '0' && *str <= '9')
    {
	if (a <= 0) a = *str - '0';
	else        a = 10 * a + (*str - '0');
	str++;
    }

    if (*str == '.')
    {
	str++;

	while (*str >= '0' && *str <= '9')
	{
	    if (b <= 0) b = *str - '0';
	    else        b = 10 * b + (*str - '0');
	    str++;
	}
    }

    if (*str == '.')
    {
	str++;

	while (*str >= '0' && *str <= '9')
	{
	    if (c <= 0) c = *str - '0';
	    else        c = 10 * c + (*str - '0');
	    str++;
	}
    }

    if (*str == '.')
    {
	str++;

	d = 0;
	while (*str >= '0' && *str <= '9')
	{
	    if (d <= 0) d = *str - '0';
	    else        d = 10 * d + (*str - '0');
	    str++;
	}
    }

    while (d > 255)
    {
	d >>= 8;
    }

    while (c > 255)
    {
	d = c & 255;
	c >>= 8;
    }

    while (b > 255)
    {
	d = c;
	c = b & 255;
	b >>= 8;
    }

    while (a > 255)
    {
	d = c;
	c = b;
	b = a & 255;
	a >>= 8;
    }

    if (d < 0) if (c < 0) if (b < 0) if (a < 0)
	net->netmask.s_addr = htonl(0x00000000);
    else
	net->netmask.s_addr = htonl(0xFF000000);
    else
	net->netmask.s_addr = htonl(0xFFFF0000);
    else
	net->netmask.s_addr = htonl(0xFFFFFF00);
    else
	net->netmask.s_addr = htonl(0xFFFFFFFF);
    
    if (a < 0) a = 0;
    if (b < 0) b = 0;
    if (c < 0) c = 0;
    if (d < 0) d = 0;
    net->network.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | (d));

    /* Check for IP range */
    if (*str == '-')
    {
	str++;
	net->check_method = net->check_method == IP_MATCH
	    ? IP_RANGE : IP_NOTRANGE ;

	a = b = c = d = 0;

	while (*str == ' ' || *str == '\t' || *str == '\n')
	{
	    str++;
	}
	
	while (*str >= '0' && *str <= '9')
	{
	    a = 10 * a + (*str - '0');
	    str++;
	}

	if (*str == '.')
	{
	    str++;

	    while (*str >= '0' && *str <= '9')
	    {
		b = 10 * b + (*str - '0');
		str++;
	    }
	}

	if (*str == '.')
	{
	    str++;

	    while (*str >= '0' && *str <= '9')
	    {
		c = 10 * c + (*str - '0');
		str++;
	    }
	}

	if (*str == '.')
	{
	    str++;

	    d = 0;
	    while (*str >= '0' && *str <= '9')
	    {
		d = 10 * d + (*str - '0');
		str++;
	    }
	}

	while (d > 255)
	{
	    d >>= 8;
	}

	while (c > 255)
	{
	    d = c & 255;
	    c >>= 8;
	}

	while (b > 255)
	{
	    d = c;
	    c = b & 255;
	    b >>= 8;
	}

	while (a > 255)
	{
	    d = c;
	    c = b;
	    b = a & 255;
	    a >>= 8;
	}

	net->netmask.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | (d));

	return *str ? str : NULL;
    }

    /* Check for network bits or netmask */
    if (*str != '/')
	return (*str ? str : NULL);

    str++;
    if (*str == '.')	/* AAA.BBB.CCC.DDD/.NNN[.NNN[.NNN]] */
    {
	str++;

	a = 255;
	b = 255;
	c = 255;
	d = 0;

	while (*str >= '0' && *str <= '9')
	{
	    d = 10 * d + (*str - '0');
	    str++;
	}

	if (*str == '.')
	{
	    str++;

	    a = b;
	    b = c;
	    c = d;
	    d = 0;

	    while (*str >= '0' && *str <= '9')
	    {
		d = 10 * d + (*str - '0');
		str++;
	    }
	}

	if (*str == '.')
	{
	    str++;

	    a = b;
	    b = c;
	    c = d;
	    d = 0;

	    while (*str >= '0' && *str <= '9')
	    {
		d = 10 * d + (*str - '0');
		str++;
	    }
	}
    }
    else if (*str >= '0' && *str <= '9')
    {
	a = *str - '0';
	b = 0;
	c = 0;
	d = 0;

	str++;
	while (*str >= '0' && *str <= '9')
	{
	    a = 10 * a + (*str - '0');
	    str++;
	}

	if (*str == '.')	/* AAA.BBB.CCC.DDD/AAA.BBB.CCC.DDD */
	{
	    str++;
	    while (*str >= '0' && *str <= '9')
	    {
		b = 10 * b + (*str - '0');
		str++;
	    }

	    if (*str == '.')
	    {
		str++;

		while (*str >= '0' && *str <= '9')
		{
		    c = 10 * c + (*str - '0');
		    str++;
		}

		if (*str == '.')
		{
		    str++;

		    while (*str >= '0' && *str <= '9')
		    {
			d = 10 * d + (*str - '0');
			str++;
		    }
		}
	    }
	}
	else if (a == 32)
	{
	    a = b = c = d = 255;
	}
	else if (a < 32)	/* AAA.BBB.CCC.DDD/BITS */
	{
	    a = 0xffffffff << (32 - a);
	    d = a & 255; a >>= 8;
	    c = a & 255; a >>= 8;
	    b = a & 255; a >>= 8;
	}
	else			/* AAA.BBB.CCC.DDD/NETWORK */
	{
	    d = a & 255; a >>= 8;
	    c = a & 255; a >>= 8;
	    b = a & 255; a >>= 8;
	}
    }

    while (d > 255)
    {
	d >>= 8;
    }

    while (c > 255)
    {
	d = c & 255;
	c >>= 8;
    }

    while (b > 255)
    {
	d = c;
	c = b & 255;
	b >>= 8;
    }

    while (a > 255)
    {
	d = c;
	c = b;
	b = a & 255;
	a >>= 8;
    }

    net->netmask.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | (d));
    net->network.s_addr = net->network.s_addr & net->netmask.s_addr;

    return (*str ? str : NULL);
}

static const char *add_authenticated_ip(cmd_parms *cmd, void *m,
	char *user, char *network)
{
    auth_ip_config_rec *mcfg = (auth_ip_config_rec *) m;
    auth_ip_rec *add;
    char *ret;

    add = (auth_ip_rec *) ap_push_array(mcfg->auth_ip);
    add->user = ap_pstrdup(cmd->pool, user);
    if ((ret = convert_string_to_network(network, add)) != NULL)
    {
	return ap_pstrcat(cmd->pool, "Invalid argument '", network,
	    "' on AuthenticateIP ", user, " (at '", ret, "')", NULL);
    }
    return NULL;
}

static const command_rec auth_ip_cmds[] =
{
    {"AuthenticateIP", add_authenticated_ip, NULL, OR_AUTHCFG, ITERATE2,
     "username followed by one o more networks (IP, IP/bits or IP/.mask)"},

    {NULL}
};

module MODULE_VAR_EXPORT auth_ip_module;

static int ip_check(struct in_addr *remote, auth_ip_rec *match)
{
    if (match->check_method == IP_MATCH)
	return ((remote->s_addr & match->netmask.s_addr) == match->network.s_addr);

    if (match->check_method == IP_NOMATCH)
	return ((remote->s_addr & match->netmask.s_addr) != match->network.s_addr);

    if (match->check_method == IP_RANGE)
	return ((unsigned long) ntohl(remote->s_addr)
		>= (unsigned long) ntohl(match->netmask.s_addr)
	&&  (unsigned long) ntohl(remote->s_addr)
		<= (unsigned long) ntohl(match->network.s_addr));

    if (match->check_method == IP_NOTRANGE)
	return ((unsigned long) ntohl(remote->s_addr)
		< (unsigned long) ntohl(match->netmask.s_addr)
	||  (unsigned long) ntohl(remote->s_addr)
		> (unsigned long) ntohl(match->network.s_addr));
    
    return 0;	/* Method not supported */
}

static int authenticate_ip_user(request_rec *r)
{
    auth_ip_config_rec *sec =
    (auth_ip_config_rec *) ap_get_module_config(r->per_dir_config, &auth_ip_module);
    const char *sent_pw;
    auth_ip_rec *ip = (auth_ip_rec *) sec->auth_ip->elts;
    int i;

    if (!sec->auth_ip->nelts) return DECLINED; /* we're not configured */

    for (i = 0; i < sec->auth_ip->nelts; i++)
    {
	if (ip_check(&(r->connection->remote_addr.sin_addr), &ip[i]))
	{
	    r->connection->user = ap_pstrdup(r->connection->pool, ip[i].user);
	    return OK;	/* IP is my authentication */
	}
    }

    return DECLINED;	/* switch to default authentication */
}

module MODULE_VAR_EXPORT auth_ip_module =
{
    STANDARD_MODULE_STUFF,
    NULL,			/* initializer */
    create_auth_ip_dir_config,	/* dir config creater */
    NULL,			/* dir merger --- default is to override */
    NULL,			/* server config */
    NULL,			/* merge server config */
    auth_ip_cmds,		/* command table */
    NULL,			/* handlers */
    NULL,			/* filename translation */
    authenticate_ip_user,	/* check_user_id */
    NULL,			/* check auth */
    NULL,			/* check access */
    NULL,			/* type_checker */
    NULL,			/* fixups */
    NULL,			/* logger */
    NULL,			/* header parser */
    NULL,			/* child_init */
    NULL,			/* child_exit */
    NULL			/* post read-request */
};

