/*
* su.c 1.0
* Modified by tim lashua from James Wiegand's Berkeley based su.
*
* This su now takes the shell from the  password file instead of always
* using a default shell. If no shell is specified in the password file,
* /bin/sh is used. The -l or --login options can be used to invoke the
* shell as if it were a login shell. Options to log all su activity or
* just su to root have been added. I don't know what the standard
* format for a sulog is, if there is one. The sulog created by this su
* has the following format: there are three colon seperated fields 
* containing, respectively, the login name of the person running su, 
* the login name of the person being su'd, and the time and date.
*
* I have compiled this with gcc 2.1 on Linux 0.95c+ with the following
* options: 
*	 	-O -Wall -fstrength-reduce -static
*
*/



#include <getopt.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

/* the max path length (+1) for the shell, and home directory */
#define SUPATHLEN 60
#define USERLEN 9   /* maximum user name length + 1 */
#define FAILURE -1

#define DEF_SHELL "/bin/sh" /* default shell if none is in /etc/passwd */
#define LOG_FILE "/usr/adm/sulog"
#define LOG_ROOT_ONLY 0 /* true if you only want su's to root logged */
#define LOG_ALL 0	/* true if you want to log all su's */

struct option longopts[] = 
  {
    {"login", 0, NULL, 'l'},
    {NULL, 0, NULL, 0}
  };

int main(int argc, char **argv)
{
struct passwd *pwd;
char *p;
uid_t cur_uid;
int  ch, login = 0;

char *new_user, username[USERLEN], *nargv[4], **np;
char new_home_env[SUPATHLEN + 5]; /* + HOME= */
char new_shell_env[SUPATHLEN + 6]; /* + SHELL= */
char new_shell[SUPATHLEN];

FILE *logfile;
long su_time;
np = &nargv[3];
*np-- = NULL;

while ((ch = getopt_long( argc, argv, "l", longopts, (int *) 0 )) != EOF)
  switch((char)ch){
  case 'l':
    login = 1;
    break;
  case '?':
  default:
    fprintf(stderr, "Usage: su [-l] [--login] username \n");
    exit(1);
  }

argv += optind;
errno = 0;

/* get current login password entry */
cur_uid = getuid(); 
pwd = getpwuid(cur_uid);

if(pwd == NULL){
  fprintf(stderr, "su: invalid login\n"); 
  exit(1);
}

strcpy( username, pwd->pw_name ); /* store current login name */

/* get target login information, default to root */
new_user = *argv ? *argv : "root";
	
pwd = getpwnam(new_user); /* get password entry for 'new_user' */
if(pwd == NULL)
  {
    fprintf(stderr, "su: unknown login %s\n", new_user);
    exit(1);
  }

su_time = time(NULL); /* the time this program was run */

if(LOG_ALL){	/* log all attempts to su to a valid user */
  logfile = fopen(LOG_FILE, "a");
    fprintf(logfile,  "%s:%s:%s", username, new_user, \
	    asctime(localtime(&su_time)));
  fclose(logfile);
}
else if(LOG_ROOT_ONLY){		/* log attempts to su root */
  if (!strcmp(new_user, "root")){
    logfile = fopen(LOG_FILE, "a");
    fprintf(logfile,  "%s:%s:%s", username, new_user, \
	    asctime(localtime(&su_time)));
    fclose(logfile);
  }
}

 /* if a password is required, check it */
 /* a password is required if the current user is not root and the */
 /* 'new_user' has a password */

if(cur_uid){	/* if current user isn't root */
  if(*pwd->pw_passwd){
    p = getpass("Password:");
    if(strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))){
      fprintf(stderr, "Sorry\n");
      exit(1);
    }
  }
}

/* set user and group id */

if(setgid(pwd->pw_gid) == FAILURE ){
  perror("su: setgid");
  exit(1);
}

if(setuid( pwd->pw_uid) == FAILURE ){
  perror("su: setuid");
  exit(1);
}

 /* determine which shell to execute. get the users shell from the */
 /* entry in /etc/passwd, if no shell is specified there use /bin/sh */

strcpy(new_shell, pwd->pw_shell);

if (!(new_shell[0] == '/')){
  strcpy(new_shell, DEF_SHELL);
  fprintf(stderr, "su: no shell. using %s\n", DEF_SHELL);
}

/* get and reset the environment variables HOME  and SHELL */
strcpy(new_home_env, "HOME=");
strcat(new_home_env, pwd->pw_dir);
putenv(new_home_env);

strcpy(new_shell_env, "SHELL=");
strcat(new_shell_env, new_shell);
putenv(new_shell_env);

/* should this be treated as a login shell ? */
*np = login ? "-su" : "su"; 

/* start the new shell */
execv(new_shell, np);

return(0);

}
