/*
 * $Id: ftp-ldap.c,v 1.4 1999/09/24 06:38:52 wiegand Exp $
 *
 * FTP Proxy LDAP interface handling
 *
 * Author(s): Jens-Gero Boehm <jens-gero.boehm@suse.de>
 *            Pieter Hollants <pieter.hollants@suse.de>
 *            Marius Tomaschewski <mt@suse.de>
 *            Volker Wiegand <volker.wiegand@suse.de>
 *
 * This file is part of the SuSE Proxy Suite
 *            See also  http://proxy-suite.suse.de/
 *
 * 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.
 *
 * A history log can be found at the end of this file.
 */

#ifndef lint
static char rcsid[] = "$Id: ftp-ldap.c,v 1.4 1999/09/24 06:38:52 wiegand Exp $";
#endif

#include <config.h>

#if defined(STDC_HEADERS)
#  include <stdio.h>
#  include <string.h>
#  include <stdlib.h>
#  include <stdarg.h>
#  include <errno.h>
#  include <ctype.h>
#endif

#if defined(HAVE_UNISTD_H)
#  include <unistd.h>
#endif

#if defined(TIME_WITH_SYS_TIME)
#  include <sys/time.h>
#  include <time.h>
#else
#  if defined(HAVE_SYS_TIME_H)
#    include <sys/time.h>
#  else
#    include <time.h>
#  endif
#endif

#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#if defined(HAVE_LIBLDAP)
#  if defined(HAVE_LDAP_UMICH)
#    include <lber.h>
#  endif
#  include <ldap.h>
#  if !defined(LDAP_PORT)
#    define LDAP_PORT	389
#  endif
#endif

#include "com-config.h"
#include "com-debug.h"
#include "com-misc.h"
#include "com-socket.h"
#include "com-syslog.h"
#include "ftp-client.h"
#include "ftp-cmds.h"
#include "ftp-ldap.h"


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

#if defined(HAVE_LIBLDAP)
#  if defined(HAVE_LDAP_UMICH)
#    define GET_LDERROR(ld)	(ld)->ld_errno
#  else
#    define GET_LDERROR(ld)	ldap_get_lderrno((ld), NULL, NULL)
#  endif
#endif


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

#if defined(HAVE_LIBLDAP)
static int   ldap_fetch (CONTEXT *ctx, char *srv);
static char *ldap_attrib(LDAP *ld, LDAPMessage *e, char *attr, char *dflt);
#endif


/* ------------------------------------------------------------ **
**
**	Function......:	ldap_setup_user
**
**	Parameters....:	ctx		Pointer to user context
**
**	Return........:	(none)
**
**	Purpose.......: Read the user specific parameters from
**			an LDAP Server if one is known. Else
**			read the values from the config file.
**
** ------------------------------------------------------------ */

