/* 
** adduser 1.0: add a new user account (For systems not using shadow)
** With a nice little interface and a will to do all the work for you.
**
** Craig Hagan
** hagan@opine.cs.umass.edu
**
** Modified to really work, look clean, and find unused uid by Chris Cappuccio
** chris@slinky.cs.umass.edu
**
** cc -O -o adduser adduser.c
** Use gcc if you have it... (political reasons beyond my control) (chris)
**
** I've gotten this program to work with success under Linux (without
** shadow) and SunOS 4.1.3. I would assume it should work pretty well
** on any system that uses no shadow. (chris)
**
** If you have no crypt() then try
** cc -DNO_CRYPT -O -o adduser adduser.c xfdes.c
** I'm not sure how login operates with no crypt()... I guess
** the same way we're doing it here.
*/

#include <pwd.h>
#include <grp.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <sys/time.h>
#include <sys/stat.h>

#define PASSWD_FILE	"/etc/passwd"
#define DEFAULT_SHELL	"/bin/bash"
#define DEFAULT_HOME	"/home"
#define DEFAULT_GROUP	100	/* 100 is 'users' under Slackware Linux */
#define FIRST		500	/* First UID to use */
#define DEFAULT_PERMS	0751	/* Perms for the users home directory */

FILE *bull;	/* Bullshit way to find a shadow password file */
int unused_uid,nag;
char *crypt();

