/* 
 * $Id: login.c,v 1.1 1992/01/15 09:58:11 epeisach Exp $
 * $Source: /mit/patriot/devel/src/rkinit/rkinitd/RCS/login.c,v $
 * $Author: epeisach $
 *
 * This is a "login" utility library
 */

#include <sys/time.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <signal.h>
#include <pwd.h>
#include <stdio.h>
#include <errno.h>
#include <syslog.h>
#include <strings.h>
#include <sys/dir.h>
#include <sys/wait.h>
#include <rkinit.h>
#include <rkinit_err.h>
#include <rkinit_private.h>


#if !defined(lint) && !defined(SABER) && !defined(LOCORE) && defined(RCS_HDRS)
static char *rcsid = "$Id: login.c,v 1.1 1992/01/15 09:58:11 epeisach Exp $";
#endif /* lint || SABER || LOCORE || RCS_HDRS */

#ifndef NOATTACH
#define NOATTACH "/etc/noattach"
#endif

/* homedir status */
#define HD_LOCAL 0
#define HD_ATTACHED 1
#define HD_TEMP 2

extern char *malloc();

static int added_to_passwd=FALSE;

athena_cleanup(p)
struct passwd *p; 
{
	int ret;
	if(added_to_passwd) ret=remove_from_passwd(p);
	return(ret);
}

add_to_passwd(p)
struct passwd *p;
{
    int i, fd = -1;
    FILE *etc_passwd;

#ifdef _IBMR2
    struct userpw pw_stuff;
    int id;

    /* Do real locking of the user database */
    for (i = 0; i < 10; i++)
      if (setuserdb(S_WRITE) == 0) {
	fd = 1;
	break;
      }
      else
	sleep(1);

    if (fd != 1)
      return(errno);

/* Need to have these to create empty stanzas, in the */
/* /etc/security/{environ, limits, user} files so that they pick up */
/* the default values */
    putuserattr(p->pw_name,(char *)NULL,((void *) 0),SEC_NEW);
    putuserattr(p->pw_name,S_ID,p->pw_uid,SEC_INT);
    putuserattr(p->pw_name,S_PWD,"!",SEC_CHAR);
    putuserattr(p->pw_name,S_PGRP,"mit",SEC_CHAR);
    putuserattr(p->pw_name,S_HOME,p->pw_dir,SEC_CHAR);
    putuserattr(p->pw_name,S_SHELL,p->pw_shell,SEC_CHAR);
    putuserattr(p->pw_name,S_GECOS,p->pw_gecos,SEC_CHAR);
    putuserattr(p->pw_name,S_LOGINCHK,1,SEC_BOOL);
    putuserattr(p->pw_name,S_SUCHK,1,SEC_BOOL);
    putuserattr(p->pw_name,S_RLOGINCHK,1,SEC_BOOL);
    putuserattr(p->pw_name,S_ADMIN,0,SEC_BOOL);
    putuserattr(p->pw_name,(char *)NULL,((void *) 0),SEC_COMMIT);
    enduserdb();

/* Now, lock the shadow password file */
    fd = -1;
    for (i = 0; i < 10; i++)
      if (setpwdb(S_WRITE) == 0) {
	fd = 1;
	break;
      }
      else
	sleep(1);

    if (fd != 1)
      return(errno);

    strncpy(pw_stuff.upw_name,p->pw_name,PW_NAMELEN);
    pw_stuff.upw_passwd = p->pw_passwd;
    pw_stuff.upw_flags = 0;
    pw_stuff.upw_lastupdate = 0;
    putuserpw(&pw_stuff);
    endpwdb();
#else	/* RIOS */
    i = 10;
    while (i-- > 0 &&
	   (fd = open("/etc/ptmp", O_WRONLY|O_CREAT|O_EXCL, 0644)) < 0)
      sleep(1);
    if (fd < 0) {
	syslog(LOG_CRIT, "failed to lock /etc/passwd for insert");
	return (-1);
    }

    etc_passwd = fopen("/etc/passwd", "a");
    if (etc_passwd == NULL) {
	(void) close(fd);
	(void) unlink("/etc/ptmp");
	return(-1);
    }
    fprintf(etc_passwd, "%s:%s:%d:%d:%s:%s:%s\n",
	    p->pw_name,
	    p->pw_passwd,
	    p->pw_uid,
	    p->pw_gid,
	    p->pw_gecos,
	    p->pw_dir,
	    p->pw_shell);
    (void) fclose(etc_passwd);
    (void) close(fd);
    (void) unlink("/etc/ptmp");
#endif	/* RIOS */

    added_to_passwd = TRUE;
    return(0);
}

#if defined(_AIX) && defined(_IBMR2)
remove_from_passwd(p)
struct passwd *p;
{
  char buf[1024];