void ldap_setup_user(CONTEXT *ctx)
{
	char *p;
	u_int16_t l, u;

	if (ctx == NULL)		/* Basic sanity check	*/
		misc_die(FL, "ldap_setup_user: ?ctx?");

	/*
	** Setup defaults for the client's DTP process
	*/
	ctx->cli_mode = MOD_ACT_FTP;
	ctx->cli_addr = ctx->cli_ctrl->addr;
	ctx->cli_port = ctx->cli_ctrl->port;

	ctx->srv_addr = (u_int32_t) 0;	/* Let's set a marker	*/

#if defined(HAVE_LIBLDAP)
	/*
	** If an LDAP server is configured, insist on using it
	*/
	if ((p = config_str(NULL, "LDAPServer", NULL)) != NULL) {
		int lderr;
		if ((lderr = ldap_fetch(ctx, p)) != LDAP_SUCCESS) {
			errno = 0;
			syslog_error("can't read LDAP data "
					"for %s: %.512s",
					ctx->cli_ctrl->peer,
					ldap_err2string(lderr));
			exit(EXIT_FAILURE);
		}
		if (ctx->srv_addr != (u_int32_t) 0) {
			syslog_write(U_INF,
				"reading data for '%s' from LDAP",
				ctx->username);
			return;		/* LDAP has delivered	*/
		}
	}
#endif

	/*
	** Inform the auditor that we are using the config file
	*/
	syslog_write(U_INF, "reading data for '%s' from cfg-file",
						ctx->username);

	/*
	** Evaluate the destination FTP server address.
	** This is mandatory! Refuse to run if none given.
	*/
	if (ctx->magic_addr != INADDR_ANY)
		ctx->srv_addr = ctx->magic_addr;
	else if ((ctx->srv_addr = config_addr(ctx->username,
			"DestinationAddress",
			INADDR_BROADCAST)) == INADDR_BROADCAST) {
		errno = 0;
		syslog_error("can't eval DestAddr for %s",
					ctx->cli_ctrl->peer);
		exit(EXIT_FAILURE);
	}
#if defined(COMPILE_DEBUG)
	debug(2, "DestAddr for %s: '%s'", ctx->cli_ctrl->peer,
				socket_addr2str(ctx->srv_addr));
#endif

	/*
	** Evaluate the destination FTP server port
	*/
	if (ctx->magic_port != (u_int16_t) 0)
		ctx->srv_port = ctx->magic_port;
	else if ((ctx->srv_port = config_port(ctx->username,
			"DestinationPort", IPPORT_FTP)) == 0) {
		errno = 0;
		syslog_error("can't eval DestPort for %s",
					ctx->cli_ctrl->peer);
		exit(EXIT_FAILURE);
	}
#if defined(COMPILE_DEBUG)
	debug(2, "DestPort for %s: %d",
			ctx->cli_ctrl->peer, (int) ctx->srv_port);
#endif

	/*
	** Evaluate the destination transfer mode
	*/
	p = config_str(ctx->username,
			"DestinationTransferMode", "client");
	if (strcasecmp(p, "active") == 0)
		ctx->srv_mode = MOD_ACT_FTP;
	else if (strcasecmp(p, "passive") == 0)
		ctx->srv_mode = MOD_PAS_FTP;
	else if (strcasecmp(p, "client") == 0)
		ctx->srv_mode = MOD_CLI_FTP;
	else {
		syslog_error("can't eval DestMode for %s",
					ctx->cli_ctrl->peer);
		exit(EXIT_FAILURE);
	}
#if defined(COMPILE_DEBUG)
	debug(2, "DestMode for %s: %s", ctx->cli_ctrl->peer, p);
#endif

	/*
	** Evaluate the port ranges
	*/
	l = config_port(ctx->username, "DestinationMinPort", 0);
	u = config_port(ctx->username, "DestinationMaxPort", 0);
	if (l > 0 && u > 0 && u >= l) {
		ctx->srv_lrng = l;
		ctx->srv_urng = u;
	} else {
		ctx->srv_lrng = 0;
		ctx->srv_urng = 0;
	}
#if defined(COMPILE_DEBUG)
	debug(2, "DestRange for %s: %d-%d", ctx->cli_ctrl->peer,
			(int) ctx->srv_lrng, (int) ctx->srv_urng);
#endif

	l = config_port(ctx->username, "ActiveMinDataPort", 0);
	u = config_port(ctx->username, "ActiveMaxDataPort", 0);
	if (l > 0 && u > 0 && u >= l) {
		ctx->act_lrng = l;
		ctx->act_urng = u;
	} else {
		ctx->act_lrng = (IPPORT_FTP - 1);
		ctx->act_urng = (IPPORT_FTP - 1);
	}
#if defined(COMPILE_DEBUG)
	debug(2, "ActiveRange for %s: %d-%d", ctx->cli_ctrl->peer,
			(int) ctx->act_lrng, (int) ctx->act_urng);
#endif

	l = config_port(ctx->username, "PassiveMinDataPort", 0);
	u = config_port(ctx->username, "PassiveMaxDataPort", 0);
	if (l > 0 && u > 0 && u >= l) {
		ctx->pas_lrng = l;
		ctx->pas_urng = u;
	} else {
		ctx->pas_lrng = 0;
		ctx->pas_urng = 0;
	}
#if defined(COMPILE_DEBUG)
	debug(2, "PassiveRange for %s: %d-%d", ctx->cli_ctrl->peer,
			(int) ctx->pas_lrng, (int) ctx->pas_urng);
#endif

	/*
	** Setup other configuration options
	*/
	ctx->same_adr = config_bool(ctx->username, "SameAddress", 1);
	ctx->timeout  = config_int (ctx->username, "TimeOut",   900);
#if defined(COMPILE_DEBUG)
	debug(2, "SameAddress for %s: %s", ctx->cli_ctrl->peer,
				ctx->same_adr ? "yes" : "no");
	debug(2, "TimeOut for %s: %d", ctx->cli_ctrl->peer,
						ctx->timeout);
#endif

	/*
	** Adjust the allow/deny flags for the commands
	*/
	p = config_str(ctx->username, "ValidCommands", NULL);
	cmds_set_allow(p);
}


