Compare commits
No commits in common. "master" and "noproxy" have entirely different histories.
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +0,0 @@
|
||||
us
|
18
README.md
18
README.md
@ -3,21 +3,3 @@
|
||||
Opens a shell as a different user without needing to authenticate as that user.
|
||||
This is similar to the behavior of `su` but it allows to execute privileged
|
||||
and/or commands as any other user without needing their password.
|
||||
It doesn't use PAM, instead relies on shadow files and the passwd database
|
||||
to check if the given password is correct. Support for different methods of
|
||||
authentication is planned.
|
||||
|
||||
## Installing
|
||||
|
||||
To install use `make install` and to uninstall use `make uninstall`, all
|
||||
other documentation is provided as man pages, `man us` will give general
|
||||
information about the utility and how to use it and `man us.conf` contains
|
||||
info about the config file format.
|
||||
|
||||
## Supported platforms
|
||||
|
||||
All platforms that store the encrypted password in `/etc/passwd` should be
|
||||
supported, which to this day are none. Linux is supported through `shadow(3)`
|
||||
and OpenBSD through its API. Support for more systems is planned.
|
||||
This program is libc-agnostic as (apart from authentication) it is POSIX
|
||||
compliant code.
|
||||
|
16
TODO
Normal file
16
TODO
Normal file
@ -0,0 +1,16 @@
|
||||
- Modify the following environment variables: (listed in environ(7))
|
||||
* USER -> to target user
|
||||
* LOGNAME -> to target user
|
||||
* SHELL -> to the target user's SHELL
|
||||
* HOME -> to the target user's HOME
|
||||
|
||||
- fork before exec, that is because processes might try to kill us or the
|
||||
command but since they may run under elevated privileges they will get
|
||||
permission denied error. If we remain the parent processes, unprivileged
|
||||
proceses can send signals to us and we will relay them to our children
|
||||
running at the same privilege as us. This is useful when:
|
||||
- The child command hangs and we want to cose it, kinda
|
||||
problematic but we could run kill with us as well
|
||||
- The parent shell dies and children need to be killed, then
|
||||
since one of their children (us) has higher privileges
|
||||
they can't kill us and we would end up as zombies
|
96
config.template
Normal file
96
config.template
Normal file
@ -0,0 +1,96 @@
|
||||
SECURITY CONSIDERATIONS
|
||||
=======================
|
||||
|
||||
1. commands must be given by absolute path, that's because if you do otherwise
|
||||
nopassword commands could be hijacked:
|
||||
|
||||
in the config:
|
||||
nopass badguy as root cmd zzz
|
||||
in the shell:
|
||||
~ $ export PATH=/home/badguy/test:$PATH
|
||||
~ $ mkdir test
|
||||
~ $ printf '#!/bin/sh\nrm -rf --no-preserve-root' > test/zzz
|
||||
~ $ chmod +x test/zzz
|
||||
~ $ us zzz #this deletes the filesystem without password!
|
||||
|
||||
IDEA 1
|
||||
======
|
||||
|
||||
# this is a comment
|
||||
# rules are goruped by user/group
|
||||
# rules are structured somewhat like json, example:
|
||||
|
||||
# Only 'command' is allowed to run without a password, all the rest is blocked
|
||||
ale {
|
||||
allow {
|
||||
command nopass
|
||||
}
|
||||
|
||||
deny {
|
||||
/.*/
|
||||
}
|
||||
}
|
||||
|
||||
IDEA 2 - THE DOAS WAY
|
||||
=====================
|
||||
|
||||
# this is a comment
|
||||
# every line is a rule
|
||||
# rules are structured like this:
|
||||
|
||||
permit|deny [options] identity [as target] [cmd command [args ...]]
|
||||
|
||||
# look at doas.conf(5) for more information
|
||||
|
||||
IDEA 2-3
|
||||
========
|
||||
|
||||
# reverse-doas way
|
||||
-> identity permit|deny [command [args ...]] [options]
|
||||
|
||||
# but how would I distinguish between command and options?
|
||||
-> identity [options] permit|deny [command [args ...]]
|
||||
|
||||
# spaces are not a very good separatow when in comes to commands
|
||||
-> identity,[options],permit|deny,[command [args ...]]
|
||||
|
||||
#
|
||||
# this is kinda similar to a crontab, basically options are required
|
||||
#
|
||||
|
||||
# config structure:
|
||||
-> identity options as action [command [args ...]]
|
||||
^ ^ ^ ^
|
||||
can be * | | permit, deny
|
||||
can be nil (NULL) |
|
||||
can be *
|
||||
|
||||
# permit user "ale" to execute command "shutdown" as root without password:
|
||||
-> ale nopass root permit shutdown
|
||||
# permit members of the wheel group to execute any comands as any user:
|
||||
-> :wheel nil * permit
|
||||
# deny users of the wheel group to execute commands that begin with "sys":
|
||||
# this could be circumvented by having the command inside a shell script
|
||||
-> :wheel nil * deny /sys.*/
|
||||
# deny all users to execute all comands as any other user
|
||||
-> * nil * deny
|
||||
#
|
||||
# let's scramble things up to make more sense
|
||||
#
|
||||
[action] options identity as [command [args ...]]
|
||||
^ ^ ^ ^
|
||||
| | can both be * (any)
|
||||
| can be none, comma separated
|
||||
none: permit
|
||||
'!': deny (negate rule)
|
||||
|
||||
# allow users of the wheel group to execute any command as root:
|
||||
-> none :wheel root
|
||||
# deny all users to execute commands that start with "sys"
|
||||
-> ! none * * /sys.*/
|
||||
|
||||
IDEA 3 - THE SUCKLESS WAY
|
||||
=========================
|
||||
|
||||
configuration should happen inside a source file called config.h, to apply
|
||||
changes to the configuration the program has to be recompiled
|
23
makefile
23
makefile
@ -1,34 +1,27 @@
|
||||
.POSIX:
|
||||
|
||||
CC ?= gcc
|
||||
CFLAGS = -Wall -pedantic --std=c99 -O2
|
||||
DBG_CFLAGS = -Wall -Werror -pedantic --std=c99 -O0 -g
|
||||
SYSTEM != uname
|
||||
LDFLAGS != if [ '${SYSTEM}' != 'OpenBSD' ]; then echo '-lcrypt'; fi
|
||||
LDFLAGS = -lpam -lpam_misc
|
||||
PREFIX = /usr/local
|
||||
MANPREFIX = ${PREFIX}/share/man
|
||||
|
||||
us: us.c
|
||||
|
||||
dbg:
|
||||
${CC} ${LDFLAGS} ${DBG_CFLAGS} us.c -o us
|
||||
gcc ${LDFLAGS} ${DBG_CFLAGS} us.c -o us
|
||||
|
||||
install: us
|
||||
mkdir -p ${DESTDIR}${PREFIX}/bin
|
||||
cp -f us ${DESTDIR}${PREFIX}/bin/us
|
||||
chown 0:0 ${DESTDIR}${PREFIX}/bin/us
|
||||
chown root:root ${DESTDIR}${PREFIX}/bin/us
|
||||
chmod 4755 ${DESTDIR}${PREFIX}/bin/us
|
||||
mkdir -p ${DESTDIR}${MANPREFIX}/man1
|
||||
cp -f us.1 ${DESTDIR}${MANPREFIX}/man1/us.1
|
||||
chmod 644 ${DESTDIR}${MANPREFIX}/man1/us.1
|
||||
mkdir -p ${DESTDIR}${MANPREFIX}/man5
|
||||
cp -f us.conf.5 ${DESTDIR}${MANPREFIX}/man5/us.conf.5
|
||||
chmod 644 ${DESTDIR}${MANPREFIX}/man5/us.conf.5
|
||||
# mkdir -p ${DESTDIR}${MANPREFIX}/man1
|
||||
# cp -f us.1 ${DESTDIR}${MANPREFIX}/man1/us.1
|
||||
# chmod 644 ${DESTDIR}${MANPREFIX}/man1/us.1
|
||||
|
||||
uninstall:
|
||||
rm -f ${DESTDIR}${PREFIX}/bin/us \
|
||||
${DESTDIR}${MANPREFIX}/man1/us.1
|
||||
${DESTDIR}${MANPREFIX}/man1/us.conf.5
|
||||
rm -f ${DESTDIR}${PREFIX}/bin/us
|
||||
# ${DESTDIR}${MANPREFIX}/man1/us.1
|
||||
|
||||
clean:
|
||||
rm -f us us-dbg
|
||||
|
77
us.1
77
us.1
@ -1,77 +0,0 @@
|
||||
.TH US 1 "JULY 2021" "Alessandro Mauri"
|
||||
|
||||
.SH NAME
|
||||
us \- execute command with another identity
|
||||
|
||||
.SH SYNOPSIS
|
||||
.SY us
|
||||
.OP \-hseA
|
||||
.OP \-u user
|
||||
.OP \-g group
|
||||
.OP \-C config
|
||||
.OP command
|
||||
.OP args
|
||||
.YS
|
||||
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
The
|
||||
.BR us
|
||||
utility executes the given command as another identity, which by default is
|
||||
root. If no command is specified, it starts a shell as that user.
|
||||
.PP
|
||||
In order to execute anything users need to authenticate and the user + target
|
||||
identity configuration must be allowed in the configuration file, see
|
||||
.BR us.conf(5)
|
||||
for more information.
|
||||
.PP
|
||||
By default when a command or shell gets executed a new environment gets created,
|
||||
USER is set with the target user, as well as LOGNAME, SHELL and HOME get all set
|
||||
with the default values for the target user.
|
||||
PATH, TERM, EDITOR, VISUAL, DISPLAY and XAUTHORITY instead are kept between
|
||||
execution.
|
||||
Lastly a new variable US_USER is added (but not overridden) which contains the
|
||||
calling user's username.
|
||||
.PP
|
||||
Invoking the program logs by default to
|
||||
.BR syslog(2)
|
||||
the outcome of the invocation, this behavior can be changed in the config.
|
||||
|
||||
.SH OPTIONS
|
||||
.IP \-h
|
||||
Print usage info message.
|
||||
.IP \-s
|
||||
Use the calling user's SHELL instead of the target user's one.
|
||||
.IP \-e
|
||||
Keep the entire environment between execution instead of just PATH, TERM,
|
||||
EDITOR, VISUAL, DISPLAY and XAUTHORITY; user variables still get overridden.
|
||||
.IP \-A
|
||||
Instead of prompting for a password,
|
||||
.BR us
|
||||
executes the command specified in the variable US_ASKPASS and reads it's stdout
|
||||
as the password. If US_ASKPASS is not specified then it will fall back
|
||||
prompting the password.
|
||||
.IP "\-u user"
|
||||
Change the target identity to
|
||||
.I user
|
||||
(default is root).
|
||||
.IP "\-g group"
|
||||
Set the group of the target user to
|
||||
.I group
|
||||
instead of the target user's default, also add it to the group list.
|
||||
.IP "\-C config"
|
||||
Use the specified config file
|
||||
|
||||
.SH "RETURN VALUE"
|
||||
The
|
||||
.BR us
|
||||
utility returns 0 on success and != 0 on failure which may occur on various
|
||||
occasions. An error message will be outputted to specify the reason.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR su(1)
|
||||
.BR us.conf(5)
|
||||
|
||||
.SH AUTHOR
|
||||
Alessandro Mauri <alemauri001@tuta.io>
|
||||
|
734
us.c
734
us.c
@ -17,16 +17,9 @@
|
||||
*/
|
||||
|
||||
#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>
|
||||
@ -35,111 +28,34 @@
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <grp.h>
|
||||
#include <termios.h>
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <fcntl.h>
|
||||
#include <syslog.h>
|
||||
#include <security/pam_appl.h>
|
||||
#include <security/pam_misc.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 void usage (void);
|
||||
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 *);
|
||||
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 *);
|
||||
|
||||
// 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};
|
||||
|
||||
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, 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) {
|
||||
int shellflag = 0, envflag = 0;
|
||||
while ((opt = getopt(argc, argv, "A:u:g:C:se")) != -1) {
|
||||
switch (opt) {
|
||||
case 'A':
|
||||
askpass = 1;
|
||||
printf("-A is not yet implemented\n");
|
||||
exit(EXIT_FAILURE);
|
||||
break;
|
||||
case 'u':
|
||||
t_usr = optarg;
|
||||
@ -148,7 +64,8 @@ int main(int argc, char *argv[])
|
||||
t_grp = optarg;
|
||||
break;
|
||||
case 'C':
|
||||
config_file = optarg;
|
||||
printf("-C is not yet implemented\n");
|
||||
exit(EXIT_FAILURE);
|
||||
break;
|
||||
case 's':
|
||||
shellflag = 1;
|
||||
@ -156,146 +73,36 @@ int main(int argc, char *argv[])
|
||||
case 'e':
|
||||
envflag = 1;
|
||||
break;
|
||||
case 'h':
|
||||
usage(1);
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
case '?':
|
||||
usage(0);
|
||||
usage();
|
||||
exit(EINVAL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get user info */
|
||||
const char *uname;
|
||||
char *shell;
|
||||
uid_t ruid = getuid();
|
||||
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);
|
||||
struct passwd *my_pw = getpwuid(ruid);
|
||||
if (!my_pw) {
|
||||
fprintf(stderr, "getpwid: %s\n", strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
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:");
|
||||
uname = my_pw->pw_name;
|
||||
|
||||
/* Authenticate */
|
||||
if (authenticate(uname) != PAM_SUCCESS)
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
|
||||
/* Get target user and group info */
|
||||
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_pw = user_to_passwd(t_usr);
|
||||
if (!t_pw) {
|
||||
fprintf(stderr, "user_to_passwd: %s\n", strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
t_gr = group_to_grp(t_grp);
|
||||
|
||||
/* Get target user's shell */
|
||||
if (!shellflag)
|
||||
@ -307,18 +114,40 @@ int main(int argc, char *argv[])
|
||||
|
||||
/* Set argc and argv */
|
||||
int c_argc = argc - optind;
|
||||
char **c_argv = NULL;
|
||||
char **c_argv;
|
||||
if (c_argc) {
|
||||
c_argv = emalloc(sizeof(char *) * (c_argc + 1));
|
||||
for (int i = 0; optind < argc; optind++, i++)
|
||||
c_argv[i] = estrdup(argv[optind]);
|
||||
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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c_argc = 1;
|
||||
c_argv = emalloc(sizeof(char *) * (c_argc + 1));
|
||||
c_argv[0] = estrdup(shell);
|
||||
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[c_argc] = NULL;
|
||||
|
||||
struct env_elem {
|
||||
char *name;
|
||||
char *value;
|
||||
};
|
||||
|
||||
struct env_elem env_keep[] = {
|
||||
{"PATH", NULL},
|
||||
{"TERM", NULL},
|
||||
@ -337,12 +166,10 @@ int main(int argc, char *argv[])
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
/* Copy what has to be copied and then clear the environment, we'll
|
||||
* make a fresh one later */
|
||||
if (envflag) {
|
||||
if (envflag) { /* clear env */
|
||||
for (int i = 0; env_keep[i].name; i++)
|
||||
env_keep[i].value = estrdup(getenv(env_keep[i].name));
|
||||
environ = NULL;
|
||||
env_keep[i].value = strdup(getenv(env_keep[i].name));
|
||||
environ = NULL; // in place of clearenv
|
||||
}
|
||||
|
||||
for (int i = 0; env_mod[i].name; i++) {
|
||||
@ -353,7 +180,8 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; envflag &&env_keep[i].name; i++) {
|
||||
if (envflag) {
|
||||
for (int i = 0; env_keep[i].name; i++) {
|
||||
if (env_keep[i].value) {
|
||||
err = setenv(env_keep[i].name, env_keep[i].value, 1);
|
||||
if (err == -1) {
|
||||
@ -362,21 +190,9 @@ 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_name, 0);
|
||||
// do not override, we might be under more levels of 'us'
|
||||
err = setenv("US_USER", my_pw->pw_name, 0);
|
||||
|
||||
errno = 0;
|
||||
/* Set permissions */
|
||||
@ -385,19 +201,10 @@ 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, "execvp: %s\n", strerror(errno));
|
||||
fprintf(stderr, "execl: %s\n", strerror(errno));
|
||||
|
||||
/* Cleanup and return */
|
||||
fail_end:
|
||||
@ -408,18 +215,17 @@ int main(int argc, char *argv[])
|
||||
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");
|
||||
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");
|
||||
// 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");
|
||||
}
|
||||
|
||||
static int perm_set (struct passwd *pw, struct group *gr)
|
||||
@ -462,196 +268,44 @@ static int perm_set(struct passwd *pw, struct group *gr)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int authenticate(uid_t uid, int ask, int persist)
|
||||
static int authenticate (const char *uname)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
} else {
|
||||
valid_session = 0;
|
||||
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;
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
if (ask && askpass) {
|
||||
close(fd);
|
||||
} else {
|
||||
if (tcsetattr(tty_fd, TCSANOW, &tio_before) == -1)
|
||||
die("tcsetattr:");
|
||||
pam_err = pam_authenticate(pamh, 0);
|
||||
if (pam_err == PAM_SUCCESS) {
|
||||
pam_err = pam_acct_mgmt(pamh, 0);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
if (pam_err != PAM_SUCCESS) {
|
||||
printf("Auth failed: %s\n", pam_strerror(pamh, pam_err));
|
||||
pam_end(pamh, pam_err);
|
||||
}
|
||||
|
||||
/* 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)
|
||||
count++;
|
||||
} while (pam_err != PAM_SUCCESS && count < 3);
|
||||
|
||||
if (pam_err != PAM_SUCCESS) {
|
||||
fprintf(stderr, "better luck next time\n");
|
||||
return pam_err;
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
static struct passwd* user_to_passwd (const char *user)
|
||||
{
|
||||
if (!user) {
|
||||
errno = EINVAL;
|
||||
@ -662,22 +316,26 @@ static struct passwd* user_to_passwd(const char *user, struct user_info *info)
|
||||
long uid_l;
|
||||
errno = 0;
|
||||
if (user[0] != '#') {
|
||||
getpwnam_r(user, &(info->d.pw), info->str, STR_MAX, &pw);
|
||||
pw = getpwnam(user);
|
||||
} else {
|
||||
uid_l = strtol(&user[1], NULL, 10);
|
||||
if (uid_l < 0 || errno) {
|
||||
errno = errno ? errno : EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
getpwuid_r((uid_t)uid_l, &(info->d.pw), info->str, STR_MAX, &pw);
|
||||
pw = getpwuid((uid_t)uid_l);
|
||||
}
|
||||
|
||||
if (!pw && !errno)
|
||||
if (!pw) {
|
||||
if (!errno)
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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) {
|
||||
errno = EINVAL;
|
||||
@ -688,168 +346,20 @@ static struct group* group_to_grp(const char *group, struct user_info *info)
|
||||
long gid_l;
|
||||
errno = 0;
|
||||
if (group[0] != '#') {
|
||||
getgrnam_r(group, &(info->d.gr), info->str, STR_MAX, &gr);
|
||||
gr = getgrnam(group);
|
||||
} else {
|
||||
gid_l = strtol(&group[1], NULL, 10);
|
||||
if (gid_l < 0 || errno) {
|
||||
errno = errno ? errno : EINVAL;
|
||||
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)
|
||||
if (!gr) {
|
||||
if (!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;
|
||||
}
|
||||
|
105
us.conf.5
105
us.conf.5
@ -1,105 +0,0 @@
|
||||
.TH US.CONF 5 "JULY 2021" "Alessandro Mauri"
|
||||
|
||||
.SH NAME
|
||||
us.conf \- us configuration file
|
||||
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
The
|
||||
.BR us(1)
|
||||
utility executes commands as another identity according to the rules in the
|
||||
.BR us.conf
|
||||
configuration file.
|
||||
.PP
|
||||
The rules have the following format:
|
||||
.IP
|
||||
.BR "permit|deny"
|
||||
.BR user
|
||||
as
|
||||
.BR target
|
||||
.OP options
|
||||
.OP ENV
|
||||
.SS Options
|
||||
Possible options are:
|
||||
.IP nopass
|
||||
The user is not required to enter a password.
|
||||
.IP persist
|
||||
Once entering the password for the first time, a timer for five minutes is
|
||||
started, during those five minutes the user is not required to re-enter
|
||||
the password for that session. Re-invoking us resets that timer.
|
||||
.IP nolog
|
||||
Do not log to
|
||||
.BR syslog(2)
|
||||
command outcome
|
||||
.PP
|
||||
The sum of matching rules determines the action taken, if no rules match
|
||||
the action is denied.
|
||||
.PP
|
||||
Comments are made by having the first non-blank character of a line be an hash
|
||||
mark ('#'), comments take up the whole line and cannot be embedded in the
|
||||
middle of a line.
|
||||
.PP
|
||||
A valid user or target is an alphanumeric string containing the name of the
|
||||
target. If the target is a user, the string begins with [0-9A-z]; if the
|
||||
target is a group then the has to begin with ':'. Instead of the name of the
|
||||
user/group it's number can be used, in that case the part of the string that
|
||||
would contain the name must begin with '#' (so after a possible ':').
|
||||
.PP
|
||||
As options a comma separated list of environment variables can be specified,
|
||||
these will be added or will override existing environment variables during
|
||||
execution of the command. A valid environment variable list starts with an
|
||||
uppercase letter and ends at the next space.
|
||||
.PP
|
||||
A valid config line must be owned by root:root and should not be readable,
|
||||
writable or executable for any other user or group, in other words the best
|
||||
file permissions for the config file are
|
||||
.BR 660
|
||||
if the config file fails to meet this requirements it will get rejected and
|
||||
invocation will fail.
|
||||
|
||||
.SH FILES
|
||||
.IP /etc/us.conf
|
||||
us(1) configuration file
|
||||
|
||||
.SH EXAMPLES
|
||||
.PP
|
||||
The following example will allow root to execute commands as itself without
|
||||
requiring a password and without logging:
|
||||
.PP
|
||||
.EX
|
||||
permit root as root nopass nolog
|
||||
.EE
|
||||
.PP
|
||||
This next example allows users in the wheel group to execute commands as
|
||||
root including a new environment variable IS_WHEEL set to 'yes' and the variable
|
||||
EDITOR will be set to ed, the standard unix editor:
|
||||
.PP
|
||||
.EX
|
||||
permit :wheel as root IS_WHEEL=yes,EDITOR=ed
|
||||
.EE
|
||||
.PP
|
||||
In this example the user maria is allowed to execute commands as a member of
|
||||
the group wheel and the session is remembered so that in the next five
|
||||
minutes the password won't be needed:
|
||||
.PP
|
||||
.EX
|
||||
permit maria as :wheel persist
|
||||
.EE
|
||||
.PP
|
||||
This time the user joe is denied to execute commands as anyone who's member of
|
||||
the group 'coolppl' because joe is uncool
|
||||
.PP
|
||||
.EX
|
||||
deny joe as :coolppl
|
||||
.EE
|
||||
|
||||
.SH LIMITATIONS
|
||||
.PP
|
||||
Due to the way the environment is parsed, neither the name nor the value can
|
||||
contain commas.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR us(1)
|
||||
|
||||
.SH AUTHOR
|
||||
Alessandro Mauri <alemauri001@tuta.io>
|
Loading…
Reference in New Issue
Block a user