/*
 * rlm_realm.c
 *
 * Version:	$Id: rlm_realm.c,v 1.50 2004/05/17 09:52:05 aland Exp $
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Copyright 2000  The FreeRADIUS server project
 * FIXME add copyrights
 */

#include "autoconf.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#include "libradius.h"
#include "radiusd.h"
#include "modules.h"

static const char rcsid[] = "$Id: rlm_realm.c,v 1.50 2004/05/17 09:52:05 aland Exp $";

#define  REALM_FORMAT_PREFIX   0
#define  REALM_FORMAT_SUFFIX   1

typedef struct realm_config_t {
        int        format;
        char       *formatstring;
        char       *delim;
	int        ignore_default;
	int        ignore_null;
} realm_config_t;

static CONF_PARSER module_config[] = {
  { "format", PW_TYPE_STRING_PTR,
    offsetof(realm_config_t,formatstring), NULL, "suffix" },
  { "delimiter", PW_TYPE_STRING_PTR,
    offsetof(realm_config_t,delim), NULL, "@" },
  { "ignore_default", PW_TYPE_BOOLEAN,
    offsetof(realm_config_t,ignore_default), NULL, "no" },
  { "ignore_null", PW_TYPE_BOOLEAN,
    offsetof(realm_config_t,ignore_null), NULL, "no" },
  { NULL, -1, 0, NULL, NULL }    /* end the list */
};

/*
 *	Internal function to cut down on duplicated code.
 *
 *	Returns -1 on failure, 0 on no failure.  returnrealm
 *	is NULL on don't proxy, realm otherwise.
 */