#if defined(HAVE_LIBLDAP)
/* ------------------------------------------------------------ **
**
**	Function......:	ldap_fetch
**
**	Parameters....:	ctx		Pointer to user context
**
**	Return........:	LDAP_SUCCESS or LDAP_... error number
**
**	Purpose.......: Read the user specific parameters from
**			an LDAP Server. The return values is
**			only used for error handling, and not
**			to determine if the user is known to
**			the server. This is actually handled
**			by setting the destination address.
**
** ------------------------------------------------------------ */

static int ldap_fetch(CONTEXT *ctx, char *srv)
{
	char str[MAX_PATH_SIZE], *p, *base, *idnt, *objc;
	int port, lderr;
	LDAP *ld;
	LDAPMessage *result, *e;
	u_int16_t l, u;

	if (ctx == NULL || srv == NULL)		/* Basic sanity */
		misc_die(FL, "ldap_fetch: ?ctx? ?srv?");

	/*
	** Determine LDAP server and port
	*/
	misc_strncpy(str, srv, sizeof(str));
	if ((p = strchr(str, ':')) != NULL) {
		*p++ = '\0';
		port = (int) socket_str2port(p, LDAP_PORT);
	} else
		port = (int) LDAP_PORT;

#if defined(COMPILE_DEBUG)
	debug(2, "LDAP server: %s:%d", str, port);
#endif

	/*
	** Ready to contact the LDAP server (as anonymous user)
	*/
	if ((ld = ldap_init(str, port)) == NULL) {
		syslog_error("can't reach LDAP server %s:%d",
						str, port);
		exit(EXIT_FAILURE);
	}
	if ((lderr = ldap_simple_bind_s(ld,
				NULL, NULL)) != LDAP_SUCCESS)
		return lderr;

	/*
	** Search for the user
	**	(by LDAPIdentifier, maybe also ObjectClass)
	*/
	if ((base = config_str(NULL, "LDAPBaseDN", "")) == NULL)
		misc_die(FL, "ldap_fetch: ?BaseDN?");
	if ((objc = config_str(NULL, "LDAPObjectClass", "")) == NULL)
		misc_die(FL, "ldap_fetch: ?ObjectClass?");
	if ((idnt = config_str(NULL, "LDAPIdentifier", "")) == NULL)
		misc_die(FL, "ldap_fetch: ?Identifier?");
	if (*idnt == '\0')
		idnt = "CN";
	if (*objc == '\0')
		sprintf(str, "(%.256s=%.256s)", idnt, ctx->username);
	else {
		sprintf(str, "(&(ObjectClass=%.256s)(%.256s=%.256s))",
					objc,  idnt, ctx->username);
	}

#if defined(COMPILE_DEBUG)
	debug(2, "LDAP search: base='%.256s' filter='%s'", base, str);
#endif

	if ((lderr = ldap_search_s(ld, base, LDAP_SCOPE_SUBTREE,
			str, NULL, 0, &result)) != LDAP_SUCCESS) {
		ldap_unbind(ld);
		return lderr;
	}

	/*
	** Check if we have a user (else return 'error' or 'empty')
	*/
	if ((e = ldap_first_entry(ld, result)) == NULL) {
		lderr = GET_LDERROR(ld);
		ldap_msgfree(result);
		ldap_unbind(ld);
		return lderr;
	}

	/*
	** Evaluate the destination FTP server address.
	** This is mandatory! Refuse to run if none given.
	*/
	p = ldap_attrib(ld, e, "DestinationAddress", "255.255.255.255");
	if (ctx->magic_addr != INADDR_ANY)
		ctx->srv_addr = ctx->magic_addr;
	else if ((ctx->srv_addr = socket_str2addr(p,
			INADDR_BROADCAST)) == INADDR_BROADCAST) {
		errno = 0;
		syslog_error("can't eval DestAddr for %s",
					ctx->cli_ctrl->peer);
		exit(EXIT_FAILURE);
	}
#if defined(COMPILE_DEBUG)
	debug(2, "DestAddr for %s: '%s'", ctx->cli_ctrl->peer,
				socket_addr2str(ctx->srv_addr));
#endif

	/*
	** Evaluate the destination FTP server port
	*/
	p = ldap_attrib(ld, e, "DestinationPort", "ftp");
	if (ctx->magic_port != (u_int16_t) 0)
		ctx->srv_port = ctx->magic_port;
	else if ((ctx->srv_port = socket_str2port(p,
				IPPORT_FTP)) == (u_int16_t) 0) {
		errno = 0;
		syslog_error("can't eval DestPort for %s",
					ctx->cli_ctrl->peer);
		exit(EXIT_FAILURE);
	}
#if defined(COMPILE_DEBUG)
	debug(2, "DestPort for %s: %d",
			ctx->cli_ctrl->peer, (int) ctx->srv_port);
#endif

	/*
	** Evaluate the destination transfer mode
	*/
	p = ldap_attrib(ld, e, "DestinationTransferMode", "client");
	if (strcasecmp(p, "active") == 0)
		ctx->srv_mode = MOD_ACT_FTP;
	else if (strcasecmp(p, "passive") == 0)
		ctx->srv_mode = MOD_PAS_FTP;
	else if (strcasecmp(p, "client") == 0)
		ctx->srv_mode = MOD_CLI_FTP;
	else {
		syslog_error("can't eval DestMode for %s",
					ctx->cli_ctrl->peer);
		exit(EXIT_FAILURE);
	}
#if defined(COMPILE_DEBUG)
	debug(2, "DestMode for %s: %s", ctx->cli_ctrl->peer, p);
#endif

	/*
	** Evaluate the port ranges
	*/
	p = ldap_attrib(ld, e, "DestinationMinPort", "0");
	l = socket_str2port(p, 0);
	p = ldap_attrib(ld, e, "DestinationMaxPort", "0");
	u = socket_str2port(p, 0);
	if (l > 0 && u > 0 && u >= l) {
		ctx->srv_lrng = l;
		ctx->srv_urng = u;
	} else {
		ctx->srv_lrng = 0;
		ctx->srv_urng = 0;
	}
#if defined(COMPILE_DEBUG)
	debug(2, "DestRange for %s: %d-%d", ctx->cli_ctrl->peer,
			(int) ctx->srv_lrng, (int) ctx->srv_urng);
#endif

	p = ldap_attrib(ld, e, "ActiveMinDataPort", "0");
	l = socket_str2port(p, 0);
	p = ldap_attrib(ld, e, "ActiveMaxDataPort", "0");
	u = socket_str2port(p, 0);
	if (l > 0 && u > 0 && u >= l) {
		ctx->act_lrng = l;
		ctx->act_urng = u;
	} else {
		ctx->act_lrng = (IPPORT_FTP - 1);
		ctx->act_urng = (IPPORT_FTP - 1);
	}
#if defined(COMPILE_DEBUG)
	debug(2, "ActiveRange for %s: %d-%d", ctx->cli_ctrl->peer,
			(int) ctx->act_lrng, (int) ctx->act_urng);
#endif

	p = ldap_attrib(ld, e, "PassiveMinDataPort", "0");
	l = socket_str2port(p, 0);
	p = ldap_attrib(ld, e, "PassiveMaxDataPort", "0");
	u = socket_str2port(p, 0);
	if (l > 0 && u > 0 && u >= l) {
		ctx->pas_lrng = l;
		ctx->pas_urng = u;
	} else {
		ctx->pas_lrng = 0;
		ctx->pas_urng = 0;
	}
#if defined(COMPILE_DEBUG)
	debug(2, "PassiveRange for %s: %d-%d", ctx->cli_ctrl->peer,
			(int) ctx->pas_lrng, (int) ctx->pas_urng);
#endif

	/*
	** Setup other configuration options
	*/
	p = ldap_attrib(ld, e, "SameAddress", "yes");
	if (strcasecmp(p, "y") == 0)
		ctx->same_adr = 1;
	else if (strcasecmp(p, "on") == 0)
		ctx->same_adr = 1;
	else if (strcasecmp(p, "yes") == 0)
		ctx->same_adr = 1;
	else if (strcasecmp(p, "true") == 0)
		ctx->same_adr = 1;
	else if (*p >= '0' && *p <= '9')
		ctx->same_adr = (atoi(p) != 0);
	else
		ctx->same_adr = 0;
	p = ldap_attrib(ld, e, "TimeOut", "900");
	if (*p >= '0' && *p <= '9')
		ctx->timeout = atoi(p);
	else
		ctx->timeout = 900;
#if defined(COMPILE_DEBUG)
	debug(2, "SameAddress for %s: %s", ctx->cli_ctrl->peer,
				ctx->same_adr ? "yes" : "no");
	debug(2, "TimeOut for %s: %d", ctx->cli_ctrl->peer,
						ctx->timeout);
#endif

	/*
	** Adjust the allow/deny flags for the commands
	*/
	p = ldap_attrib(ld, e, "ValidCommands", NULL);
	cmds_set_allow(p);

	/*
	** All relevant attributes have been evaluated
	*/
	ldap_msgfree(result);
	ldap_unbind(ld);
	return LDAP_SUCCESS;
}


