/*
* (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