#include "ksu.h"
#include "adm_proto.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <grp.h>
char * prog_name;
int auth_debug =0;
char k5login_path[MAXPATHLEN];
char k5users_path[MAXPATHLEN];
char * gb_err = NULL;
int quiet = 0;
#define _DEF_CSH "/bin/csh"
static int set_env_var (char *, char *);
static void sweep_up (krb5_context, krb5_ccache);
static char * ontty (void);
#ifdef HAVE_STDARG_H
static void print_status( const char *fmt, ...)
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 7)
__attribute__ ((__format__ (__printf__, 1, 2)))
#endif
;
#else
static void print_status();
#endif
char * get_dir_of_file();
void usage (){
fprintf(stderr, "Usage: %s [target user] [-n principal] [-c source cachename] [-k] [-D] [-r time] [-pf] [-l lifetime] [-zZ] [-q] [-e command [args... ] ] [-a [args... ] ]\n", prog_name);
}
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64
#endif
static uid_t source_uid, target_uid;
int
main (argc, argv)
int argc;
char ** argv;
{
int hp =0;
int some_rest_copy = 0;
int all_rest_copy = 0;
char *localhostname = NULL;
opt_info options;
int option=0;
int statusp=0;
krb5_error_code retval = 0;
krb5_principal client = NULL;
krb5_ccache cc_target = NULL;
krb5_context ksu_context;
char * cc_target_tag = NULL;
char * target_user = NULL;
char * source_user;
krb5_ccache cc_source = NULL;
const char * cc_source_tag = NULL;
uid_t source_gid, target_gid;
const char * cc_source_tag_tmp = NULL;
char * cc_target_tag_tmp=NULL;
char * cmd = NULL, * exec_cmd = NULL;
int errflg = 0;
krb5_boolean auth_val;
krb5_boolean authorization_val = FALSE;
int path_passwd = 0;
int done =0,i,j;
uid_t ruid = getuid ();
struct passwd *pwd=NULL, *target_pwd ;
char * shell;
char ** params;
int keep_target_cache = 0;
int child_pid, child_pgrp, ret_pid;
extern char * getpass(), *crypt();
int pargc;
char ** pargv;
struct stat st_temp;
krb5_boolean stored = FALSE;
krb5_principal kdc_server;
krb5_boolean zero_password;
char * dir_of_cc_target;
options.opt = KRB5_DEFAULT_OPTIONS;
options.lifetime = KRB5_DEFAULT_TKT_LIFE;
options.rlife =0;
options.princ =0;
params = (char **) xcalloc (2, sizeof (char *));
params[1] = NULL;
unsetenv ("KRB5_CONFIG");
retval = krb5_init_secure_context(&ksu_context);
if (retval) {
com_err(argv[0], retval, "while initializing krb5");
exit(1);
}
if (strrchr(argv[0], '/'))
argv[0] = strrchr(argv[0], '/')+1;
prog_name = argv[0];
if (strlen (prog_name) > 50) {
com_err(prog_name, 0, "program name too long - quitting to avoid triggering system logging bugs");
exit (1);
}
#ifndef LOG_NDELAY
#define LOG_NDELAY 0
#endif
#ifndef LOG_AUTH
openlog(prog_name, LOG_PID|LOG_NDELAY);
#else
openlog(prog_name, LOG_PID | LOG_NDELAY, LOG_AUTH);
#endif
if (( argc == 1) || (argv[1][0] == '-')){
target_user = xstrdup("root");
pargc = argc;
pargv = argv;
} else {
target_user = xstrdup(argv[1]);
pargc = argc -1;
if ((pargv =(char **) calloc(pargc +1,sizeof(char *)))==NULL){
com_err(prog_name, errno, "while allocating memory");
exit(1);
}
pargv[pargc] = NULL;
pargv[0] = argv[0];
for(i =1; i< pargc; i ++){
pargv[i] = argv[i + 1];
}
}
if (krb5_seteuid (ruid)) {
com_err (prog_name, errno, "while setting euid to source user");
exit (1);
}
while(!done && ((option = getopt(pargc, pargv,"n:c:r:a:zZDfpkql:e:")) != -1)){
switch (option) {
case 'r':
options.opt |= KDC_OPT_RENEWABLE;
if (strlen (optarg) >= 14)
optarg = "bad-time";
retval = krb5_string_to_deltat(optarg, &options.rlife);
if (retval != 0 || options.rlife == 0) {
fprintf(stderr, "Bad lifetime value (%s hours?)\n", optarg);
errflg++;
}
break;
case 'a':
optind --;
if (auth_debug){printf("Before get_params optind=%d\n", optind);}
if ((retval = get_params( & optind, pargc, pargv, ¶ms))){
com_err(prog_name, retval, "when gathering parameters");
errflg++;
}
if(auth_debug){ printf("After get_params optind=%d\n", optind);}
done = 1;
break;
case 'p':
options.opt |= KDC_OPT_PROXIABLE;
break;
case 'f':
options.opt |= KDC_OPT_FORWARDABLE;
break;
case 'k':
keep_target_cache =1;
break;
case 'q':
quiet =1;
break;
case 'l':
if (strlen (optarg) >= 14)
optarg = "bad-time";
retval = krb5_string_to_deltat(optarg, &options.lifetime);
if (retval != 0 || options.lifetime == 0) {
fprintf(stderr, "Bad lifetime value (%s hours?)\n", optarg);
errflg++;
}
break;
case 'n':
if ((retval = krb5_parse_name(ksu_context, optarg, &client))){
com_err(prog_name, retval, "when parsing name %s", optarg);
errflg++;
}
options.princ = 1;
break;
#ifdef DEBUG
case 'D':
auth_debug = 1;
break;
#endif
case 'z':
some_rest_copy = 1;
if(all_rest_copy) {
fprintf(stderr,
"-z option is mutually exclusive with -Z.\n");
errflg++;
}
break;
case 'Z':
all_rest_copy = 1;
if(some_rest_copy) {
fprintf(stderr,
"-Z option is mutually exclusive with -z.\n");
errflg++;
}
break;
case 'c':
if (cc_source_tag == NULL) {
cc_source_tag = xstrdup(optarg);
if ( strchr(cc_source_tag, ':')){
cc_source_tag_tmp = strchr(cc_source_tag, ':') + 1;
if( stat( cc_source_tag_tmp, &st_temp)){
com_err (prog_name, errno,
"while looking for credentials file %s",
cc_source_tag_tmp);
exit (1);
}
}
else {
fprintf(stderr,"malformed credential cache name %s\n",
cc_source_tag);
errflg++;
}
} else {
fprintf(stderr, "Only one -c option allowed\n");
errflg++;
}
break;
case 'e':
cmd = xstrdup(optarg);
if(auth_debug){printf("Before get_params optind=%d\n", optind);}
if ((retval = get_params( & optind, pargc, pargv, ¶ms))){
com_err(prog_name, retval, "when gathering parameters");
errflg++;
}
if(auth_debug){printf("After get_params optind=%d\n", optind);}
done = 1;
if (auth_debug){
fprintf(stderr,"Command to be executed: %s\n", cmd);
}
break;
case '?':
default:
errflg++;
break;
}
}
if (errflg) {
usage();
exit(2);
}
if (optind != pargc ){
usage();
exit(2);
}
if (auth_debug){
for(j=1; params[j] != NULL; j++){
fprintf (stderr,"params[%d]= %s\n", j,params[j]);
}
}
source_user = getlogin();
if (source_user == NULL ||(pwd = getpwnam(source_user)) == NULL ||
pwd->pw_uid != ruid){
pwd = getpwuid(ruid);
}
if (pwd == NULL) {
fprintf(stderr, "ksu: who are you?\n");
exit(1);
}
if (pwd->pw_uid != ruid) {
fprintf (stderr, "Your uid doesn't match your passwd entry?!\n");
exit (1);
}
source_user = xstrdup(pwd->pw_name);
source_uid = pwd->pw_uid;
source_gid = pwd->pw_gid;
if (!strcmp(SOURCE_USER_LOGIN, target_user)){
target_user = xstrdup (source_user);
}
if ((target_pwd = getpwnam(target_user)) == NULL){
fprintf(stderr, "ksu: unknown login %s\n", target_user);
exit(1);
}
target_uid = target_pwd->pw_uid;
target_gid = target_pwd->pw_gid;
init_auth_names(target_pwd->pw_dir);
if (cc_source_tag == NULL){
cc_source_tag = krb5_cc_default_name(ksu_context);
cc_source_tag_tmp = strchr(cc_source_tag, ':');
if (cc_source_tag_tmp == 0)
cc_source_tag_tmp = cc_source_tag;
else
cc_source_tag_tmp++;
}
if ((retval = krb5_cc_resolve(ksu_context, cc_source_tag, &cc_source))){
com_err(prog_name, retval,"while getting source cache");
exit(1);
}
if (((retval = krb5_cc_set_flags(ksu_context, cc_source, 0x0)) != 0)
&& (retval != KRB5_FCC_NOFILE)) {
com_err(prog_name, retval, "while opening ccache");
exit(1);
}
if ((retval = get_best_princ_for_target(ksu_context, source_uid,
target_uid, source_user,
target_user, cc_source,
&options, cmd, localhostname,
&client, &hp))){
com_err(prog_name,retval, "while selecting the best principal");
exit(1);
}
if ( geteuid() != source_uid) {
if (krb5_seteuid(0) || krb5_seteuid(source_uid) ) {
com_err(prog_name, errno, "while returning to source uid after finding best principal");
exit(1);
}
}
if (auth_debug){
if (hp){
fprintf(stderr,
"GET_best_princ_for_target result: NOT AUTHORIZED\n");
}else{
fprintf(stderr,
"GET_best_princ_for_target result-best principal ");
plain_dump_principal (ksu_context, client);
fprintf(stderr,"\n");
}
}
if (hp){
if (gb_err) fprintf(stderr, "%s", gb_err);
fprintf(stderr,"account %s: authorization failed\n",target_user);
exit(1);
}
if (cc_target_tag == NULL) {
cc_target_tag = (char *)xcalloc(KRB5_SEC_BUFFSIZE ,sizeof(char));
do {
sprintf(cc_target_tag, "%s%ld.%d",
KRB5_SECONDARY_CACHE,
(long) target_uid, gen_sym());
cc_target_tag_tmp = strchr(cc_target_tag, ':') + 1;
}while ( !stat ( cc_target_tag_tmp, &st_temp));
}
dir_of_cc_target = get_dir_of_file(cc_target_tag_tmp);
if (access(dir_of_cc_target, R_OK | W_OK )){
fprintf(stderr,
"%s does not have correct permissions for %s\n",
source_user, cc_target_tag);
exit(1);
}
if (auth_debug){
fprintf(stderr, " source cache = %s\n", cc_source_tag);
fprintf(stderr, " target cache = %s\n", cc_target_tag);
}
if ((source_uid == 0) && (target_uid != 0)) {
if ((retval = krb5_ccache_copy_restricted(ksu_context, cc_source,
cc_target_tag, client,
&cc_target, &stored,
target_uid))){
com_err (prog_name, retval,
"while copying cache %s to %s",
krb5_cc_get_name(ksu_context, cc_source),cc_target_tag);
exit(1);
}
} else {
if ((retval = krb5_ccache_copy(ksu_context, cc_source, cc_target_tag,
client,&cc_target, &stored, target_uid))) {
com_err (prog_name, retval,
"while copying cache %s to %s",
krb5_cc_get_name(ksu_context, cc_source),
cc_target_tag);
exit(1);
}
}
if (krb5_seteuid(0)) {
com_err(prog_name, errno, "while reclaiming root uid");
exit(1);
}
if ((source_uid == 0) || (target_uid == source_uid)){
#ifdef GET_TGT_VIA_PASSWD
if ((!all_rest_copy) && options.princ && (stored == FALSE)){
if ((retval = ksu_tgtname(ksu_context,
krb5_princ_realm (ksu_context, client),
krb5_princ_realm(ksu_context, client),
&kdc_server))){
com_err(prog_name, retval,
"while creating tgt for local realm");
sweep_up(ksu_context, cc_target);
exit(1);
}
fprintf(stderr,"WARNING: Your password may be exposed if you enter it here and are logged\n");
fprintf(stderr," in remotely using an unsecure (non-encrypted) channel.\n");
if (krb5_get_tkt_via_passwd (ksu_context, &cc_target, client,
kdc_server, &options,
&zero_password) == FALSE){
if (zero_password == FALSE){
fprintf(stderr,"Goodbye\n");
sweep_up(ksu_context, cc_target);
exit(1);
}
fprintf(stderr,
"Could not get a tgt for ");
plain_dump_principal (ksu_context, client);
fprintf(stderr, "\n");
}
}
#endif
}
if (source_uid && (source_uid != target_uid)) {
char * client_name;
auth_val = krb5_auth_check(ksu_context, client, localhostname, &options,
target_user,cc_target, &path_passwd, target_uid);
if (auth_val ==FALSE){
fprintf(stderr, "Authentication failed.\n");
syslog(LOG_WARNING,
"'%s %s' authentication failed for %s%s",
prog_name,target_user,source_user,ontty());
sweep_up(ksu_context, cc_target);
exit(1);
}
#if 0
if (!path_passwd){
if ((retval = krb5_ccache_overwrite(ksu_context, cc_target, cc_source,
client))){
com_err (prog_name, retval,
"while copying cache %s to %s",
krb5_cc_get_name(ksu_context, cc_target),
krb5_cc_get_name(ksu_context, cc_source));
sweep_up(ksu_context, cc_target);
exit(1);
}
if (chown(cc_source_tag_tmp, source_uid, source_gid)){
com_err(prog_name, errno,
"while changing owner for %s",
cc_source_tag_tmp);
exit(1);
}
}
#endif
if ((retval = krb5_unparse_name(ksu_context, client, &client_name))) {
com_err (prog_name, retval, "When unparsing name");
sweep_up(ksu_context, cc_target);
exit(1);
}
print_status("Authenticated %s\n", client_name);
syslog(LOG_NOTICE,"'%s %s' authenticated %s for %s%s",
prog_name,target_user,client_name,
source_user,ontty());
if (krb5_seteuid(target_uid)) {
com_err(prog_name, errno, "while switching to target for authorization check");
sweep_up(ksu_context, cc_target);
exit(1);
}
if ((retval = krb5_authorization(ksu_context, client,target_user,
cmd, &authorization_val, &exec_cmd))){
com_err(prog_name,retval,"while checking authorization");
krb5_seteuid(0);
sweep_up(ksu_context, cc_target);
exit(1);
}
if (krb5_seteuid(0)) {
com_err(prog_name, errno, "while switching back from target after authorization check");
sweep_up(ksu_context, cc_target);
exit(1);
}
if (authorization_val == TRUE){
if (cmd) {
print_status(
"Account %s: authorization for %s for execution of\n",
target_user, client_name);
print_status(" %s successful\n",exec_cmd);
syslog(LOG_NOTICE,
"Account %s: authorization for %s for execution of %s successful",
target_user, client_name, exec_cmd);
}else{
print_status(
"Account %s: authorization for %s successful\n",
target_user, client_name);
syslog(LOG_NOTICE,
"Account %s: authorization for %s successful",
target_user, client_name);
}
}else {
if (cmd){
if (exec_cmd){
fprintf(stderr, "%s", exec_cmd );
syslog(LOG_WARNING, "%s",exec_cmd);
}
fprintf(stderr,
"Account %s: authorization for %s for execution of %s failed\n",
target_user, client_name, cmd );
syslog(LOG_WARNING,
"Account %s: authorization for %s for execution of %s failed",
target_user, client_name, cmd );
}else{
fprintf(stderr,
"Account %s: authorization of %s failed\n",
target_user, client_name);
syslog(LOG_WARNING,
"Account %s: authorization of %s failed",
target_user, client_name);
}
sweep_up(ksu_context, cc_target);
exit(1);
}
}
if( some_rest_copy){
if ((retval = krb5_ccache_filter(ksu_context, cc_target, client))){
com_err(prog_name,retval,"while calling cc_filter");
sweep_up(ksu_context, cc_target);
exit(1);
}
}
if (all_rest_copy){
if ((retval = krb5_cc_initialize(ksu_context, cc_target, client))){
com_err(prog_name, retval,
"while erasing target cache");
exit(1);
}
}
target_pwd = getpwnam(target_user);
if (target_pwd->pw_shell)
shell = xstrdup(target_pwd->pw_shell);
else {
shell = _DEF_CSH;
}
#ifdef HAVE_GETUSERSHELL
if (!standard_shell(target_pwd->pw_shell) && source_uid) {
fprintf(stderr, "ksu: permission denied (shell).\n");
sweep_up(ksu_context, cc_target);
exit(1);
}
#endif
if (target_pwd->pw_uid){
if(set_env_var("USER", target_pwd->pw_name)){
fprintf(stderr,"ksu: couldn't set environment variable USER\n");
sweep_up(ksu_context, cc_target);
exit(1);
}
}
if(set_env_var( "HOME", target_pwd->pw_dir)){
fprintf(stderr,"ksu: couldn't set environment variable USER\n");
sweep_up(ksu_context, cc_target);
exit(1);
}
if(set_env_var( "SHELL", shell)){
fprintf(stderr,"ksu: couldn't set environment variable USER\n");
sweep_up(ksu_context, cc_target);
exit(1);
}
if(set_env_var( KRB5_ENV_CCNAME, cc_target_tag)){
fprintf(stderr,"ksu: couldn't set environment variable %s\n",
KRB5_ENV_CCNAME);
sweep_up(ksu_context, cc_target);
exit(1);
}
if (setgid(target_pwd->pw_gid) < 0) {
perror("ksu: setgid");
sweep_up(ksu_context, cc_target);
exit(1);
}
if (initgroups(target_user, target_pwd->pw_gid)) {
fprintf(stderr, "ksu: initgroups failed.\n");
sweep_up(ksu_context, cc_target);
exit(1);
}
if ( ! strcmp(target_user, source_user)){
print_status("Leaving uid as %s (%ld)\n",
target_user, (long) target_pwd->pw_uid);
}else{
print_status("Changing uid to %s (%ld)\n",
target_user, (long) target_pwd->pw_uid);
}
#ifdef HAVE_SETLUID
if (setluid((uid_t) pwd->pw_uid) < 0) {
perror("setluid");
sweep_up(ksu_context, cc_target);
exit(1);
}
#endif
if (setuid(target_pwd->pw_uid) < 0) {
perror("ksu: setuid");
sweep_up(ksu_context, cc_target);
exit(1);
}
if (access( cc_target_tag_tmp, R_OK | W_OK )){
com_err(prog_name, errno,
"%s does not have correct permissions for %s, %s aborted",
target_user, cc_target_tag_tmp, prog_name);
exit(1);
}
if ( cc_source)
krb5_cc_close(ksu_context, cc_source);
if (cmd){
if ((source_uid == 0) || (source_uid == target_uid )){
exec_cmd = cmd;
}
if( !exec_cmd){
fprintf(stderr,
"Internal error: command %s did not get resolved\n",cmd);
exit(1);
}
params[0] = exec_cmd;
}
else{
params[0] = shell;
}
if (auth_debug){
fprintf(stderr, "program to be execed %s\n",params[0]);
}
if( keep_target_cache ) {
execv(params[0], params);
com_err(prog_name, errno, "while trying to execv %s",
params[0]);
sweep_up(ksu_context, cc_target);
exit(1);
}else{
statusp = 1;
switch ((child_pid = fork())) {
default:
if (auth_debug){
printf(" The child pid is %ld\n", (long) child_pid);
printf(" The parent pid is %ld\n", (long) getpid());
}
while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
if (WIFSTOPPED(statusp)) {
child_pgrp = tcgetpgrp(1);
kill(getpid(), SIGSTOP);
tcsetpgrp(1, child_pgrp);
kill(child_pid, SIGCONT);
statusp = 1;
continue;
}
break;
}
if (auth_debug){
printf("The exit status of the child is %d\n", statusp);
}
if (ret_pid == -1) {
com_err(prog_name, errno, "while calling waitpid");
}
sweep_up(ksu_context, cc_target);
exit (statusp);
case -1:
com_err(prog_name, errno, "while trying to fork.");
sweep_up(ksu_context, cc_target);
exit (1);
case 0:
execv(params[0], params);
com_err(prog_name, errno, "while trying to execv %s", params[0]);
exit (1);
}
}
}
#ifdef HAVE_GETUSERSHELL
int standard_shell(sh)
char *sh;
{
register char *cp;
char *getusershell();
while ((cp = getusershell()) != NULL)
if (!strcmp(cp, sh))
return (1);
return (0);
}
#endif
static char * ontty()
{
char *p, *ttyname();
static char buf[MAXPATHLEN + 4];
buf[0] = 0;
if ((p = ttyname(STDERR_FILENO))) {
if (strlen (p) > MAXPATHLEN) {
fprintf (stderr, "terminal name %s too long\n", p);
exit (1);
}
sprintf(buf, " on %s", p);
}
return (buf);
}
static int set_env_var(name, value)
char *name;
char *value;
{
char * env_var_buf;
env_var_buf = (char *) xcalloc(2 + strlen(name) + strlen(value),
sizeof(char));
sprintf(env_var_buf,"%s=%s",name, value);
return putenv(env_var_buf);
}
static void sweep_up(context, cc)
krb5_context context;
krb5_ccache cc;
{
krb5_error_code retval;
const char * cc_name;
struct stat st_temp;
krb5_seteuid(0);
if (krb5_seteuid(target_uid) < 0) {
com_err(prog_name, errno,
"while changing to target uid for destroying ccache");
exit(1);
}
cc_name = krb5_cc_get_name(context, cc);
if ( ! stat(cc_name, &st_temp)){
if ((retval = krb5_cc_destroy(context, cc))){
com_err(prog_name, retval,
"while destroying cache");
}
}
}
krb5_error_code
get_params(optindex, pargc, pargv, params)
int *optindex;
int pargc;
char **pargv;
char ***params;
{
int i,j;
char ** ret_params;
int size = pargc - *optindex + 2;
if ((ret_params = (char **) calloc(size, sizeof (char *)))== NULL ){
return ENOMEM;
}
for (i = *optindex, j=1; i < pargc; i++,j++){
ret_params[j] = pargv[i];
*optindex = *optindex + 1;
}
ret_params[size-1] = NULL;
*params = ret_params;
return 0;
}
static
#ifdef HAVE_STDARG_H
void print_status( const char *fmt, ...)
#else
void print_status (va_alist)
va_dcl
#endif
{
va_list ap;
#ifndef HAVE_STDARG_H
char *fmt;
va_start (ap);
fmt = va_arg (ap, char*);
if (!quiet) vfprintf(stderr, fmt, ap);
va_end(ap);
#else
if (! quiet){
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
#endif
}
char *get_dir_of_file(path)
const char *path;
{
char * temp_path;
char * ptr;
temp_path = xstrdup(path);
if ((ptr = strrchr( temp_path, '/'))) {
*ptr = '\0';
} else {
free (temp_path);
temp_path = xmalloc(MAXPATHLEN);
if (temp_path)
getcwd(temp_path, MAXPATHLEN);
}
return temp_path;
}
krb5_error_code
ksu_tgtname(context, server, client, tgtprinc)
krb5_context context;
const krb5_data *server, *client;
krb5_principal *tgtprinc;
{
return krb5_build_principal_ext(context, tgtprinc, client->length, client->data,
KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME,
server->length, server->data,
0);
}