/* ------------------------------------------------------------ **
**
**	Function......:	ldap_attrib
**
**	Parameters....:	ld		Pointer to LDAP struct
**			e		Pointer to result buffer
**			attr		Name of desired option
**			dflt		Default value
**
**	Return........:	Value for attr (or dflt if not found)
**
**	Purpose.......: Search the LDAP result message for the
**			desired attribute value and return it.
**			NEVER return a NULL pointer except if
**			the dflt was taken and is NULL itself.
**
** ------------------------------------------------------------ */

static char *ldap_attrib(LDAP *ld, LDAPMessage *e, char *attr, char *dflt)
{
	static char str[MAX_PATH_SIZE];
	char **vals;

	if (ld == NULL || e == NULL || attr == NULL)
		misc_die(FL, "ldap_attrib: ?ld? ?e? ?attr?");

	/*
	** See if this attribute has values available
	*/
	if ((vals = ldap_get_values(ld, e, attr)) == NULL) {
#if defined(COMPILE_DEBUG)
		debug(3, "LDAP result: '%.256s' - '%.1024s'",
						attr, NIL(dflt));
#endif
		return dflt;
	}

	/*
	** Save value (use the first one) and free memory
	*/
	misc_strncpy(str, vals[0], sizeof(str));
	ldap_value_free(vals);

#if defined(COMPILE_DEBUG)
	debug(3, "LDAP result: '%.256s' = '%.1024s'", attr, str);
#endif
	return str;
}
#endif


/* ------------------------------------------------------------
 * $Log: ftp-ldap.c,v $
 * Revision 1.4  1999/09/24 06:38:52  wiegand
 * added regular expressions for all commands
 * removed character map and length of paths
 * added flag to reset PASV on every PORT
 * added "magic" user with built-in destination
 * added some argument pointer fortification
 *
 * Revision 1.3  1999/09/21 07:14:43  wiegand
 * syslog / abort cleanup and review
 * default PASV port range to 0:0
 *
 * Revision 1.2  1999/09/17 16:32:29  wiegand
 * changes from source code review
 * added POSIX regular expressions
 *
 * Revision 1.1  1999/09/15 14:06:22  wiegand
 * initial checkin
 *
 * ------------------------------------------------------------ */