  switch (quota_pid = fork()) {
  case -1:
      fprintf(stderr, "Unable to fork to edit password file.\n");
      break;
  case 0:
      execl("/bin/rmuser", "rmuser", "-p", p->pw_name, NULL);
      fprintf(stderr, "Unable to exec rmuser to edit passwd file.\n");
      _exit(-1);
  }
}
#else /* RIOS */
remove_from_passwd(p)
struct passwd *p;
{
    int fd, len, i;
    char buf[512];
    FILE *old, *new;

    for (i = 0; i < 10; i++)
      if ((fd = open("/etc/ptmp", O_WRONLY | O_CREAT | O_EXCL, 0644)) == -1 &&
	  errno == EEXIST)
	sleep(1);
      else
	break;
    if (fd == -1) {
	if (i < 10)
	  return(errno);
    }

    old = fopen("/etc/passwd", "a");
    if (old == NULL) {
	(void) close(fd);
	(void) unlink("/etc/ptmp");
	return(-1);
    }
    new = fdopen(fd, "w");
    len = strlen(p->pw_name);

    while (fgets(buf, sizeof(buf) - 1, old)) {
	if (strncmp(p->pw_name, buf, len - 1) || buf[len] != ':')
	  fputs(buf, new);
    }

    (void) fclose(old);
    (void) fclose(new);
    (void) rename("/etc/ptmp", "/etc/passwd");
    return(0);
}
#endif /* RIOS */

#define MAXGNAMELENGTH	32

#ifdef _IBMR2
char *add_to_group(name, glist)
char *name;
char *glist;
{
  char *cp,*p;			/* temporary */
  char *gname, *new_list;
  char *fix_gname();
  int gid;
  int len,namelen;
  int i, fd = -1, ngroups,found;
  static char data[BUFSIZ];
  char *members;
  
  for (i = 0; i < 10; i++)
    if (setuserdb(S_WRITE) == -1)
      sleep(1);
    else {
      fd = 1;
      break;
    }

  if (fd == -1) {
    sprintf(data, "Locking of user db failed: errno %d", errno);
    enduserdb();
    return(data);
  }
  
  /* count groups (there are 2 ':'s in the group list per group, except
     the first group only has one) */
  cp = glist;
  ngroups = 1;
  while (cp = index(cp, ':')) {
    ngroups++;
    cp++;
  }
  ngroups /= 2;
  if (ngroups > NGROUPS) {
    fprintf(stderr, "Warning - you are in too many groups.  Some of them will be ignored.\n");
  }

  cp = glist;
  for (i = 0; i < ngroups; i++) {
    gname = cp;
    cp = index(cp, ':');
    *cp++ = '\0';
    gid = atoi(cp);
    if (cp = index(cp, ':'))
      *cp++ = '\0';
    
    /* AIX restricts group names to 8 alphanumeric characters; the first */
    /* must be alpha.  Many existing groups do not meet this criteria, and */
    /* must be mapped into a group named "G###" where ### is the gid */
    /* Not a perfect solution- */
    gname = fix_gname(gname,gid);
    /* Make sure the group exists */
    if (getgroupattr(gname,S_USERS,(void *)&members,SEC_LIST) == -1) {
      if (errno == ENOENT) {
	/* Create group */
	if (putgroupattr(gname,(char *)NULL,((void *) 0),SEC_NEW) != 0) {
	  fprintf(stderr,"Error creating group %s (%d)\n",gname,gid);
	  continue;
	}
	putgroupattr(gname,S_ID,gid,SEC_INT);
	putgroupattr(gname,S_ADMIN,0,SEC_BOOL);
	putgroupattr(gname,(char *)NULL,((void *) 0),SEC_COMMIT);
      }
    }
    /* Add user to group */

    p = members;
    found = 0;
    while (1) {
      if (strcmp(p,name) == 0) {
	found = 1;
	break;
      }
      p = index(p,'\0')+1;
      if (*p == '\0')
	break;
    }
  
    if (found)
      continue;
    
    /* Add user to group */
    len = p - members + 1;
    namelen = strlen(name);
    new_list = (char *)malloc(len+namelen+1);
    bcopy(name,new_list,namelen+1);
    bcopy(members,(char *)(new_list+namelen+1),len);
    if (putgroupattr(gname,S_USERS,new_list,SEC_LIST) == -1) {
      sprintf(data,"Update of group %s failed; errno %d", gname, errno);
      return(data);
    }
    
    if (putgroupattr(gname,(char *)NULL,((void *) 0),SEC_COMMIT) == -1) {
      sprintf(data,"Commit of group %s failed; errno %d", gname, errno);
      return(data);
    }
  }
  enduserdb();
  return(NULL);
}

