/*
 * (POP3Lite) APOP - 3lite POP3 Daemon (APOP support)
 * Copyright (C) 2000, 2001 Gergely Nagy <8@free.bsd.hu>
 *
 * This file is part of POP3Lite.
 *
 * POP3Lite 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.
 *
 * POP3Lite 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
 */

#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <sys/param.h>

#include <pop3lite.h>
#include <glib.h>
#include "md5-wrap.h"

#include <fcntl.h>
#include <string.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include "apop.h"

#ifdef HAVE_SETFSUID
#	include <sys/fsuid.h>
#endif

#ifndef MAXHOSTNAMELEN
#	define MAXHOSTNAMELEN 64
#endif

static const char rcsid[]="$Id: apop.c,v 1.13.2.1 2001/04/18 10:15:33 algernon Exp $";

int apop_LTX_module_done ( P3LControl *control );
int apop_LTX_module_init ( P3LControl *control );
static CommandResponse *apop_cmd_auth_apop ( P3LControl *control,
					     const char *args );
static CommandResponse *apop_greeting ( P3LControl *control );
static gpointer apop_default_get_secret ( P3LControl *control,
					  size_t *ssize,
					  const char *user );

static p3l_pop3_command B_apop_cmd_auth_apop;
static P3LControl_greeting B_apop_greeting;
static P3LHook_apop_get_secret B_apop_apop_get_secret;

static char *apop_timestamp;

/**
 * apop_expand_tilde: expand tilde to home directory
 * @control: the usual control structure
 * @path: the path to expand the initial tilde in
 * @user: the logged (or to be logged) in user's name
 *
 * This one expands the initial tilde to a specified
 * users' home directory.
 *
 * Returns: the expanded directory.
 */

static char *
apop_expand_tilde ( P3LControl *control, const char *path,
		    const char *user )
{
	UserInfo *ui;

	/*
	 * Does it begin with ~ ? No ? Then we have nothing
	 * to do;
	 */
	if ( path[0] != '~' )
		return g_strdup ( path );

	/*
	 * Get the passwd entry.
	 */
	ui = control->system->getuinam ( control, user );

	/*
	 * If the user is unknown, we can do nothing.
	 */
	if ( ui == NULL )
		return NULL;

	/*
	 * Now replace the tilde!
	 */
	return g_strdup_printf ( "%s%s", ui->home, &path[1] );
}

/**
 * apop_default_get_secret: default control->hooks/apop_get_secret
 * @control: the ususal control structure
 * @ssize: variable to put the secret's size in
 * @user: the user whose secret we want to know
 *
 * Reads the secret file and returns all of its contents.
 *
 * Returns: a secret.
 **/

static gpointer
apop_default_get_secret ( P3LControl *control,
			  size_t *ssize, const char *user )
{
	char *secret_file, *secret;
#ifdef HAVE_SETFSUID
	uid_t curr, temp;
	UserInfo *ui;
#endif

#ifdef DEBUG
	control->system->log ( control, LOG_DEBUG, "%s:%d: handling APOP-GET-SECRET (%s)",
			       __FILE__, __LINE__, user );
#endif

	/*
	 * Determine filename which contains our secret.
	 */

	secret_file = P3L_GET_FIRST_OPTION ( "APOP.SECRET_FILE" );
	if ( secret_file == NULL )
		secret_file = "~/.apop_secret";

	secret_file = apop_expand_tilde ( control, secret_file, user );

#ifdef HAVE_SETFSUID
	ui = control->system->getuinam ( control, user );
	if ( ui != NULL )
	{
		temp = ui->uid;
		curr = setfsuid ( temp );
	}
	else
		curr = 0;
#endif

	/*
	 * If the file is not found, we can
	 *	(1) fall back to the previous APOP-get-secret implementation
	 *	(2) bail out with an error. (default)
	 */

	if ( secret_file == NULL )
	{
#ifdef HAVE_SETFSUID
		setfsuid ( curr );
#endif
		if ( B_apop_apop_get_secret != NULL &&
		     p3l_is_enabled ( P3L_GET_FIRST_OPTION ( "APOP.GET_SECRET.FALLBACK" ) ) )
			return (*B_apop_apop_get_secret) ( control, ssize, user );
		else
			return NULL;
	}

	if ( ( secret = p3l_read_file ( secret_file, ssize ) ) == NULL )
	{
#ifdef HAVE_SETFSUID
		setfsuid ( curr );
#endif
		if ( B_apop_apop_get_secret != NULL &&
		     p3l_is_enabled ( P3L_GET_FIRST_OPTION ( "APOP.GET_SECRET.FALLBACK" ) ) )
			return (*B_apop_apop_get_secret) ( control, ssize, user );
		else
			return NULL;
	}

#ifdef HAVE_SETFSUID
	setfsuid ( curr );
#endif

	return (gpointer) secret;
}

