/* * (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 #endif #include #include #include #include "md5-wrap.h" #include #include #include #include #include #include #include #include "apop.h" #ifdef HAVE_SETFSUID # include #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 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; }