view src/authenticate.c @ 500:614a3177b15c noffle tip

Add mail-from option. Some modern mail systems will try and ensure the sender email is a legitimate address. Which will fail if there isn't such an address.
author Jim Hague <jim.hague@acm.org>
date Wed, 14 Aug 2013 12:04:39 +0100
parents 20abd71918ad
children
line wrap: on
line source

/*
  authenticate.c

  Do client authentication

  $Id: authenticate.c 622 2004-03-16 10:24:18Z bears $
*/

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <grp.h>
#include <pwd.h>
#include "common.h"
#include "authenticate.h"
#include "configfile.h"
#include "log.h"
#include "portable.h"
#include "util.h"

#if USE_AUTH

#if USE_PAM
#include <security/pam_appl.h>

static const char *password;

/*
 * It's a bit tricky to go around asking PAM questions at this stage,
 * as well as not fitting NNTP, so just repond to all PAM questions
 * with the password and hope that works.
 */
static int noffle_conv(	int num_msg, 
			const struct pam_message **msgm,
			struct pam_response **response, 
			void *appdata_ptr	)
{
    struct pam_response *reply;

    UNUSED(appdata_ptr);
    UNUSED(msgm);
    
    reply = calloc( num_msg, sizeof (struct pam_response) );
    reply->resp = strdup( password );
    reply->resp_retcode = 0;
    *response = reply;
    return PAM_SUCCESS;
}

static struct pam_conv conv = {
    noffle_conv,
    NULL
};

static pam_handle_t *pamh = NULL;
static Bool pam_session_opened = FALSE;
static Bool pam_set_cred = FALSE;
static uid_t oldEuid;

static Bool
PAM_open( void )
{
    int retval;

    /* To use PAM successfully we need to be root. */
    ASSERT ( getuid() == 0 );
    
    ASSERT( pamh == NULL );

    /*
     * Preserve old eUid to be restored when PAM closes and set
     * current euid to root for PAMs benefit.
     */
    oldEuid = geteuid();
    if ( seteuid( 0 ) < 0 )
    {
	Log_err( "Cannot set euid to root: %s", strerror( errno ) );
	return FALSE;
    }
    
    retval = pam_start( "noffle", NULL, &conv, &pamh );
    if ( retval != PAM_SUCCESS )
    {
	Log_err( "Cannot starting authentication: %s",
		 pam_strerror( pamh, retval ) );
	return FALSE;
    }

    return TRUE;
}

static enum AuthResult
PAM_authenticate( const char *user, const char *pass )
{
    int retval;
    
    ASSERT( pamh != NULL );

    password = pass;
    
    retval = pam_set_item( pamh, PAM_USER, user );
    if ( retval != PAM_SUCCESS )
	Log_dbg( LOG_DBG_AUTH, "pam_set_item failed: %s",
		 pam_strerror( pamh, retval ) );

    if ( retval == PAM_SUCCESS )
    {
	retval = pam_authenticate( pamh, PAM_SILENT );
	if ( retval != PAM_SUCCESS )
	    Log_dbg( LOG_DBG_AUTH, "pam_authenticate failed: %s",
		     pam_strerror( pamh, retval ) );
    }

    if ( retval == PAM_SUCCESS )
    {
	  retval = pam_setcred( pamh, PAM_ESTABLISH_CRED );
	  if ( retval != PAM_SUCCESS )
	      Log_dbg( LOG_DBG_AUTH, "pam_setcred failed: %s",
		       pam_strerror( pamh, retval ) );
	  else
	      pam_set_cred = TRUE;
    }
    
    if ( retval == PAM_SUCCESS )
    {
	  retval = pam_open_session( pamh, 0 );
	  if ( retval != PAM_SUCCESS )
	      Log_dbg( LOG_DBG_AUTH, "pam_open_session failed: %s",
		       pam_strerror( pamh, retval ) );
	  else
	      pam_session_opened = TRUE;
    }

    switch ( retval )
    {
    case PAM_SUCCESS:
	return AUTH_OK;

    case PAM_MAXTRIES:
	return AUTH_DISCONNECT;

    case PAM_ABORT:
	return AUTH_ERROR;
    }

    return AUTH_FAILED;
}