main()
{
  char foo[32], commandbuf[80], bogusbuf[12];			

  /* Note from PJV: I added the bogusbuf variable, because taking out a */
  /* similar character buffer broke the program. Take bogusbuf out and  */
  /* you'll see what I mean. I can't figure it out, but as far as I'm   */
  /* concerned if 12 bytes fixes the problem then it ain't worth the    */
  /* hassle... :^) */
  /* Maybe commandbuf[] should be longer? */
  
  /* a user read in from /etc/passwd this */
  /* to see if the username is in use     */				
  /* also for misc. inputs and other bullshit that i fell it should be */

  char uname[9],person[32],passwd[9],dir[32],shell[32],salt[2];
  
  /* uname=user name,person=full name,passwd=password,dir=home dir,
   * shell=shell,salt=for password encryption */

  unsigned int group,uid;

  /* the group and uid of the new user */

  int bad=0,done=0,correct=0,gets_warning=0;

  /* flags, in order:
   * bad to see if the username is in /etc/passwd, or if strange stuff has
   * been typed if the user might be put in group 0
   * done allows the program to exit when a user has been added
   * correct loops until a password is found that isn't in /etc/passwd
   * gets_warning allows the fflush to be skipped for the first gets
   * so that output is still legible
   */

  time_t tm;

  struct passwd *pw;

  FILE *passwd_file;  /* Yep, it's a file allright */
  
  /* set unused uid to FIRST (#defined above) so that find_unused picks
   * a uid over FIRST (assuming we do everything else right :)
   */

  unused_uid = FIRST;

  /* The real program starts HERE! */

  /* Smile, it's the 2nd best thing you can do with your lips */

  /* Lesse if we know what we're doing here... */
  
  if(geteuid()!=0)
  {
     printf("It seems you don't have access to add a new user.  Try\n");
     printf("logging in as root or su root to gain super-user access.\n");
     exit(1);
  }
  
  /* We don't support shadow password files, let's check and make
   * sure we're not using em. Sure we could use filesearch or something
   * but i don't feel like it.
   */

  if((bull=fopen("/etc/shadow","r"))!=NULL) /* SYSV Shadow */
  found_shadow();
  if((bull=fopen("/etc/master.passwd","r"))!=NULL) /* Hmm, BSD */
  found_shadow();

  /* The scent of SunOS NIS is in the air */

  if((bull=fopen("/var/yp","r"))!=NULL)
  {
/* Linux has a /var/yp (since YP can be run), but it isn't usually running, so */
/* we don't need to warn the user. Even when YP is active on Linux, I don't */
/* think it has a 'make passwd' command. */
/* */ 
/*  printf("You have Yellow Pages (NIS). Don't forget to 'make passwd' under /var/yp\n"); */
/*  printf("after you have made your new user.\n"); */
  fclose(bull);
  }

  while(!correct)		/* loop until a "good" uname is chosen */
    {				/* good = not in /etc/passwd */
      while(!done)
	{
          printf("\nAdding a new user. The username should be not exceed 8 characters\n");
          printf("in length, or you many run into problems later.\n");
	  printf("\nEnter login name for new account (^C to quit): ");

	  if(gets_warning)	/* if the warning was already shown */
	    fflush(stdout);	/* fflush stdout, otherwise set the flag */
	  else
	    gets_warning=1;

	  gets(uname);

          /* what I saw here before made me think maybe I was running DOS */
          /* might this be a solution? (chris) */
	  if(nag=getpwnam(uname) != NULL)
	    {
	      printf("That name is in use, choose another.\n");
	      done=0;
	    }
	  else done=1;
	}

      /* all set, get the rest of the stuff */

      printf("\nEditing information for new user [%s]\n",uname);
  
      printf("\nFull Name: ",uname);
      gets(person);
      
      printf("GID [%d]: ",DEFAULT_GROUP);
      gets(foo);
      group==atoi(foo);

      if (group==0)	/* You're not allowed to make root group users! */
	group=DEFAULT_GROUP;

      unused_uid = find_unused(++unused_uid);	/* our k-eleet unused! */

      printf("\nUID [%d]: ",unused_uid);
      gets(foo);
      uid=atoi(foo);

      if(uid==0) /* this is how i detect if you just hit return. */
        uid=unused_uid;
                 /* it may also disable you from making a root user, but
                  * doesen't that sound more like a feature?
                  */

      if((pw=getpwuid(uid))!=NULL)	/* uhh. duhh. egh.. */
      {
        printf("\nWarning: UID [%d] is already in use, this would conflict with\n",uid);
        printf("who is already owns that user ID. [%s]'s UID has been reset to\n",uname);
        printf("the last unused UID: [%d].\n",unused_uid);
        uid=unused_uid;
      }
      
      fflush(stdin);
      
      printf("\nHome Directory [%s/%s]: ",DEFAULT_HOME,uname);
      fflush(stdout);
      gets(dir);

      if (!strcmp(dir,""))
	sprintf(dir,"%s/%s",DEFAULT_HOME,uname);
      fflush(stdin);

      printf("\nShell [%s]: ",DEFAULT_SHELL);
      fflush(stdout);
      gets(shell);
      
      if (!strcmp(shell,""))
	sprintf(shell,"%s",DEFAULT_SHELL);
      
      printf("\nPassword [%s]: ",uname);
      fflush(stdout);
      gets(passwd);

      if (!strcmp(passwd,""))
	sprintf(passwd,"%s",uname);
      {
	time(&tm);
	salt[0] = (tm & 0x0f) +	'A';
	salt[1] = ((tm & 0xf0) >> 4) + 'a';
      }
  
      printf("\nInformation for new user [%s]:\n",uname);
      printf("Home directory: [%s] Shell: [%s]\n",dir,shell);
      printf("Password: [%s] uid: [%d] gid: [%d]\n",passwd,uid,group);
      printf("\nIs this correct? [y/N]: ");
      fflush(stdout);
      gets(foo);

      done=bad=correct=(foo[0]=='y'||foo[0]=='Y');

      if(bad!=1)
       printf("\nUser [%s] not added\n",uname);
    }

  printf("\nAdding login [%s] and making directory [%s]\n",uname,dir);
  mkdir(dir,DEFAULT_PERMS);

  system("cp /etc/passwd /etc/passwd.OLD"); /* Let's have safe sex */
  
  passwd_file=fopen(PASSWD_FILE,"a");

#ifdef NO_CRYPT
  fprintf(passwd_file,"%s:%s:%d:%d:%s:%s:%s\n"
  	  ,uname,fcrypt(passwd,salt),uid,group,person,dir,shell);
#else
  fprintf(passwd_file,"%s:%s:%d:%d:%s:%s:%s\n"
	  ,uname,crypt(passwd,salt),uid,group,person,dir,shell);
#endif

  fflush(passwd_file);
  fclose(passwd_file);
  /* yes, I fixed uid and group being screwed around (chris) */

  /* make sure that they own their directory -- its kinda nice :) */
/*  chown(dir,uid,group); (removed due to hacks below) */

  /* These are the Slackware hacks, added by Patrick Volkerding 12/3/93 */
  /* ...and changed some more on Mon May  2 14:43:34 CDT 1994 */
  printf("\nAdding the files from the /etc/skel directory:\n");
  fflush(stdout);
  /* First, we "give" the /etc/skel directory to the new user: */
  sprintf(commandbuf,"chown --recursive %d.%d /etc/skel 2> /dev/null",uid,group);
  system(commandbuf);
  /* Then, we copy the files owned by the new user into the new user's home
     directory. This way, if there are already files in the user's home
     directory (say, from a backup), the ownership of those files won't be
     changed. Some say this is progress. ;^) */
  sprintf(commandbuf,"( cd /etc/skel ; cp -a --verbose . %s )",dir);
  system(commandbuf);
  /* Give this stuff back to root. By sure to put the uid/gid you want for the
     default ownership of /etc/skel into the line below if 0.0 isn't good. */
  sprintf(commandbuf,"chown --recursive 0.0 /etc/skel 2> /dev/null");
  system(commandbuf);
  sprintf(commandbuf,"touch /var/spool/mail/%s",uname);
  system(commandbuf);
  sprintf(commandbuf,"chown %d.mail /var/spool/mail/%s",uid,uname);
  system(commandbuf);
  sprintf(commandbuf,"chmod 660 /var/spool/mail/%s",uname);
  system(commandbuf);
  printf("\n\n");
  fflush(stdout);
  /* End SlackHacks */

}

/* Here is our trade secret patented copyrighted code to find an unused UID */

find_unused(begin)
	int begin;
{
	int trial;
	struct passwd *pw;
	trial = begin - 1;

        printf("\nChecking for an available UID after %d\n",FIRST);

	while ((pw = getpwuid(++trial)) != NULL)
	{
		printf("%d...",trial);
	}
	
	printf("\nFirst unused uid is %d\n", trial);

	return(trial);
}

found_shadow()
{
	printf("Sorry, it seems you are using some form of shadow password utilities\n");
	printf("instead of a normal password file which contains the actual encrypted\n");
	printf("password. You should use 'useradd' or equivalent user addition\n");
	printf("utility to make sure the new user is added correctly.\n");
	fclose(bull);
	exit(1);
}
