Mercurial > noffle
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; }