static void
PAM_close( void )
{
    int retval = 0;
    
    ASSERT ( pamh != NULL );

    if ( pam_session_opened )
    {
	pam_session_opened = FALSE;
	retval = pam_close_session( pamh, 0 );
	if ( retval != PAM_SUCCESS )
	    Log_dbg( LOG_DBG_AUTH, "pam_close_session failed: %s",
		     pam_strerror( pamh, retval ) );
    }

    if ( pam_set_cred )
    {
	pam_set_cred = FALSE;
	retval = pam_setcred( pamh, PAM_DELETE_CRED );
	if ( retval != PAM_SUCCESS )
	    Log_dbg( LOG_DBG_AUTH, "pam_set_cred failed: %s",
		     pam_strerror( pamh, retval ) );
    }

    retval = pam_end( pamh, retval );
    if ( retval != PAM_SUCCESS )
	Log_dbg( LOG_DBG_AUTH, "pam_end failed: %s",
		 pam_strerror( pamh, retval ) );
    pamh = NULL;

    /*
     * For completeness set euid back to original value, though it'll
     * probably be set again by Auth_dropPrivs.
     */
    if ( seteuid( oldEuid ) < 0 )
	Log_err( "Cannot set euid back to %d: %s",
		 oldEuid, strerror( errno ) );
}

#else

/*
 * No PAM, so provide a simple alternative.
 *
 * USERSFILE is a simple plain-text file consisting of username password
 * pairs, one pair per line. Comments are prefixed by '#'. Blank lines
 * are ignored.
 *
 * By way of a simple security check, the users file MUST be only
 * readable and writable by the owner.
 */

#define	AUTH_MAX_TRIES		3

static int authTries = 0;

static enum AuthResult
file_authenticate( const char *user, const char *pass )
{
    Str file, line;
    FILE *f;
    struct stat statBuf;
    enum AuthResult res = AUTH_FAILED;

    Utl_cpyStr( file, USERSFILE );
    if ( stat( file, &statBuf ) < 0 )
    {
	Log_err( "Cannot read %s (%s)", file, strerror( errno ) );
	return AUTH_ERROR;
    }
    if ( !S_ISREG( statBuf.st_mode ) )
    {
	Log_err( "%s must be a regular file, not a link", file );
	return AUTH_ERROR;
    }
    if ( ( statBuf.st_mode & ( S_IRWXG | S_IRWXO ) ) != 0 )
    {
	Log_err( "%s must be readable only by its owner", file );
	return AUTH_ERROR;
    }
    
    if ( ! ( f = fopen( file, "r" ) ) )
    {
        Log_err( "Cannot read %s (%s)", file, strerror( errno ) );
        return AUTH_ERROR;
    }
    while ( res == AUTH_FAILED && fgets( line, MAXCHAR, f ) )
    {
	Str theUser, thePass;
	char *p;
	
        p = Utl_stripWhiteSpace( line );
	Utl_stripComment( p );

	if ( *p == '\0' )
	    continue;
	
	if ( sscanf( p, MAXCHAR_FMT " " MAXCHAR_FMT, theUser, thePass ) != 2 )
	{
	    res = AUTH_ERROR;
	    Log_err( "Badly formatted line %s in %s", p, file );
	    break;
	}

	if ( strcmp( user, theUser ) == 0 )
	{
	    if ( strcmp( pass, thePass ) == 0 )
		res = AUTH_OK;
	    break;
	}
    }

    fclose( f );

    if ( res == AUTH_FAILED )
    {
	authTries++;
	sleep( authTries * authTries );
	if ( authTries >= AUTH_MAX_TRIES )
	    res = AUTH_DISCONNECT;
    }

    return res;
}

#endif	/* USE_PAM */
#endif /* USE_AUTH */

/* Open authentication session. */
Bool
Auth_open( void )
{
#if USE_AUTH
#if USE_PAM
    return PAM_open();
#else
    return TRUE;
#endif    
#else
    return TRUE;
#endif
}