static int check_for_realm(void *instance, REQUEST *request, REALM **returnrealm)
{
	char namebuf[MAX_STRING_LEN];
	char *username;
	char *realmname = NULL;
	char *ptr;
	VALUE_PAIR *vp;
	REALM *realm;

        struct realm_config_t *inst = instance;

	/* initiate returnrealm */
	*returnrealm = NULL;

	/*
	 *	If the request has a proxy entry, then it's a proxy
	 *	reply, and we're walking through the module list again.
	 *
	 *	In that case, don't bother trying to proxy the request
	 *	again.
	 *
	 *	Also, if there's no User-Name attribute, we can't
	 *	proxy it, either.
	 */
	if ((request->proxy != NULL) ||
	    (request->username == NULL)) {
		DEBUG2("    rlm_realm: Proxy reply, or no User-Name.  Ignoring.");
		return 0;
	}

	/*
	 *      Check for 'Realm' attribute.  If it exists, then we've proxied
	 *      it already ( via another rlm_realm instance ) and should return.
	 */

	if ( (vp = pairfind(request->packet->vps, PW_REALM)) != NULL ) {
	        DEBUG2("    rlm_realm: Request already proxied.  Ignoring.");
		return 0;
	}

	/*
	 *	We will be modifing this later, so we want our own copy
	 *	of it.
	 */
	strNcpy(namebuf, (char *)request->username->strvalue, sizeof(namebuf));
	username = namebuf;

	switch(inst->format)
	{

	case REALM_FORMAT_SUFFIX:

	  /* DEBUG2("  rlm_realm: Checking for suffix after \"%c\"", inst->delim[0]); */
		realmname = strrchr(username, inst->delim[0]);
		if (realmname) {
			*realmname = '\0';
			realmname++;
		}
		break;

	case REALM_FORMAT_PREFIX:

		/* DEBUG2("  rlm_realm: Checking for prefix before \"%c\"", inst->delim[0]); */

		ptr = strchr(username, inst->delim[0]);
		if (ptr) {
			*ptr = '\0';
		     ptr++;
		     realmname = username;
		     username = ptr;
		}
		break;

	default:
		realmname = NULL;
		break;
	}

	/*
	 *	Print out excruciatingly descriptive debugging messages
	 *	for the people who find it too difficult to think about
	 *	what's going on.
	 */
	if (realmname) {
		DEBUG2("    rlm_realm: Looking up realm \"%s\" for User-Name = \"%s\"",
		       realmname, request->username->strvalue);
	} else {
		if( inst->ignore_null ) {
			DEBUG2("    rlm_realm: No '%c' in User-Name = \"%s\", skipping NULL due to config.",
			inst->delim[0], request->username->strvalue);
			return 0;
		}
		DEBUG2("    rlm_realm: No '%c' in User-Name = \"%s\", looking up realm NULL",
		       inst->delim[0], request->username->strvalue);
	}

	/*
	 *	Allow DEFAULT realms unless told not to.
	 */
	realm = realm_find(realmname, (request->packet->code == PW_ACCOUNTING_REQUEST));
	if (!realm) {
		DEBUG2("    rlm_realm: No such realm \"%s\"",
		       (realmname == NULL) ? "NULL" : realmname);
		return 0;
	}
	if( inst->ignore_default &&
	    (strcmp(realm->realm, "DEFAULT")) == 0) {
		DEBUG2("    rlm_realm: Found DEFAULT, but skipping due to config.");
		return 0;
	}


	DEBUG2("    rlm_realm: Found realm \"%s\"", realm->realm);

	/*
	 *	If we've been told to strip the realm off, then do so.
	 */
	if (realm->striprealm) {
		/*
		 *	Create the Stripped-User-Name attribute, if it
		 *	doesn't exist.
		 *
		 */
		if (request->username->attribute != PW_STRIPPED_USER_NAME) {
			vp = paircreate(PW_STRIPPED_USER_NAME, PW_TYPE_STRING);
			if (!vp) {
				radlog(L_ERR|L_CONS, "no memory");
				return -1;
			}
			pairadd(&request->packet->vps, vp);
			DEBUG2("    rlm_realm: Adding Stripped-User-Name = \"%s\"", username);
		} else {
			vp = request->username;
			DEBUG2("    rlm_realm: Setting Stripped-User-Name = \"%s\"", username);
		}

		strcpy(vp->strvalue, username);
		vp->length = strlen((char *)vp->strvalue);
		request->username = vp;
	}

	DEBUG2("    rlm_realm: Proxying request from user %s to realm %s",
	       username, realm->realm);

	/*
	 *	Add the realm name to the request.
	 */
	pairadd(&request->packet->vps, pairmake("Realm", realm->realm,
						T_OP_EQ));
	DEBUG2("    rlm_realm: Adding Realm = \"%s\"", realm->realm);

	/*
	 *	Figure out what to do with the request.
	 */
	switch (request->packet->code) {
	default:
		DEBUG2("    rlm_realm: Unknown packet code %d\n",
		       request->packet->code);
		return 0;		/* don't do anything */

		/*
		 *	Perhaps accounting proxying was turned off.
		 */
	case PW_ACCOUNTING_REQUEST:
		if (realm->acct_ipaddr == htonl(INADDR_NONE)) {
			DEBUG2("    rlm_realm: Accounting realm is LOCAL.");
			return 0;
		}

		if (realm->acct_port == 0) {
			DEBUG2("    rlm_realm: acct_port is not set.  Proxy cancelled.");
			return 0;
		}
		break;

		/*
		 *	Perhaps authentication proxying was turned off.
		 */
	case PW_AUTHENTICATION_REQUEST:
		if (realm->ipaddr == htonl(INADDR_NONE)) {
			DEBUG2("    rlm_realm: Authentication realm is LOCAL.");
			return 0;
		}

		if (realm->auth_port == 0) {
			DEBUG2("    rlm_realm: auth_port is not set.  Proxy cancelled.");
			return 0;
		}
		break;
	}

	/*
	 *      If this request has arrived from another freeradius server
	 *      that has already proxied the request, we don't need to do
	 *      it again.
	 */
	for (vp = request->packet->vps; vp; vp = vp->next) {
		if (vp->attribute == PW_FREERADIUS_PROXIED_TO) {
			if (request->packet->code == PW_AUTHENTICATION_REQUEST &&
			    vp->lvalue == realm->ipaddr) {
				DEBUG2("    rlm_realm: Request not proxied due to Freeradius-Proxied-To");
				return 0;
			}
			if (request->packet->code == PW_ACCOUNTING_REQUEST &&
			    vp->lvalue == realm->acct_ipaddr) {
				DEBUG2("    rlm_realm: Request not proxied due to Freeradius-Proxied-To");
				return 0;
			}
		}
        }

	/*
	 *	We got this far, which means we have a realm, set returnrealm
	 */
	*returnrealm = realm;
	return 0;
}

/*
 *	Add a "Proxy-To-Realm" attribute to the request.
 */