char *
fix_gname(gname,gid)
     char *gname;
     int gid;
{
  int valid=1;
  static char mname[8];
  char *p;

  if ((strlen(gname) > 8) || (!isalpha(gname[0])))
    valid = 0;
  else {
    for(p=gname;(*p)!='\0';p++)
      if (!isalnum(*p)) {
	valid = 0;
	break;
      }
  }

  if (valid)
    return(gname);
  else {
    sprintf(mname,"G%d",gid);
    return(mname);
  }
}

#else /* _IBMR2 */
char *add_to_group(name, glist)
char *name;
char *glist;
{
    char *cp;			/* temporary */
    char **gnames = NULL, **gids;/* array of group names, numbers */
    int i, fd = -1, ngroups;
    int namelen = strlen(name);
    FILE *etc_group, *etc_gtmp;
    char data[BUFSIZ+MAXGNAMELENGTH];	/*  space to add new username */

    for (i = 0; i < 10; i++)
      if ((fd = open("/etc/gtmp", O_RDWR | O_EXCL | O_CREAT, 0644)) == -1 &&
	  errno == EEXIST)
	sleep(1);
      else
	break;
    if (fd == -1) {
	if (i < 10) {
	    sprintf(data, "Update of group file failed: errno %d", errno);
	    return(data);
	} else
	  unlink("/etc/gtmp");
    }

    if ((etc_gtmp = fdopen(fd, "w")) == NULL ||	/* can't happen ? */
	(etc_group = fopen("/etc/group", "r")) == NULL) {
	(void) close(fd);
	(void) unlink("/etc/gtmp");
	return("Failed to open temporary group file to update your access control groups.");
    }

    /* count groups (there are 2 ':'s in the group list per group, except
       the first group only has one) */
    cp = glist;
    ngroups = 1;
    while (cp = index(cp, ':')) {
	ngroups++;
	cp++;
    }
    ngroups /= 2;
    if (ngroups > NGROUPS) {
	fprintf(stderr, "Warning - you are in too many groups.  Some of them will be ignored.\n");
    }

    if ((gnames = (char **)malloc(ngroups * sizeof(char *))) == NULL ||
	(gids = (char **)malloc(ngroups * sizeof(char *))) == NULL) {
	if (gnames)
	  free(gnames);
	(void) fclose(etc_gtmp);
	(void) fclose(etc_group);
	(void) unlink("/etc/gtmp");
	return("Ran out of memory while updating your access control groups");
    }
    cp = glist;
    for (i = 0; i < ngroups; i++) {
	gnames[i] = cp;
	cp = index(cp, ':');
	*cp++ = '\0';
	gids[i] = cp;
	if (cp = index(cp, ':'))
	  *cp++ = '\0';
    }

    while (fgets(data, sizeof(data) - MAXGNAMELENGTH, etc_group)) {
	char *gpwd, *gid, *guserlist = NULL;
	int add = -1;	/* index of group entry in user's hesiod list */

	if (data[0] == '\0')
	  continue;	/* empty line ??? */

	/* If a valid format line, check to see if the user belongs in
	   the group.  Otherwise, just write it out as-is. */
	if ((gpwd = index(data, ':')) &&
	    (gid = index(++gpwd, ':')) &&
	    (guserlist = index(++gid, ':'))) {
	    *guserlist = '\0';
	    /* step through our groups */
	    for (i = 0; i < ngroups; i++)
	      if (gids[i] && !strcmp(gid, gids[i])) {
		  /* found it, now check users */
		  for (cp = guserlist; cp; cp = index(cp, ',')) {
		      cp++;
		      if (!strncmp(name, cp, strlen(name)) &&
			  (cp[namelen] == ',' ||
			   cp[namelen] == ' ' ||
			   cp[namelen] == '\n')) {
			  gnames[i] = NULL;
			  gids[i] = NULL;
			  break;
		      }
		  }
		  if (gnames[i] != NULL)
		    add = i;
		  break;
	      }
	    *guserlist++ = ':';
	}
	if (add != -1) {
	    char *end_userlist = guserlist + strlen(guserlist);
	    *(end_userlist-1) = ',';	/* overwrite newline */
	    strcpy(end_userlist, name);
	    *(end_userlist + namelen) = '\n';
	    *(end_userlist + namelen + 1) = 0;
	    gnames[add] = NULL;
	    gids[add] = NULL;
	}
	if (fputs(data, etc_gtmp) == EOF && ferror(etc_gtmp)) {
	    (void) fclose(etc_gtmp);
	    goto fail;
	}
    }	/* end while */

    /* now append all groups remaining in gids[], gnames[] */
    for (i = 0;i < ngroups;i++)
      if (gids[i] && gnames[i]) {
	  fprintf(etc_gtmp, "%s:*:%s:%s\n", gnames[i], gids[i], name);
      }

    (void) fchmod(fd, 0644);
    if (fclose(etc_gtmp) == EOF)
      goto fail;

    (void) fclose(etc_group);
    free(gids);
    free(gnames);
    if (rename("/etc/gtmp", "/etc/group") == 0)
      return(NULL);
    else {
	sprintf(data, "Failed to install your access control groups in the group file; errno %d", errno);
	return(data);
    }

 fail:
    (void) unlink(etc_gtmp);
    (void) fclose(etc_group);
    free(gids);
    free(gnames);
    return("Failed to update your access control groups");
}
#endif /* _IBMR2 */


