|
|
@ -17,16 +17,9 @@ |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
#define _POSIX_C_SOURCE 200809L |
|
|
|
#define _POSIX_C_SOURCE 200809L |
|
|
|
#ifdef __linux__ |
|
|
|
|
|
|
|
#define _DEFAULT_SOURCE |
|
|
|
#define _DEFAULT_SOURCE |
|
|
|
#else |
|
|
|
|
|
|
|
#define _BSD_SOURCE |
|
|
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <sys/types.h> |
|
|
|
#include <sys/types.h> |
|
|
|
#include <sys/wait.h> |
|
|
|
|
|
|
|
#include <sys/stat.h> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <stdio.h> |
|
|
|
#include <stdio.h> |
|
|
|
#include <stdlib.h> |
|
|
|
#include <stdlib.h> |
|
|
|
#include <string.h> |
|
|
|
#include <string.h> |
|
|
@ -35,111 +28,34 @@ |
|
|
|
#include <unistd.h> |
|
|
|
#include <unistd.h> |
|
|
|
#include <limits.h> |
|
|
|
#include <limits.h> |
|
|
|
#include <grp.h> |
|
|
|
#include <grp.h> |
|
|
|
#include <termios.h> |
|
|
|
#include <security/pam_appl.h> |
|
|
|
#include <stdarg.h> |
|
|
|
#include <security/pam_misc.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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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]; |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
|
static void usage (void); |
|
|
|
char *config_file = "/etc/us.conf"; |
|
|
|
static int perm_set (struct passwd *, struct group *); |
|
|
|
int tty_fd = STDOUT_FILENO; |
|
|
|
static int authenticate (const char *); |
|
|
|
struct termios tio_before = {0}; |
|
|
|
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 *);
|
|
|
|
|
|
|
|
|
|
|
|
void int_handler(int signum) |
|
|
|
// FIXME: misc_conv is a separate library, should stick to plain PAM or make
|
|
|
|
{ |
|
|
|
// our own pam module
|
|
|
|
(void)signum; |
|
|
|
static struct pam_conv conv = {misc_conv, NULL}; |
|
|
|
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[]) |
|
|
|
extern char **environ; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int main (int argc, char *argv[]) |
|
|
|
{ |
|
|
|
{ |
|
|
|
char *t_usr = "root", *t_grp = NULL; |
|
|
|
char *t_usr = "root", *t_grp = NULL; |
|
|
|
struct passwd *t_pw; |
|
|
|
struct passwd *t_pw; |
|
|
|
struct group *t_gr; |
|
|
|
struct group *t_gr; |
|
|
|
struct user_info t_gr_info = {0}, t_pw_info = {0}; |
|
|
|
|
|
|
|
int opt, err; |
|
|
|
int opt, err; |
|
|
|
int shellflag = 0, envflag = 0, askpass = 0; |
|
|
|
int shellflag = 0, envflag = 0; |
|
|
|
|
|
|
|
while ((opt = getopt(argc, argv, "A:u:g:C:se")) != -1) { |
|
|
|
/* 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) { |
|
|
|
switch (opt) { |
|
|
|
case 'A': |
|
|
|
case 'A': |
|
|
|
askpass = 1; |
|
|
|
printf("-A is not yet implemented\n"); |
|
|
|
|
|
|
|
exit(EXIT_FAILURE); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'u': |
|
|
|
case 'u': |
|
|
|
t_usr = optarg; |
|
|
|
t_usr = optarg; |
|
|
@ -148,7 +64,8 @@ int main(int argc, char *argv[]) |
|
|
|
t_grp = optarg; |
|
|
|
t_grp = optarg; |
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'C': |
|
|
|
case 'C': |
|
|
|
config_file = optarg; |
|
|
|
printf("-C is not yet implemented\n"); |
|
|
|
|
|
|
|
exit(EXIT_FAILURE); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case 's': |
|
|
|
case 's': |
|
|
|
shellflag = 1; |
|
|
|
shellflag = 1; |
|
|
@ -156,146 +73,36 @@ int main(int argc, char *argv[]) |
|
|
|
case 'e': |
|
|
|
case 'e': |
|
|
|
envflag = 1; |
|
|
|
envflag = 1; |
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'h': |
|
|
|
|
|
|
|
usage(1); |
|
|
|
|
|
|
|
exit(EXIT_SUCCESS); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case '?': |
|
|
|
case '?': |
|
|
|
usage(0); |
|
|
|
usage(); |
|
|
|
exit(EINVAL); |
|
|
|
exit(EINVAL); |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* Get user info */ |
|
|
|
/* Get user info */ |
|
|
|
|
|
|
|
const char *uname; |
|
|
|
char *shell; |
|
|
|
char *shell; |
|
|
|
uid_t ruid = getuid(); |
|
|
|
uid_t ruid = getuid(); |
|
|
|
struct passwd *my_pw = NULL; |
|
|
|
struct passwd *my_pw = getpwuid(ruid); |
|
|
|
struct user_info my_info = {0}; |
|
|
|
|
|
|
|
getpwuid_r(ruid, &my_info.d.pw, my_info.str, STR_MAX, &my_pw); |
|
|
|
|
|
|
|
if (!my_pw) { |
|
|
|
if (!my_pw) { |
|
|
|
fprintf(stderr, "getpwid: %s\n", strerror(errno)); |
|
|
|
fprintf(stderr, "getpwid: %s\n", strerror(errno)); |
|
|
|
return errno; |
|
|
|
return errno; |
|
|
|
} |
|
|
|
} |
|
|
|
char *my_name = my_pw->pw_name; |
|
|
|
uname = 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 */ |
|
|
|
/* Authenticate */ |
|
|
|
t_pw = user_to_passwd(t_usr, &t_pw_info); |
|
|
|
if (authenticate(uname) != PAM_SUCCESS) |
|
|
|
if (!t_pw) |
|
|
|
exit(EXIT_FAILURE); |
|
|
|
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}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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; |
|
|
|
} |
|
|
|
} |
|
|
|
/* We don't need conf anymore */ |
|
|
|
t_gr = group_to_grp(t_grp); |
|
|
|
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); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Get target user's shell */ |
|
|
|
/* Get target user's shell */ |
|
|
|
if (!shellflag) |
|
|
|
if (!shellflag) |
|
|
@ -307,18 +114,40 @@ int main(int argc, char *argv[]) |
|
|
|
|
|
|
|
|
|
|
|
/* Set argc and argv */ |
|
|
|
/* Set argc and argv */ |
|
|
|
int c_argc = argc - optind; |
|
|
|
int c_argc = argc - optind; |
|
|
|
char **c_argv = NULL; |
|
|
|
char **c_argv; |
|
|
|
if (c_argc) { |
|
|
|
if (c_argc) { |
|
|
|
c_argv = emalloc(sizeof(char *) * (c_argc + 1)); |
|
|
|
c_argv = malloc(sizeof(char *) * (c_argc + 1)); |
|
|
|
for (int i = 0; optind < argc; optind++, i++) |
|
|
|
if (!c_argv) { |
|
|
|
c_argv[i] = estrdup(argv[optind]); |
|
|
|
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); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
c_argc = 1; |
|
|
|
c_argc = 1; |
|
|
|
c_argv = emalloc(sizeof(char *) * (c_argc + 1)); |
|
|
|
c_argv = malloc(sizeof(char *) * (c_argc + 1)); |
|
|
|
c_argv[0] = estrdup(shell); |
|
|
|
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[c_argc] = NULL; |
|
|
|
c_argv[c_argc] = NULL; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct env_elem { |
|
|
|
|
|
|
|
char *name; |
|
|
|
|
|
|
|
char *value; |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
struct env_elem env_keep[] = { |
|
|
|
struct env_elem env_keep[] = { |
|
|
|
{"PATH", NULL}, |
|
|
|
{"PATH", NULL}, |
|
|
|
{"TERM", NULL}, |
|
|
|
{"TERM", NULL}, |
|
|
@ -337,12 +166,10 @@ int main(int argc, char *argv[]) |
|
|
|
{NULL, NULL} |
|
|
|
{NULL, NULL} |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/* Copy what has to be copied and then clear the environment, we'll
|
|
|
|
if (envflag) { /* clear env */ |
|
|
|
* make a fresh one later */ |
|
|
|
|
|
|
|
if (envflag) { |
|
|
|
|
|
|
|
for (int i = 0; env_keep[i].name; i++) |
|
|
|
for (int i = 0; env_keep[i].name; i++) |
|
|
|
env_keep[i].value = estrdup(getenv(env_keep[i].name)); |
|
|
|
env_keep[i].value = strdup(getenv(env_keep[i].name)); |
|
|
|
environ = NULL; |
|
|
|
environ = NULL; // in place of clearenv
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; env_mod[i].name; i++) { |
|
|
|
for (int i = 0; env_mod[i].name; i++) { |
|
|
@ -350,33 +177,22 @@ int main(int argc, char *argv[]) |
|
|
|
if (err == -1) { |
|
|
|
if (err == -1) { |
|
|
|
fprintf(stderr, "setenv: %s\n", strerror(errno)); |
|
|
|
fprintf(stderr, "setenv: %s\n", strerror(errno)); |
|
|
|
goto fail_end; |
|
|
|
goto fail_end; |
|
|
|
} |
|
|
|
}
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
|
fprintf(stderr, "setenv: %s\n", strerror(errno)); |
|
|
|
|
|
|
|
goto fail_end; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; env_extra && env_extra[i].name; i++) { |
|
|
|
if (envflag) { |
|
|
|
if (env_extra[i].value) { |
|
|
|
for (int i = 0; env_keep[i].name; i++) { |
|
|
|
err = setenv(env_extra[i].name, env_extra[i].value, 1); |
|
|
|
if (env_keep[i].value) { |
|
|
|
if (err == -1) { |
|
|
|
err = setenv(env_keep[i].name, env_keep[i].value, 1); |
|
|
|
fprintf(stderr, "setenv: %s\n", strerror(errno)); |
|
|
|
if (err == -1) { |
|
|
|
goto fail_end; |
|
|
|
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'
|
|
|
|
/* Do not override, we might be under more levels of 'us' */ |
|
|
|
err = setenv("US_USER", my_pw->pw_name, 0); |
|
|
|
err = setenv("US_USER", my_name, 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
errno = 0; |
|
|
|
errno = 0; |
|
|
|
/* Set permissions */ |
|
|
|
/* Set permissions */ |
|
|
@ -385,19 +201,10 @@ int main(int argc, char *argv[]) |
|
|
|
goto fail_end; |
|
|
|
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 */ |
|
|
|
/* Execute the command */ |
|
|
|
err = execvp(c_argv[0], c_argv); |
|
|
|
err = execvp(c_argv[0], c_argv); |
|
|
|
if (err == -1) |
|
|
|
if (err == -1) |
|
|
|
fprintf(stderr, "execvp: %s\n", strerror(errno)); |
|
|
|
fprintf(stderr, "execl: %s\n", strerror(errno)); |
|
|
|
|
|
|
|
|
|
|
|
/* Cleanup and return */ |
|
|
|
/* Cleanup and return */ |
|
|
|
fail_end: |
|
|
|
fail_end: |
|
|
@ -408,21 +215,20 @@ int main(int argc, char *argv[]) |
|
|
|
return errno; |
|
|
|
return errno; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static inline void usage(int complete) |
|
|
|
static inline void usage (void) |
|
|
|
{ |
|
|
|
{ |
|
|
|
printf("usage: us [-hseA] [-u user] [-g group] [-C config] command [args]\n"); |
|
|
|
// TODO: planned options
|
|
|
|
if (!complete) |
|
|
|
// -a [program]: like sudo's askpass
|
|
|
|
return; |
|
|
|
// -u [user]: change the default user from root to user
|
|
|
|
printf("-h print this message\n" |
|
|
|
// -g [group]: change the primary group to [gorup]
|
|
|
|
"-s use the user's shell instead of /bin/sh\n" |
|
|
|
// both -a and -g will accept numbers with #[num] like sudo
|
|
|
|
"-e keep the user's entire environment\n" |
|
|
|
// -c [file]: manually select config file
|
|
|
|
"-A use the command in US_ASKPASS as askpass helper\n" |
|
|
|
// something about environment
|
|
|
|
"-u user set new user to 'user' instead of root\n" |
|
|
|
// something about non interactiveness
|
|
|
|
"-g group set new group to 'group'\n" |
|
|
|
printf("usage: us [-se] [-u user] [-g group] command [args]\n"); |
|
|
|
"-C config use specifi config file\n"); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static int perm_set(struct passwd *pw, struct group *gr) |
|
|
|
static int perm_set (struct passwd *pw, struct group *gr) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (!pw) { |
|
|
|
if (!pw) { |
|
|
|
errno = EINVAL; |
|
|
|
errno = EINVAL; |
|
|
@ -453,7 +259,7 @@ static int perm_set(struct passwd *pw, struct group *gr) |
|
|
|
printf("setregid failed\n"); |
|
|
|
printf("setregid failed\n"); |
|
|
|
return -1; |
|
|
|
return -1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (setreuid(uid, uid) == -1) { |
|
|
|
if (setreuid(uid, uid) == -1) { |
|
|
|
printf("setreuid failed\n"); |
|
|
|
printf("setreuid failed\n"); |
|
|
|
return -1; |
|
|
|
return -1; |
|
|
@ -462,394 +268,98 @@ static int perm_set(struct passwd *pw, struct group *gr) |
|
|
|
return 0; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static int authenticate(uid_t uid, int ask, int persist) |
|
|
|
static int authenticate (const char *uname) |
|
|
|
{ |
|
|
|
{ |
|
|
|
// TODO: implement u2f compat
|
|
|
|
pam_handle_t *pamh; |
|
|
|
// TODO: check root access, maybe
|
|
|
|
int pam_err, count = 0; |
|
|
|
/* try to check if a valid saved session exists */ |
|
|
|
|
|
|
|
char tmp_file[512] = {0}; |
|
|
|
do { |
|
|
|
if (persist) { |
|
|
|
pam_err = pam_start("User Switcher", uname, &conv, &pamh); |
|
|
|
pid_t sid = getsid(0); |
|
|
|
if (pam_err != PAM_SUCCESS) { |
|
|
|
if (sid == (pid_t)-1) |
|
|
|
fprintf(stderr, "pam_start: %s\n", pam_strerror(pamh, pam_err)); |
|
|
|
die("getsid:"); |
|
|
|
return pam_err; |
|
|
|
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); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
valid_session = 0; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (valid_session && session_file != -1) { |
|
|
|
pam_err = pam_authenticate(pamh, 0); |
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &(now_t[0])) == -1) |
|
|
|
if (pam_err == PAM_SUCCESS) { |
|
|
|
die("clock_gettime:"); |
|
|
|
pam_err = pam_acct_mgmt(pamh, 0); |
|
|
|
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; |
|
|
|
if (pam_err != PAM_SUCCESS) { |
|
|
|
} |
|
|
|
printf("Auth failed: %s\n", pam_strerror(pamh, pam_err)); |
|
|
|
/* get the encrypted password */ |
|
|
|
pam_end(pamh, pam_err); |
|
|
|
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'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (ask && askpass) { |
|
|
|
|
|
|
|
close(fd); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
if (tcsetattr(tty_fd, TCSANOW, &tio_before) == -1) |
|
|
|
|
|
|
|
die("tcsetattr:"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char *enc = crypt(pass, hash); |
|
|
|
count++; |
|
|
|
/* Remove password from memory, just to be sure */ |
|
|
|
} while (pam_err != PAM_SUCCESS && count < 3); |
|
|
|
memset(pass, 0, PASS_MAX); |
|
|
|
|
|
|
|
if (strncmp(hash, enc, PASS_MAX)) { |
|
|
|
if (pam_err != PAM_SUCCESS) { |
|
|
|
sleep(FAIL_PAUSE); |
|
|
|
fprintf(stderr, "better luck next time\n"); |
|
|
|
printf("Authentication failure\n"); |
|
|
|
return pam_err; |
|
|
|
setuid(uid); |
|
|
|
|
|
|
|
return -1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
printf("\n"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
|
// 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return pam_end(pamh, pam_err); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* user_to_passwd() and group_to_passwd() both receive the user/group name
|
|
|
|
static struct passwd* user_to_passwd (const char *user) |
|
|
|
* 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) { |
|
|
|
if (!user) { |
|
|
|
errno = EINVAL; |
|
|
|
errno = EINVAL; |
|
|
|
return NULL; |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
struct passwd *pw; |
|
|
|
struct passwd* pw; |
|
|
|
long uid_l; |
|
|
|
long uid_l; |
|
|
|
errno = 0; |
|
|
|
errno = 0; |
|
|
|
if (user[0] != '#') { |
|
|
|
if (user[0] != '#') { |
|
|
|
getpwnam_r(user, &(info->d.pw), info->str, STR_MAX, &pw); |
|
|
|
pw = getpwnam(user); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
uid_l = strtol(&user[1], NULL, 10); |
|
|
|
uid_l = strtol(&user[1], NULL, 10); |
|
|
|
if (uid_l < 0 || errno) { |
|
|
|
if (uid_l < 0 || errno) { |
|
|
|
errno = errno ? errno : EINVAL; |
|
|
|
errno = errno ? errno : EINVAL; |
|
|
|
return NULL; |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
getpwuid_r((uid_t)uid_l, &(info->d.pw), info->str, STR_MAX, &pw); |
|
|
|
pw = getpwuid((uid_t)uid_l); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!pw) { |
|
|
|
|
|
|
|
if (!errno) |
|
|
|
|
|
|
|
errno = EINVAL; |
|
|
|
|
|
|
|
return NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!pw && !errno) |
|
|
|
|
|
|
|
errno = EINVAL; |
|
|
|
|
|
|
|
return pw; |
|
|
|
return pw; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static struct group* group_to_grp(const char *group, struct user_info *info) |
|
|
|
static struct group* group_to_grp (const char *group) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (!group) { |
|
|
|
if (!group) { |
|
|
|
errno = EINVAL; |
|
|
|
errno = EINVAL; |
|
|
|
return NULL; |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
struct group *gr; |
|
|
|
struct group* gr; |
|
|
|
long gid_l; |
|
|
|
long gid_l; |
|
|
|
errno = 0; |
|
|
|
errno = 0; |
|
|
|
if (group[0] != '#') { |
|
|
|
if (group[0] != '#') { |
|
|
|
getgrnam_r(group, &(info->d.gr), info->str, STR_MAX, &gr); |
|
|
|
gr = getgrnam(group); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
gid_l = strtol(&group[1], NULL, 10); |
|
|
|
gid_l = strtol(&group[1], NULL, 10); |
|
|
|
if (gid_l < 0 || errno) { |
|
|
|
if (gid_l < 0 || errno) { |
|
|
|
errno = errno ? errno : EINVAL; |
|
|
|
errno = errno ? errno : EINVAL; |
|
|
|
return NULL; |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
getgrgid_r((gid_t)gid_l, &(info->d.gr), info->str, STR_MAX, &gr); |
|
|
|
gr = getgrgid((gid_t)gid_l); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!gr && !errno) |
|
|
|
|
|
|
|
errno = EINVAL; |
|
|
|
|
|
|
|
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); |
|
|
|
if (!gr) { |
|
|
|
exit(errno ? errno : EXIT_FAILURE); |
|
|
|
if (!errno) |
|
|
|
} |
|
|
|
errno = EINVAL; |
|
|
|
|
|
|
|
return NULL; |
|
|
|
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 gr; |
|
|
|
return *num; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|