static void add_proxy_to_realm(VALUE_PAIR **vps, REALM *realm)
{
	VALUE_PAIR *vp;

	/*
	 *	Tell the server to proxy this request to another
	 *	realm.
	 */
	vp = pairmake("Proxy-To-Realm", realm->realm, T_OP_EQ);
	if (!vp) {
		radlog(L_ERR|L_CONS, "no memory");
		exit(1);
	}

	/*
	 *  Add it, even if it's already present.
	 */
	pairadd(vps, vp);
}

/*
 *  Perform the realm module instantiation.  Configuration info is
 *  stored in *instance for later use.
 */

static int realm_instantiate(CONF_SECTION *conf, void **instance)
{
        struct realm_config_t *inst;

        /* setup a storage area for instance data */
        inst = rad_malloc(sizeof(*inst));
	if (!inst) {
		return -1;
	}
	memset(inst, 0, sizeof(*inst));

	if(cf_section_parse(conf, inst, module_config) < 0) {
	       free(inst);
               return -1;
	}

	if(strcasecmp(inst->formatstring, "suffix") == 0) {
	     inst->format = REALM_FORMAT_SUFFIX;
	} else if(strcasecmp(inst->formatstring, "prefix") == 0) {
	     inst->format = REALM_FORMAT_PREFIX;
        } else {
	     radlog(L_ERR, "Bad value \"%s\" for realm format value", inst->formatstring);
	     free(inst);
	     return -1;
	}
	free(inst->formatstring);
	if(strlen(inst->delim) != 1) {
	     radlog(L_ERR, "Bad value \"%s\" for realm delimiter value", inst->delim);
	     free(inst);
	     return -1;
	}

	*instance = inst;
	return 0;

}





/*
 *  Examine a request for a username with an realm, and if it
 *  corresponds to something in the realms file, set that realm as
 *  Proxy-To.
 *
 *  This should very nearly duplicate the old proxy_send() code
 */
static int realm_authorize(void *instance, REQUEST *request)
{
	REALM *realm;

	/*
	 *	Check if we've got to proxy the request.
	 *	If not, return without adding a Proxy-To-Realm
	 *	attribute.
	 */
	if (check_for_realm(instance, request, &realm) < 0) {
		return RLM_MODULE_FAIL;
	}
	if (!realm) {
		return RLM_MODULE_NOOP;
	}

	/*
	 *	Maybe add a Proxy-To-Realm attribute to the request.
	 */
	DEBUG2("    rlm_realm: Preparing to proxy authentication request to realm \"%s\"\n",
	       realm->realm);
	add_proxy_to_realm(&request->config_items, realm);

	return RLM_MODULE_UPDATED; /* try the next module */
}

/*
 * This does the exact same thing as the realm_authorize, it's just called
 * differently.
 */
static int realm_preacct(void *instance, REQUEST *request)
{
	const char *name = (char *)request->username->strvalue;
	REALM *realm;

	if (!name)
	  return RLM_MODULE_OK;


	/*
	 *	Check if we've got to proxy the request.
	 *	If not, return without adding a Proxy-To-Realm
	 *	attribute.
	 */
	if (check_for_realm(instance, request, &realm) < 0) {
		return RLM_MODULE_FAIL;
	}
	if (!realm) {
		return RLM_MODULE_NOOP;
	}


	/*
	 *	Maybe add a Proxy-To-Realm attribute to the request.
	 */
	DEBUG2("    rlm_realm: Preparing to proxy accounting request to realm \"%s\"\n",
	       realm->realm);
	add_proxy_to_realm(&request->config_items, realm);

	return RLM_MODULE_UPDATED; /* try the next module */
}

static int realm_detach(void *instance)
{
	struct realm_config_t *inst = instance;
	free(inst->delim);
	free(instance);
	return 0;
}

/* globally exported name */
module_t rlm_realm = {
  "realm",
  0,				/* type: reserved */
  NULL,				/* initialization */
  realm_instantiate,	       	/* instantiation */
  {
	  NULL,			/* authentication */
	  realm_authorize,	/* authorization */
	  realm_preacct,	/* preaccounting */
	  NULL,			/* accounting */
	  NULL,			/* checksimul */
	  NULL,			/* pre-proxy */
	  NULL,			/* post-proxy */
	  NULL			/* post-auth */
  },
  realm_detach,			/* detach */
  NULL,				/* destroy */
};


syntax highlighted by Code2HTML, v. 0.9.1