int attach_state, attach_pid, attach_status, homedir_status;
#define file_exists(f) (access((f), F_OK) == 0)

char *attachhomedir(pwd)
struct passwd *pwd;
{
    struct stat stb;
    int i;
    union wait status;

    /* Delete empty directory if it exists.  We just try to rmdir the 
     * directory, and if it's not empty that will fail.
     */
    rmdir(pwd->pw_dir);

    /* If a good local homedir exists, use it */
    if (file_exists(pwd->pw_dir) && !IsRemoteDir(pwd->pw_dir) &&
	homedirOK(pwd->pw_dir))
      return(NULL);

    /* Using homedir already there that may or may not be good. */
    if (file_exists(NOATTACH) && file_exists(pwd->pw_dir) &&
	homedirOK(pwd->pw_dir)) {
	return("This workstation is configured not to attach remote filesystems.");
    }

    if (file_exists(NOATTACH))
      return("This workstation is configured not to create local home directories.  Please contact the system administrator for this machine or a consultant for further information.");

    /* attempt attach now */
    attach_state = -1;
    switch (attach_pid = fork()) {
    case -1:
	return("Unable to attach your home directory (could not fork to create attach process).");
    case 0:
 	if (setuid(pwd->pw_uid) != 0) {
#if 0
 	    fprintf(stderr, "Could not execute attach command as user %s,\n",
 		    pwd->pw_name);
 	    fprintf(stderr, "Filesystem mappings may be incorrect.\n");
#endif
 	}
	/* don't do zephyr here since user doesn't have zwgc started anyway */
	execlp("/bin/athena/attach", "attach", "-quiet", "-nozephyr", pwd->pw_name, NULL);
	_exit(-1);
    default:
	break;
    }
    while (wait(&status) != attach_pid) 
	    ;

    attach_state = status.w_retcode;

    if (attach_state != 0 || !file_exists(pwd->pw_dir)) {
	return("Your home directory could not be attached.");
    }

    if (attach_state != 0 || !file_exists(pwd->pw_dir)) {
	/* do tempdir here */
	char buf[BUFSIZ];
	homedir_status = HD_TEMP;
	return("Home directory unable to be attached.");

    } else
      homedir_status = HD_ATTACHED;
    return(NULL);
}


/* Function Name: IsRemoteDir
 * Description: Stolen form athena's version of /bin/login
 *              returns true of this is an NFS directory.
 * Arguments: dname - name of the directory.
 * Returns: true or false to the question (is remote dir).
 *
 * The following lines rely on the behavior of Sun's NFS (present in
 * 3.0 and 3.2) which causes a read on an NFS directory (actually any
 * non-reg file) to return -1, and AFS which also returns a -1 on
 * read (although with a different errno).  This is a fast, cheap
 * way to discover whether a user's homedir is a remote filesystem.
 * Naturally, if the NFS and/or AFS semantics change, this must also change.
 */

IsRemoteDir(dir)
char *dir;
{
  int f;
  char c;
  struct stat stbuf;
  
  if (lstat(dir, &stbuf))
    return(FALSE);
  if (!(stbuf.st_mode & S_IFDIR))
    return(TRUE);

  if ((f = open(dir, O_RDONLY, 0)) < 0)
    return(FALSE);

  if (read(f, &c, 1) < 0) {
      close(f);
      return(TRUE);
  }

  close(f);
  return(FALSE);
}


/* Function Name: homedirOK
 * Description: checks to see if our homedir is okay, i.e. exists and 
 *	contains at least 1 file
 * Arguments: dir - the directory to check.
 * Returns: TRUE if the homedir is okay.
 */

int homedirOK(dir)
char *dir;
{
    DIR *dp;
#ifdef POSIX
    struct dirent *temp;
#else
    struct direct *temp;
#endif
    int count;

    if ((dp = opendir(dir)) == NULL)
      return(FALSE);

    /* Make sure that there is something here besides . and .. */
    for (count = 0; count < 3 ; count++)
      temp = readdir(dp);

    closedir(dp);
    return(temp != NULL);
}


char *strsave(s)
char *s;
{
    char *ret = malloc(strlen(s) + 1);
    strcpy(ret, s);
    return(ret);
}

