@ -17,9 +17,16 @@
*/
# define _POSIX_C_SOURCE 200809L
# ifdef __linux__
# define _DEFAULT_SOURCE
# else
# define _BSD_SOURCE
# endif
# include <sys/types.h>
# include <sys/wait.h>
# include <sys/stat.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
@ -28,34 +35,111 @@
# include <unistd.h>
# include <limits.h>
# include <grp.h>
# include <security/pam_appl.h>
# include <security/pam_misc.h>
# include <termios.h>
# include <stdarg.h>
# include <ctype.h>
# include <signal.h>
# include <time.h>
# include <fcntl.h>
# include <syslog.h>
# if !defined(_XOPEN_CRYPT) || _XOPEN_CRYPT == -1
# include <crypt.h>
# endif
# if defined(__linux__)
# include <shadow.h>
# endif
# define MAX_HASH 1024
# define PASS_MAX 1024
# define CONF_LINE_MAX 1024
# define GROUPS_MAX 256
# define STR_MAX 1024
# define FLAG_PERSIST 0x1
# define FLAG_NOPASS 0x2
# define FLAG_NOLOG 0x4
# define SESSION_FILE_DIR " / var / run"
# define SESSION_TIMEOUT (60*5)
# define FAIL_PAUSE 3
static void usage ( void ) ;
static int perm_set ( struct passwd * , struct group * ) ;
static int authenticate ( const char * ) ;
static struct passwd * user_to_passwd ( const char * ) ;
static struct group * group_to_grp ( const char * ) ;
//static int execvpe(const char *, char *const *, char *const *);
struct env_elem {
char * name ;
char * value ;
} ;
struct config {
int type ;
int flags ;
char * who ;
char * as ;
struct env_elem * env ;
int env_n ;
} ;
struct user_info {
union {
struct passwd pw ;
struct group gr ;
} d ;
char str [ STR_MAX ] ;
} ;
// FIXME: misc_conv is a separate library, should stick to plain PAM or make
// our own pam module
static struct pam_conv conv = { misc_conv , NULL } ;
static void * emalloc ( size_t ) ;
static char * estrdup ( const char * ) ;
void * erealloc ( void * , size_t ) ;
static void usage ( int ) ;
static void die ( const char * , . . . ) ;
static int perm_set ( struct passwd * , struct group * ) ;
static int authenticate ( uid_t , int , int ) ;
static struct passwd * user_to_passwd ( const char * , struct user_info * ) ;
static struct group * group_to_grp ( const char * , struct user_info * ) ;
static int get_config ( struct config * * , int * ) ;
extern char * * environ ;
char * config_file = " /etc/us.conf " ;
int tty_fd = STDOUT_FILENO ;
struct termios tio_before = { 0 } ;
void int_handler ( int signum )
{
( void ) signum ;
if ( tio_before . c_iflag | | tio_before . c_oflag | | tio_before . c_iflag )
tcsetattr ( tty_fd , TCSANOW , & tio_before ) ;
putchar ( ' \n ' ) ;
exit ( signum ) ;
}
int main ( int argc , char * argv [ ] )
{
char * t_usr = " root " , * t_grp = NULL ;
struct passwd * t_pw ;
struct group * t_gr ;
struct user_info t_gr_info = { 0 } , t_pw_info = { 0 } ;
int opt , err ;
int shellflag = 0 , envflag = 0 ;
while ( ( opt = getopt ( argc , argv , " A:u:g:C:se " ) ) ! = - 1 ) {
int shellflag = 0 , envflag = 0 , askpass = 0 ;
/* Save the terminal setup, don't fail since we don't know if we'll
* need it , save it because some shells don ' t reset termios upon
* program exit , if we don ' t reset it after a SIGINT or SIGTERM then
* the controlling terminal will be stuck in no echo */
if ( tcgetattr ( tty_fd , & tio_before ) = = - 1 ) {
tio_before . c_iflag = 0 ;
tio_before . c_oflag = 0 ;
tio_before . c_cflag = 0 ;
}
struct sigaction action ;
memset ( & action , 0 , sizeof ( action ) ) ;
action . sa_handler = int_handler ;
if ( sigaction ( SIGINT , & action , NULL ) = = - 1 )
die ( " Error setting interrupt handler: " ) ;
if ( sigaction ( SIGTERM , & action , NULL ) = = - 1 )
die ( " Error setting interrupt handler: " ) ;
while ( ( opt = getopt ( argc , argv , " Au:g:C:seh " ) ) ! = - 1 ) {
switch ( opt ) {
case ' A ' :
printf ( " -A is not yet implemented \n " ) ;
exit ( EXIT_FAILURE ) ;
askpass = 1 ;
break ;
case ' u ' :
t_usr = optarg ;
@ -64,8 +148,7 @@ int main (int argc, char *argv[])
t_grp = optarg ;
break ;
case ' C ' :
printf ( " -C is not yet implemented \n " ) ;
exit ( EXIT_FAILURE ) ;
config_file = optarg ;
break ;
case ' s ' :
shellflag = 1 ;
@ -73,36 +156,146 @@ int main (int argc, char *argv[])
case ' e ' :
envflag = 1 ;
break ;
case ' h ' :
usage ( 1 ) ;
exit ( EXIT_SUCCESS ) ;
break ;
case ' ? ' :
usage ( ) ;
usage ( 0 ) ;
exit ( EINVAL ) ;
break ;
}
}
/* Get user info */
const char * uname ;
char * shell ;
uid_t ruid = getuid ( ) ;
struct passwd * my_pw = getpwuid ( ruid ) ;
struct passwd * my_pw = NULL ;
struct user_info my_info = { 0 } ;
getpwuid_r ( ruid , & my_info . d . pw , my_info . str , STR_MAX , & my_pw ) ;
if ( ! my_pw ) {
fprintf ( stderr , " getpwid: %s \n " , strerror ( errno ) ) ;
return errno ;
}
uname = my_pw - > pw_name ;
/* Authenticate */
if ( authenticate ( uname ) ! = PAM_SUCCESS )
exit ( EXIT_FAILURE ) ;
char * my_name = my_pw - > pw_name ;
gid_t my_groups [ GROUPS_MAX ] ;
int n_groups = 0 ;
if ( ( n_groups = getgroups ( GROUPS_MAX , my_groups ) ) = = - 1 )
die ( " getgroups: " ) ;
/* Get target user and group info */
t_pw = user_to_passwd ( t_usr ) ;
if ( ! t_pw ) {
fprintf ( stderr , " user_to_passwd: %s \n " , strerror ( errno ) ) ;
return errno ;
t_pw = user_to_passwd ( t_usr , & t_pw_info ) ;
if ( ! t_pw )
die ( " user_to_passwd: " ) ;
t_gr = group_to_grp ( t_grp , & t_gr_info ) ;
gid_t t_groups [ GROUPS_MAX ] ;
int nt_groups = GROUPS_MAX ;
if ( getgrouplist ( t_pw - > pw_name , t_pw - > pw_gid , t_groups , & nt_groups ) = = - 1 )
die ( " getgrouplist: " ) ;
/* Don't have to wait for children */
struct sigaction sa = { 0 } ;
sa . sa_handler = SIG_DFL ;
sa . sa_flags = SA_NOCLDWAIT ;
if ( sigaction ( SIGCHLD , & sa , NULL ) = = - 1 )
die ( " sigaction: " ) ;
/* From now on most actions require root */
if ( setuid ( 0 ) = = - 1 )
die ( " setuid: " ) ;
if ( setgid ( 0 ) = = - 1 )
die ( " setgid: " ) ;
/* get info from the config file and check if the action we want to
* do is permitted */
struct env_elem * env_extra = NULL ;
struct config * conf = NULL ;
int conf_num , conf_flags = 0 , env_extra_n = 0 ;
if ( get_config ( & conf , & conf_num ) < = 0 )
die ( " get_config: invalid config " ) ;
int here = 0 ;
for ( int i = 0 ; i < conf_num ; i + + ) {
struct passwd * who_pw , * as_pw ;
struct group * who_gr , * as_gr ;
struct user_info who_info = { 0 } , as_info = { 0 } ;
int who_usr = conf [ i ] . who [ 0 ] = = ' : ' ? 0 : 1 ;
int as_usr = conf [ i ] . as [ 0 ] = = ' : ' ? 0 : 1 ;
if ( who_usr ) {
who_pw = user_to_passwd ( conf [ i ] . who , & who_info ) ;
if ( ! who_pw )
die ( " %s not a valid user " , conf [ i ] . who ) ;
if ( my_pw - > pw_uid ! = who_pw - > pw_uid )
continue ;
} else {
who_gr = group_to_grp ( conf [ i ] . who + 1 , & who_info ) ;
if ( ! who_gr )
die ( " %s not a valid group " , conf [ i ] . who ) ;
gid_t w_gid = who_gr - > gr_gid ;
int x = 0 ;
for ( ; x < n_groups & & w_gid ! = my_groups [ x ] ; x + + ) ;
if ( x = = n_groups )
continue ;
}
if ( as_usr ) {
as_pw = user_to_passwd ( conf [ i ] . as , & as_info ) ;
if ( ! as_pw )
die ( " %s not a valid user " , conf [ i ] . as ) ;
if ( t_pw - > pw_uid ! = as_pw - > pw_uid )
continue ;
} else {
as_gr = group_to_grp ( conf [ i ] . as + 1 , & as_info ) ;
if ( ! as_gr )
die ( " %s not a valid group " , conf [ i ] . as ) ;
gid_t a_gid = as_gr - > gr_gid ;
int x = 0 ;
for ( ; x < nt_groups & & a_gid ! = t_groups [ x ] ; x + + ) ;
if ( x = = nt_groups )
continue ;
}
here = 1 ;
if ( conf [ i ] . type = = 0 )
die ( " Permission denied " ) ;
conf_flags | = conf [ i ] . flags ;
if ( conf [ i ] . env_n ) {
env_extra = erealloc ( env_extra ,
( env_extra_n + conf [ i ] . env_n + 1 ) * sizeof ( struct env_elem ) ) ;
for ( int j = env_extra_n , x = 0 ; j < env_extra_n + conf [ i ] . env_n ; j + + , x + + )
env_extra [ j ] = conf [ i ] . env [ x ] ;
env_extra_n + = conf [ i ] . env_n ;
env_extra [ env_extra_n ] = ( struct env_elem ) { NULL , NULL } ;
}
}
/* We don't need conf anymore */
for ( int i = 0 ; i < conf_num ; i + + ) {
free ( conf [ i ] . who ) ;
free ( conf [ i ] . as ) ;
if ( conf [ i ] . env & & conf [ i ] . env_n )
free ( conf [ i ] . env ) ;
}
free ( conf ) ;
/* No configuration was fount, can't proceed */
if ( ! here )
die ( " no rule found for user %s " , my_name ) ;
/* Authenticate, we will be root from now on */
if ( ! ( conf_flags & FLAG_NOPASS ) )
if ( authenticate ( my_pw - > pw_uid , askpass , conf_flags & FLAG_PERSIST ) ) {
if ( ! ( conf_flags & FLAG_NOLOG ) )
exit ( EXIT_FAILURE ) ;
char cmd [ 1024 ] = { 0 } ;
for ( int i = optind , x = 0 ; argv [ i ] & & x < 1024 ; i + + )
x + = snprintf ( cmd , 1024 - x , " %s " , argv [ i ] ) ;
openlog ( " us " , LOG_NOWAIT , LOG_AUTH ) ;
syslog ( LOG_NOTICE , " user %s tried to run %s as %s "
" but failed " , my_name , cmd , t_pw - > pw_name ) ;
closelog ( ) ;
exit ( EXIT_FAILURE ) ;
}
t_gr = group_to_grp ( t_grp ) ;
/* Get target user's shell */
if ( ! shellflag )
@ -114,40 +307,18 @@ int main (int argc, char *argv[])
/* Set argc and argv */
int c_argc = argc - optind ;
char * * c_argv ;
char * * c_argv = NULL ;
if ( c_argc ) {
c_argv = malloc ( sizeof ( char * ) * ( c_argc + 1 ) ) ;
if ( ! c_argv ) {
fprintf ( stderr , " malloc: %s \n " , strerror ( errno ) ) ;
exit ( errno ) ;
}
for ( int i = 0 ; optind < argc ; optind + + , i + + ) {
c_argv [ i ] = strdup ( argv [ optind ] ) ;
if ( ! c_argv [ i ] ) {
fprintf ( stderr , " strdup: %s \n " , strerror ( errno ) ) ;
exit ( errno ) ;
}
}
c_argv = emalloc ( sizeof ( char * ) * ( c_argc + 1 ) ) ;
for ( int i = 0 ; optind < argc ; optind + + , i + + )
c_argv [ i ] = estrdup ( argv [ optind ] ) ;
} else {
c_argc = 1 ;
c_argv = malloc ( sizeof ( char * ) * ( c_argc + 1 ) ) ;
if ( ! c_argv ) {
fprintf ( stderr , " malloc: %s \n " , strerror ( errno ) ) ;
exit ( errno ) ;
}
c_argv [ 0 ] = strdup ( shell ) ;
if ( ! c_argv [ 0 ] ) {
fprintf ( stderr , " strdup: %s \n " , strerror ( errno ) ) ;
exit ( errno ) ;
}
c_argv = emalloc ( sizeof ( char * ) * ( c_argc + 1 ) ) ;
c_argv [ 0 ] = estrdup ( shell ) ;
}
c_argv [ c_argc ] = NULL ;
struct env_elem {
char * name ;
char * value ;
} ;
struct env_elem env_keep [ ] = {
{ " PATH " , NULL } ,
{ " TERM " , NULL } ,
@ -166,10 +337,12 @@ int main (int argc, char *argv[])
{ NULL , NULL }
} ;
if ( envflag ) { /* clear env */
/* Copy what has to be copied and then clear the environment, we'll
* make a fresh one later */
if ( envflag ) {
for ( int i = 0 ; env_keep [ i ] . name ; i + + )
env_keep [ i ] . value = strdup ( getenv ( env_keep [ i ] . name ) ) ;
environ = NULL ; // in place of clearenv
env_keep [ i ] . value = e strdup( getenv ( env_keep [ i ] . name ) ) ;
environ = NULL ;
}
for ( int i = 0 ; env_mod [ i ] . name ; i + + ) {
@ -180,8 +353,7 @@ int main (int argc, char *argv[])
}
}
if ( envflag ) {
for ( int i = 0 ; env_keep [ i ] . name ; i + + ) {
for ( int i = 0 ; envflag & & env_keep [ i ] . name ; i + + ) {
if ( env_keep [ i ] . value ) {
err = setenv ( env_keep [ i ] . name , env_keep [ i ] . value , 1 ) ;
if ( err = = - 1 ) {
@ -190,9 +362,21 @@ int main (int argc, char *argv[])
}
}
}
for ( int i = 0 ; env_extra & & env_extra [ i ] . name ; i + + ) {
if ( env_extra [ i ] . value ) {
err = setenv ( env_extra [ i ] . name , env_extra [ i ] . value , 1 ) ;
if ( err = = - 1 ) {
fprintf ( stderr , " setenv: %s \n " , strerror ( errno ) ) ;
goto fail_end ;
}
} else {
unsetenv ( env_extra [ i ] . name ) ;
}
}
// do not override, we might be under more levels of 'us'
err = setenv ( " US_USER " , my_pw - > pw_name , 0 ) ;
/* Do not override, we might be under more levels of 'us' */
err = setenv ( " US_USER " , my_name , 0 ) ;
errno = 0 ;
/* Set permissions */
@ -201,10 +385,19 @@ int main (int argc, char *argv[])
goto fail_end ;
}
if ( ! ( conf_flags & FLAG_NOLOG ) ) {
char cmd [ 1024 ] = { 0 } ;
for ( int i = 0 , x = 0 ; c_argv [ i ] & & x < 1024 ; i + + )
x + = snprintf ( cmd , 1024 - x , " %s " , c_argv [ i ] ) ;
openlog ( " us " , LOG_NOWAIT , LOG_AUTH ) ;
syslog ( LOG_INFO , " user %s ran %s as %s " , my_name , cmd , t_pw - > pw_name ) ;
closelog ( ) ;
}
/* Execute the command */
err = execvp ( c_argv [ 0 ] , c_argv ) ;
if ( err = = - 1 )
fprintf ( stderr , " execl: %s \n " , strerror ( errno ) ) ;
fprintf ( stderr , " execvp : %s \n " , strerror ( errno ) ) ;
/* Cleanup and return */
fail_end :
@ -215,17 +408,18 @@ int main (int argc, char *argv[])
return errno ;
}
static inline void usage ( void )
static inline void usage ( int complete )
{
// TODO: planned options
// -a [program]: like sudo's askpass
// -u [user]: change the default user from root to user
// -g [group]: change the primary group to [gorup]
// both -a and -g will accept numbers with #[num] like sudo
// -c [file]: manually select config file
// something about environment
// something about non interactiveness
printf ( " usage: us [-se] [-u user] [-g group] command [args] \n " ) ;
printf ( " usage: us [-hseA] [-u user] [-g group] [-C config] command [args] \n " ) ;
if ( ! complete )
return ;
printf ( " -h print this message \n "
" -s use the user's shell instead of /bin/sh \n "
" -e keep the user's entire environment \n "
" -A use the command in US_ASKPASS as askpass helper \n "
" -u user set new user to 'user' instead of root \n "
" -g group set new group to 'group' \n "
" -C config use specifi config file \n " ) ;
}
static int perm_set ( struct passwd * pw , struct group * gr )
@ -268,44 +462,196 @@ static int perm_set (struct passwd *pw, struct group *gr)
return 0 ;
}
static int authenticate ( const char * uname )
static int authenticate ( uid_t uid , int ask , int persist )
{
pam_handle_t * pamh ;
int pam_err , count = 0 ;
do {
pam_err = pam_start ( " User Switcher " , uname , & conv , & pamh ) ;
if ( pam_err ! = PAM_SUCCESS ) {
fprintf ( stderr , " pam_start: %s \n " , pam_strerror ( pamh , pam_err ) ) ;
return pam_err ;
// TODO: implement u2f compat
// TODO: check root access, maybe
/* try to check if a valid saved session exists */
char tmp_file [ 512 ] = { 0 } ;
if ( persist ) {
pid_t sid = getsid ( 0 ) ;
if ( sid = = ( pid_t ) - 1 )
die ( " getsid: " ) ;
if ( snprintf ( tmp_file , 512 , SESSION_FILE_DIR " /us.%d " , sid ) > = 512 )
die ( " snprintf: output truncated " ) ;
int session_file = open ( tmp_file , O_RDONLY | O_CLOEXEC ) ;
int valid_session = 1 ;
struct timespec now_t [ 2 ] = { 0 } , old_t [ 2 ] = { 0 } ;
if ( session_file ! = - 1 ) {
struct stat st ;
if ( fstat ( session_file , & st ) = = - 1 ) {
valid_session = 0 ;
} else if ( st . st_uid ! = 0 | | st . st_gid ! = 0 ) {
valid_session = 0 ;
} else if ( ! S_ISREG ( st . st_mode ) ) {
valid_session = 0 ;
} else if ( st . st_mode & S_IRWXO | |
st . st_mode & S_IROTH | |
st . st_mode & S_IWOTH | | st . st_mode & S_IXOTH ) {
valid_session = 0 ;
} else {
int r = read ( session_file , old_t , sizeof ( struct timespec ) * 2 ) ;
if ( r ! = sizeof ( struct timespec ) * 2 )
valid_session = 0 ;
close ( session_file ) ;
}
pam_err = pam_authenticate ( pamh , 0 ) ;
if ( pam_err = = PAM_SUCCESS ) {
pam_err = pam_acct_mgmt ( pamh , 0 ) ;
} else {
valid_session = 0 ;
}
if ( pam_err ! = PAM_SUCCESS ) {
printf ( " Auth failed: %s \n " , pam_strerror ( pamh , pam_err ) ) ;
pam_end ( pamh , pam_err ) ;
if ( valid_session & & session_file ! = - 1 ) {
if ( clock_gettime ( CLOCK_MONOTONIC , & ( now_t [ 0 ] ) ) = = - 1 )
die ( " clock_gettime: " ) ;
if ( clock_gettime ( CLOCK_REALTIME , & ( now_t [ 1 ] ) ) = = - 1 )
die ( " clock_gettime: " ) ;
if ( now_t [ 0 ] . tv_sec < = old_t [ 0 ] . tv_sec | |
old_t [ 0 ] . tv_sec < now_t [ 0 ] . tv_sec - SESSION_TIMEOUT | |
now_t [ 1 ] . tv_sec < = old_t [ 1 ] . tv_sec | |
old_t [ 1 ] . tv_sec < now_t [ 1 ] . tv_sec - SESSION_TIMEOUT )
valid_session = 0 ;
}
if ( valid_session )
return 0 ;
}
/* get the encrypted password */
struct passwd * pw = getpwuid ( uid ) ;
char * hash_p , hash [ MAX_HASH ] ;
char * p = pw - > pw_passwd ;
if ( ! strcmp ( p , " x " ) | | * p = = ' * ' | | * p = = ' ! ' ) {
# if defined(__linux__)
/* Get exclusive access to the shadow file, then read
* this should prevent any race conditions */
if ( lckpwdf ( ) = = - 1 )
die ( " lckpwdf " ) ;
setspent ( ) ;
struct spwd * sp = getspnam ( pw - > pw_name ) ;
if ( sp = = NULL )
die ( " getspnam " ) ;
hash_p = sp - > sp_pwdp ;
endspent ( ) ;
ulckpwdf ( ) ;
# elif defined(__OpenBSD__)
struct passwd * op = getpwuid_shadow ( uid ) ;
if ( ! op )
die ( " getpwuid_shadow: " ) ;
hash_p = op - > pw_passwd ;
# endif
} else {
hash_p = pw - > pw_passwd ;
}
strncpy ( hash , hash_p , MAX_HASH - 2 ) ;
hash [ MAX_HASH - 1 ] = ' \0 ' ;
if ( strlen ( hash ) > = MAX_HASH - 3 )
die ( " password hash too long :^) " ) ;
int fd = STDIN_FILENO ;
char * askpass = getenv ( " US_ASKPASS " ) ;
char pass [ PASS_MAX ] = { 0 } ;
struct termios tio_pass ;
if ( ask & & askpass ) {
pid_t pid , parent = getpid ( ) ;
int pipefd [ 2 ] ;
if ( pipe ( pipefd ) = = - 1 )
die ( " pipe: " ) ;
switch ( pid = fork ( ) ) {
case - 1 :
die ( " fork: " ) ;
break ;
case 0 :
/* we are still root, drop off permissions before
* disasters happen , also in case askpass fails
* before sending anything to stdout , terminate tha
* main process since it would hang forever */
if ( setuid ( uid ) = = - 1 ) {
kill ( parent , SIGTERM ) ;
die ( " askpass: setuid: " ) ;
}
if ( dup2 ( pipefd [ 1 ] , STDOUT_FILENO ) = = - 1 ) {
kill ( parent , SIGTERM ) ;
die ( " askpass: dup2: " ) ;
}
close ( pipefd [ 0 ] ) ;
execl ( " /bin/sh " , " sh " , " -c " , askpass , ( char * ) NULL ) ;
kill ( parent , SIGTERM ) ;
die ( " askpass: execl: " ) ;
break ;
default :
fd = pipefd [ 0 ] ;
close ( pipefd [ 1 ] ) ;
break ;
}
} else {
printf ( " Password (%s): " , pw - > pw_name ) ;
fflush ( stdout ) ;
if ( tcgetattr ( tty_fd , & tio_before ) = = - 1 )
die ( " tcgetattr: " ) ;
tio_pass = tio_before ;
/* Do not echo and accept when enter is pressed */
tio_pass . c_lflag & = ~ ECHO ;
tio_pass . c_lflag | = ICANON ;
if ( tcsetattr ( tty_fd , TCSANOW , & tio_pass ) = = - 1 )
die ( " tcsetattr: " ) ;
}
int r = read ( fd , pass , PASS_MAX - 1 ) ;
if ( ! r | | r = = - 1 ) {
if ( errno )
fprintf ( stderr , " read: %s \n " , strerror ( errno ) ) ;
else
printf ( " Password can't be zero length \n " ) ;
/* read() may have been interrupted, wait askpass even tough it
* should not be necessary before bailing */
waitpid ( - 1 , NULL , 0 ) ;
exit ( EXIT_FAILURE ) ;
}
pass [ PASS_MAX - 1 ] = ' \0 ' ;
/* Remove the terminating (if there is) \n in password */
int l = strlen ( pass ) ;
if ( pass [ l - 1 ] = = ' \n ' )
pass [ - - l ] = ' \0 ' ;
count + + ;
} while ( pam_err ! = PAM_SUCCESS & & count < 3 ) ;
if ( ask & & askpass ) {
close ( fd ) ;
} else {
if ( tcsetattr ( tty_fd , TCSANOW , & tio_before ) = = - 1 )
die ( " tcsetattr: " ) ;
}
if ( pam_err ! = PAM_SUCCESS ) {
fprintf ( stderr , " better luck next time \n " ) ;
return pam_err ;
char * enc = crypt ( pass , hash ) ;
/* Remove password from memory, just to be sure */
memset ( pass , 0 , PASS_MAX ) ;
if ( strncmp ( hash , enc , PASS_MAX ) ) {
sleep ( FAIL_PAUSE ) ;
printf ( " Authentication failure \n " ) ;
setuid ( uid ) ;
return - 1 ;
}
// FIXME: check again for the validity of the login for more security
// as in: https://docs.oracle.com/cd/E19120-01/open.solaris/819-2145/pam-20/index.html
// FIXME: ^C [SIGINT] will interrupt this call possibly causing a
// vulnerability
printf ( " \n " ) ;
return pam_end ( pamh , pam_err ) ;
if ( persist ) {
if ( ! tmp_file [ 0 ] )
return 0 ;
int session_file = creat ( tmp_file , S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP ) ;
if ( session_file = = - 1 )
return 0 ;
chown ( tmp_file , 0 , 0 ) ;
struct timespec t [ 2 ] = { 0 } ;
if ( clock_gettime ( CLOCK_MONOTONIC , & ( t [ 0 ] ) ) = = - 1 )
die ( " clock_gettime: " ) ;
if ( clock_gettime ( CLOCK_REALTIME , & ( t [ 1 ] ) ) = = - 1 )
die ( " clock_gettime: " ) ;
write ( session_file , t , sizeof ( struct timespec ) * 2 ) ;
close ( session_file ) ;
}
return 0 ;
}
static struct passwd * user_to_passwd ( const char * user )
/* user_to_passwd() and group_to_passwd() both receive the user/group name
* in string form , if the sring begins with ' # ' then the following number
* is interpreted as the uid / gid and used instead of the name .
* Both functions return a pointer to a passwd / group struct stored inside
* the info structure */
static struct passwd * user_to_passwd ( const char * user , struct user_info * info )
{
if ( ! user ) {
errno = EINVAL ;
@ -316,26 +662,22 @@ static struct passwd* user_to_passwd (const char *user)
long uid_l ;
errno = 0 ;
if ( user [ 0 ] ! = ' # ' ) {
pw = getpwnam ( user ) ;
getpwnam_r ( user , & ( info - > d . pw ) , info - > str , STR_MAX , & pw ) ;
} else {
uid_l = strtol ( & user [ 1 ] , NULL , 10 ) ;
if ( uid_l < 0 | | errno ) {
errno = errno ? errno : EINVAL ;
return NULL ;
}
pw = getpwuid ( ( uid_t ) uid_l ) ;
getpwuid_r ( ( uid_t ) uid_l , & ( info - > d . pw ) , info - > str , STR_MAX , & pw ) ;
}
if ( ! pw ) {
if ( ! errno )
if ( ! pw & & ! errno )
errno = EINVAL ;
return NULL ;
}
return pw ;
}
static struct group * group_to_grp ( const char * group )
static struct group * group_to_grp ( const char * group , struct user_info * info )
{
if ( ! group ) {
errno = EINVAL ;
@ -346,20 +688,168 @@ static struct group* group_to_grp (const char *group)
long gid_l ;
errno = 0 ;
if ( group [ 0 ] ! = ' # ' ) {
gr = getgrnam ( group ) ;
getgrnam_r ( group , & ( info - > d . gr ) , info - > str , STR_MAX , & gr ) ;
} else {
gid_l = strtol ( & group [ 1 ] , NULL , 10 ) ;
if ( gid_l < 0 | | errno ) {
errno = errno ? errno : EINVAL ;
return NULL ;
}
gr = getgrgid ( ( gid_t ) gid_l ) ;
getgrgid_r ( ( gid_t ) gid_l , & ( info - > d . gr ) , info - > str , STR_MAX , & gr ) ;
}
if ( ! gr ) {
if ( ! errno )
if ( ! gr & & ! errno )
errno = EINVAL ;
return NULL ;
}
return gr ;
}
/* die() emalloc() estrdup() and erealloc() are all helper functions, wrappers
* to respective functions that use die ( ) to give a message and then fail */
void die ( const char * fmt , . . . )
{
va_list ap ;
va_start ( ap , fmt ) ;
vfprintf ( stderr , fmt , ap ) ;
if ( fmt [ 0 ] & & fmt [ strlen ( fmt ) - 1 ] = = ' : ' ) {
fputc ( ' ' , stderr ) ;
perror ( NULL ) ;
} else {
fputc ( ' \n ' , stderr ) ;
}
va_end ( ap ) ;
exit ( errno ? errno : EXIT_FAILURE ) ;
}
void * emalloc ( size_t s )
{
if ( ! s | | s = = ( size_t ) - 1 )
die ( " bad malloc: invalid size " ) ;
void * p = calloc ( 1 , s ) ;
if ( ! p )
die ( " bad malloc: " ) ;
return p ;
}
void * erealloc ( void * p , size_t s )
{
if ( ! s | | s = = ( size_t ) - 1 )
die ( " bad realloc: invalid size " ) ;
void * r = realloc ( p , s ) ;
if ( ! r )
die ( " bad realloc: " ) ;
return r ;
}
char * estrdup ( const char * s )
{
if ( ! s )
die ( " bad strdup: cannot duplicate NULL pointer " ) ;
char * r = strdup ( s ) ;
if ( ! r )
die ( " bad strdup: " ) ;
return r ;
}
/* Parses the config file and stores the result in a config vector pointed
* by conf of size num */
static int get_config ( struct config * * conf , int * num )
{
if ( ! conf | | ! num )
return - 1 ;
FILE * fp = fopen ( config_file , " r " ) ;
if ( ! fp )
die ( " fopen: " ) ;
struct stat st ;
if ( fstat ( fileno ( fp ) , & st ) = = - 1 )
die ( " fstat: " ) ;
if ( st . st_uid ! = 0 | | st . st_gid ! = 0 )
die ( " config file must be owned by root:root " ) ;
if ( ! S_ISREG ( st . st_mode ) )
die ( " config file must be a regular file " ) ;
if ( st . st_mode & S_IRWXO | | st . st_mode & S_IROTH | |
st . st_mode & S_IWOTH | | st . st_mode & S_IXOTH )
die ( " others may not modify, read or execute config file \n "
" suggested permissions for the config file: 660 " ) ;
char line [ CONF_LINE_MAX ] ;
* num = 0 ;
* conf = NULL ;
for ( int i = 0 ; fgets ( line , CONF_LINE_MAX , fp ) ; i + + ) {
char * s , * t , * sv ;
int n = 0 ;
for ( int ll = strlen ( line ) - 1 ; isspace ( line [ ll ] ) ; line [ ll - - ] = ' \0 ' ) ;
struct config c = { 0 } ;
for ( s = line ; ; s = NULL , n + + ) {
int getflags = 0 ;
if ( ! ( t = strtok_r ( s , " \t " , & sv ) ) )
break ;
if ( * t = = ' # ' )
break ;
switch ( n ) {
case 0 :
if ( ! strcmp ( t , " permit " ) )
c . type = 1 ;
else if ( ! strcmp ( t , " deny " ) )
c . type = 0 ;
else
die ( " non valid config line %d " , i ) ;
break ;
case 1 :
c . who = estrdup ( t ) ;
break ;
case 2 :
if ( strcmp ( t , " as " ) )
die ( " non valid config line %d " , i ) ;
break ;
case 3 :
c . as = estrdup ( t ) ;
break ;
default :
getflags = 1 ;
break ;
}
if ( ! getflags )
continue ;
if ( isupper ( * t ) ) {
char * e , * se , * et ;
for ( e = t ; ; e = NULL ) {
if ( ! ( et = strtok_r ( e , " , " , & se ) ) )
break ;
char * sep ;
int x ;
if ( ! ( sep = strchr ( et , ' , ' ) ) )
die ( " invalid env at %d " , i ) ;
c . env = erealloc ( c . env , ( c . env_n + 1 ) * sizeof ( struct env_elem ) ) ;
x = c . env_n ;
* sep = ' \0 ' ;
for ( char * p = et ; * p ; p + + )
if ( ! isupper ( * p ) & & * p ! = ' - '
& & * p ! = ' _ ' )
die ( " non valid "
" env at %d " , i ) ;
c . env [ x ] . name = estrdup ( et ) ;
c . env [ x ] . value = estrdup ( sep + 1 ) ;
c . env_n + + ;
}
} else if ( ! strcmp ( t , " persist " ) ) {
c . flags | = FLAG_PERSIST ;
} else if ( ! strcmp ( t , " nopass " ) ) {
c . flags | = FLAG_NOPASS ;
} else if ( ! strcmp ( t , " nolog " ) ) {
c . flags | = FLAG_NOLOG ;
} else {
die ( " flag %s not recognized at %d " , t , i ) ;
}
}
if ( n < 3 )
die ( " non valid config line %d " , i ) ;
* conf = erealloc ( * conf , ( ( * num ) + 1 ) * sizeof ( struct config ) ) ;
( * conf ) [ ( * num ) ] = c ;
* num + = 1 ;
}
fclose ( fp ) ;
return * num ;
}