/**
 * apop_cmd_auth_apop: APOP implementation
 * @control: the usual control structure
 * @args: arguments passed by the user
 *
 * Handles the APOP authentication.
 *
 * Returns: the result as a CommandResponse struct.
 **/

static CommandResponse *
apop_cmd_auth_apop ( P3LControl *control, const char *args )
{
	char **argv;
	char md5[16];
	gpointer secret;
	size_t size;
	P3LHook_apop_get_secret get_secret;
	md5_context context;

#ifdef DEBUG
	control->system->log ( control, LOG_DEBUG, "%s:%d: handling APOP",
			       __FILE__, __LINE__ );
#endif

	/*
	 * Check & split arguments
	 */

	if ( args == NULL )
		return p3l_respond ( POP3_ERR, "argument missing" );

	argv = g_strsplit ( args, " ", 1 );
	if ( argv[0] == NULL || argv[1] == NULL )
	{
		g_strfreev ( argv );
		return p3l_respond ( POP3_ERR, "argument missing" );
	}

	/*
	 * Get the secret...
	 */

	get_secret = (P3LHook_apop_get_secret)
		g_hash_table_lookup ( control->hooks, "APOP-GET-SECRET" );
	if ( get_secret == NULL )
		get_secret = apop_default_get_secret;

	if ( ( secret = (*get_secret) ( control, &size, argv[0] ) ) == NULL )
	{
		if ( B_apop_cmd_auth_apop != NULL &&
		     p3l_is_enabled ( P3L_GET_FIRST_OPTION ( "APOP.FALLBACK" ) ) )
		{
			g_strfreev ( argv );
			return (*B_apop_cmd_auth_apop) ( control, args );
		}
		else
		{
			control->system->log ( control, LOG_WARNING,
					       "APOP: secret file not found for %s", argv[0] );
			g_strfreev ( argv );
			sleep ( 5 );
			return p3l_respond ( POP3_ERR, "Invalid username or password" );
		}
	}

	/**
	 * Generate md5 digest
	 **/

	__md5_init_ctx ( &context );
	__md5_process_bytes ( apop_timestamp, strlen ( apop_timestamp ), &context );
	__md5_process_bytes ( secret, size, &context );
	__md5_finish_ctx ( &context, &md5 );

	p3l_clear ( secret );
	g_free ( secret );

	/*
	 * Do the authentication. If doesn't work, fallback or bail out.
	 * If the digests match, jump to TRANSACTION state.
	 */

	if ( ! memcmp ( bintohex ( md5, 16, 1 ), argv[1], 16 ) )
	{
		p3l_clear ( md5 );
		g_hash_table_insert ( control->data, "USER", (gpointer) g_strdup ( argv[0] ) );
		control->state = POP3_STATE_TRANS;
		control->system->log ( control, LOG_NOTICE,
				       "User %s logged in", argv[0] );
		g_strfreev ( argv );
		return p3l_respond ( POP3_OK_HIDDEN, NULL );
	}
	else
	{
		p3l_clear ( md5 );
		if ( B_apop_cmd_auth_apop != NULL &&
		     p3l_is_enabled ( P3L_GET_FIRST_OPTION ( "APOP.FALLBACK" ) ) )
		{
			g_strfreev ( argv );
			return (*B_apop_cmd_auth_apop) ( control, args );
		}
		else
		{
			control->system->log ( control, LOG_WARNING,
					       "Attempted login: %s", argv[0] );
			g_strfreev ( argv );
			sleep ( 5 );
			return p3l_respond ( POP3_ERR, "Invalid username or password" );
		}
	}
}