/* Authenticate a user and password. */
enum AuthResult
Auth_authenticate( const char *user, const char *pass )
{
#if USE_AUTH
#if USE_PAM
    return PAM_authenticate( user, pass );
#else
    return file_authenticate( user, pass );
#endif    
#else
    UNUSED(user);
    UNUSED(pass);
    
    return TRUE;
#endif    
}

/* Authentication session now closed. */
void
Auth_close( void )
{
#if USE_AUTH && USE_PAM
    PAM_close();
#endif    
}

static uid_t noffleUid = (uid_t) -1;
static gid_t noffleGid= (gid_t) -1;
static Bool adminUser = FALSE;

/* Check we have appropriate privs for authentication. */
Bool
Auth_checkPrivs( void )
{
    uid_t euid;
    gid_t egid;
    uid_t ruid;
    struct passwd* pwnam;
    struct group* grnam;

    euid = geteuid();
    egid = getegid();
    
    pwnam = getpwnam( Cfg_noffleUser() );
    if ( pwnam == NULL )
    {
	Log_err( "Noffle user %s is not a known user", Cfg_noffleUser() );
	return FALSE;
    }
    noffleUid = pwnam->pw_uid;

    grnam = getgrnam( Cfg_noffleGroup() );
    if ( grnam == NULL )
    {
	Log_err( "Noffle group %s is not a known group", Cfg_noffleGroup() );
	return FALSE;
    }
    noffleGid = grnam->gr_gid;
    
    ruid = getuid();

    /* Determine if admin user - root, news... */
    adminUser = ( ruid == 0 || ruid == noffleUid );
    if ( ! adminUser && grnam->gr_mem != NULL )
    {
	/* ... or member of group news. */
	pwnam = getpwuid( ruid );
	if ( pwnam != NULL )
	{
	    char* name = pwnam->pw_name;
	    char** grpmembers = grnam->gr_mem;
	    char* grpmember;

	    for ( grpmember = *grpmembers;
		  grpmember != NULL;
		  grpmember = *++grpmembers )
	    {
		if ( strcmp( name, grpmember ) == 0 )
		{
		    adminUser = TRUE;
		    break;
		}
	    }
	}
	else
	    Log_err( "Cannot get user info for uid %d: %s",
		     ruid, strerror( errno ) );
    }
    
    /*
     * If we're really root, we will set the privs we require later. Otherwise
     * we need to check that everything is as it should be.
     */
    if ( ruid != 0 )
    {
#if USE_AUTH && USE_PAM
	if( Cfg_needClientAuth() )
	{
	    Log_err( "Noffle must run as root to use PAM authentication" );
	    return FALSE;
	}
#endif
    
	if ( noffleUid != euid )
	{
	    Log_err( "Noffle needs to run as root or user %s", Cfg_noffleUser() );
	    return FALSE;
	}

	if ( noffleGid != egid )
	{
	    Log_err( "Noffle needs to run as root or as group %s",
		     Cfg_noffleGroup() );
	    return FALSE;
	}
    }
    
    return TRUE;
}

/*
 * See if we should be permitted admin access. Admins can do anything,
 * non-admins can only read articles, list groups and post.
 *
 * This must be called after Auth_checkPrivs.
 */
Bool
Auth_admin( void )
{
    ASSERT( noffleUid != (uid_t) -1 && noffleGid != (gid_t) -1 );

    return adminUser;
}


/*
 * Drop any privs required for authentication.
 *
 * Must be called AFTER Auth_checkPrivs.
 */
Bool
Auth_dropPrivs( void )
{
    uid_t euid;

    ASSERT( noffleUid != (uid_t) -1 && noffleGid != (gid_t) -1 );

    /*
     * We only need to drop privs if we're currently root. We
     * should have already checked we're the news user on startup.
     */
    euid = geteuid();
    if ( euid != 0 )
	return TRUE;

    if ( setgid( noffleGid ) != 0 )
    {
	Log_err( "Can't set group %s: %s",
		 Cfg_noffleGroup(), strerror( errno ) );
	return FALSE;
    }

    if ( setuid( noffleUid ) != 0 )
    {
	Log_err( "Can't set user to %s: %s",
		 Cfg_noffleUser(), strerror( errno ) );
	return FALSE;
    }

    return TRUE;
}