view src/authenticate.c @ 288:c02c4eb95f95 noffle

[svn] * src/configfile.h,src/configfile.c,docs/noffle.conf.5: Add noffle-user and noffle-group configs. * src/configfile.c,src/fetch.c,src/fetchlist.c,src/protocol.c, src/server.c: Replace strcpy() with Utl_cpyStr() where appropriate. See Debian bug 168128. * src/control.c,src/configfile.c,src/noffle.c: Replace [s]scanf("%s") with [s]scanf(MAXCHAR_FMT). * src/noffle.c: Log warning if noffle.conf is world readable. * src/noffle.c: Restrict most options to news admins; i.e. those who are root or news on running Noffle. * Makefile.in,acconfig.h,aclocal.m4,config.h.in,configure,configure.in, docs/Makefile.in,docs/noffle.conf.5,packages/Makefile.in, packages/redhat/Makefile.in,src/Makefile.am,src/Makefile.in, src/authenticate.c,src/authenticate.h,src/noffle.c,src/server.c: Add basic authentication using either Noffle-specific user file or authenticating via PAM (service 'noffle'). PAM authentication needs to run as root, so a Noffle server that needs PAM must be started by root. Helpful (?) error messages will be logged if not. Noffle will switch ruid and euid to 'news' (or whatever is configured) ASAP. * src/noffle.c: Add uid checking.
author bears
date Fri, 10 Jan 2003 23:25:45 +0000
parents
children bf200dccbce5
line wrap: on
line source

/*
  authenticate.c

  Do client authentication

  $Id: authenticate.c 420 2003-01-10 23:25:45Z bears $
*/

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

#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.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();
    adminUser = ( ruid == 0 || ruid == noffleUid );
    
    /*
     * 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;
}