/**
 * apop_greeting: Greeting override a'la APOP
 * @control: the usual control struct
 *
 * Appends an APOP banner to the end of the greeting.
 *
 * Returns: the required CommandResponse.
 **/

static CommandResponse *
apop_greeting ( P3LControl *control )
{
	CommandResponse *resp;
	pid_t pid = getpid();
	char hostname[MAXHOSTNAMELEN];
	char domainname[MAXHOSTNAMELEN];
	char *fqdn;
	char *tmp;

#ifdef DEBUG
	control->system->log ( control, LOG_DEBUG, "%s:%d: Greeting",
			       __FILE__, __LINE__ );
#endif

	/*
	 * Generate timestamp.
	 */

#if defined(HAVE_GETHOSTNAME)
	gethostname ( hostname, sizeof ( hostname) );
	getdomainname ( domainname, sizeof ( domainname ) );
	if ( domainname != NULL )
		fqdn = g_strdup_printf ( "%s.%s", hostname, domainname );
	else
	{
		if ( ( tmp = P3L_GET_FIRST_OPTION ( "APOP.DOMAINNAME" ) ) != NULL )
			fqdn = g_strdup_printf ( "%s.%s", hostname, tmp );
		else
			fqdn = g_strdup ( hostname );
	}
#else
	fqdn = g_strdup ( "unknown" );
#endif

	if ( fqdn[ strlen ( fqdn )  ] == '.' )
		fqdn[ strlen ( fqdn )  ] = '\0';


	if ( P3L_GET_FIRST_OPTION ( "APOP.FQDN" ) != NULL )
	{
		g_free ( fqdn );
		fqdn = g_strdup ( P3L_GET_FIRST_OPTION ( "APOP.FQDN" ) );
	}

	apop_timestamp = g_strdup_printf ( "<%u.%d@%s>", (unsigned int) pid,
					   (int) time ( NULL ), fqdn );

	g_free ( fqdn );

	/*
	 * Without an existing greeting, we're useless.
	 * NOTE: It would be easy to make our own greeting,
	 * but if control->greeting is NULL, then something
	 * is wrong.
	 */

	if ( B_apop_greeting != NULL )
		resp = (*B_apop_greeting) ( control );
	else
	{
		control->system->log ( control, LOG_WARNING,
				       "APOP failed: no initial greeting" );
		return p3l_respond ( POP3_FATAL, "internal error" );
	}

	/*
	 * Append our <pid.time@hostname> info.
	 */

	return p3l_respond ( POP3_OK, g_strdup_printf ( "%s %s",
		resp->message, apop_timestamp ) );
}

int
apop_LTX_module_init ( P3LControl *control )
{
#ifdef DEBUG
	control->system->log ( control, LOG_DEBUG, "%s:%d: init mod-APOP",
			       __FILE__, __LINE__ );
#endif

	/*
	 * Replace APOP command
	 */
	
	B_apop_cmd_auth_apop = (p3l_pop3_command) 
		p3l_command_replace ( control->auth_commands, "APOP",
				      (gpointer) apop_cmd_auth_apop );

	/*
	 * Replace APOP-GET-SECRET
	 */
	B_apop_apop_get_secret = (P3LHook_apop_get_secret)
		p3l_command_replace ( control->hooks, "APOP-GET-SECRET",
				      (gpointer) apop_default_get_secret );

	/*
	 * Replace greeting
	 */
	B_apop_greeting = control->greeting;
	control->greeting = apop_greeting;

	return 0;
}

int
apop_LTX_module_done ( P3LControl *control )
{

#ifdef DEBUG
	control->system->log ( control, LOG_DEBUG, "%s:%d: done mod-APOP",
			       __FILE__, __LINE__ );
#endif

	control->greeting = B_apop_greeting;
	g_hash_table_insert ( control->auth_commands, "APOP",
		(gpointer) B_apop_cmd_auth_apop );
	g_hash_table_insert ( control->hooks, "APOP-GET-SECRET",
		(gpointer) B_apop_apop_get_secret );

	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1