
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <signal.h>
#include <errno.h>
#include <syslog.h>
#include <netdb.h>
#include <krb.h>
#include <hesiod.h>

#ifdef linux
#include <sys/sysmacros.h>
#endif

#define TRUE 1
#define FALSE 0

#define KRB_ENVIRON	"KRBTKFILE"
#define KRB_TK_DIR	"/tmp/tkt_"
#define KRBTKLIFETIME	DEFAULT_TKT_LIFE

#define PROTOTYPE_DIR	"/usr/athena/lib/prototype_tmpuser"
#define TEMP_DIR_PERM	0755

#define _PATH_NOCREATE		"/etc/nocreate"
#define _PATH_NOREMOTE		"/etc/noremote"
#define _PATH_NOATTACH		"/etc/noattach"
#define _PATH_GOREGISTER	"/usr/etc/go_register"

#undef  NGROUPS
#define NGROUPS		16
#define MAX_GROUPS	(NGROUPS - 2)

int hesiod_user = 0;
int kerberos_user = 0;
int afs_user = 0;
int attachedflag = 0;
int tmpdirflag = 0;

extern int errorprtflag;
extern int remote_login;

int athena_login (struct passwd **pwdp, char *username, char *password);
int get_tickets (char *username, char *password, struct passwd *pwd);
int verify_krb_tgt (char *realm);
void encrypt_password (struct passwd *pwd, char *password);
void athena_get_homedir (struct passwd *pwd);
int attach_homedir (struct passwd *pwd);
void detach_homedir (struct passwd *pwd);
int isremotedir (char *dir);
int goodhomedir (struct passwd *pwd);
int make_homedir (struct passwd *pwd);
void insert_pwent (struct passwd *pwd);
int remove_pwent (struct passwd *pwd);
void get_groups (struct passwd *pwd);
char *add_to_group (char *name, int primary, char *glist);
void athena_init_wgfile (void);
void athena_do_fork (struct passwd *pwd);

int athena_login (struct passwd **pwdp, char *username, char *password)
{
    struct passwd *pwd;
    char *p;

    hesiod_user = FALSE;
    kerberos_user = FALSE;
    errorprtflag = FALSE;

    /* check the args */
    if (! pwdp || ! username || ! password)
	return (FALSE);

    pwd = *pwdp;
    if (! pwd) {
	/* do we have a used to add to /etc/passwd */
	if ((pwd = hes_getpwnam (username)) == NULL)
	    return (FALSE);
	/* are we allowed to add users to /etc/passwd */
	if ((access (_PATH_NOCREATE, F_OK) == 0) ||
	    (access (_PATH_NOREMOTE, F_OK) == 0 && remote_login)) {
	    printf ("You are not allowed to log in here.\n");
	    errorprtflag = TRUE;
	    return (FALSE);
	}
	hesiod_user = TRUE;
    }

    kerberos_user = get_tickets (username, password, pwd);

    /* hesiod users must have tickets */
    if (hesiod_user) {
	if (! kerberos_user) {
	    hesiod_user = FALSE;
	    return (FALSE);
	}
	encrypt_password (pwd, password);
	insert_pwent (pwd);
	*pwdp = pwd;
    }

    /* Users in the passwd file simply need to get tickets.
     * Other users get a standard password check.
     */
    if (! kerberos_user) {
	p = (char *) crypt (password, pwd->pw_passwd);
	if (strcmp (p, pwd->pw_passwd) != 0)
	    return (FALSE);
    }

    /* Login successful.  Finish up. */
    get_groups (pwd);
    return (TRUE);
}

int get_tickets (char *username, char *password, struct passwd *pwd)
{
    char tkfile[256], realm[256];
    int krbval, forkval;
    extern char *tty; /* name of tty without "/dev/" */

    /* Set up the ticket file environment variable */
    strcpy (tkfile, KRB_TK_DIR);
    strcat (tkfile, tty);
    unlink (tkfile);
    setenv (KRB_ENVIRON, tkfile, 1);

    /* no tickets for root */
    if (pwd->pw_uid == 0)
	return (FALSE);

#ifdef SETPAG
    /* We only call setpag() for non-root users */
    setpag();
    afs_user = TRUE;
#endif

    /* if not root, get Kerberos tickets */
    if (krb_get_lrealm (realm, 1) != KSUCCESS)
	strcpy (realm, KRB_REALM);
    krbval = krb_get_pw_in_tkt (username, "", realm, "krbtgt", realm,
				KRBTKLIFETIME, password);
    switch (krbval) {
    case INTK_OK:
	/* Authentic, so don't time out. */
	alarm (0);
	if (verify_krb_tgt (realm) < 0) {
	    /* Oops.  He tried to fool us.  Tsk, tsk. */
	    dest_tkt ();
	    return (FALSE);
	}
	chown (getenv (KRB_ENVIRON), pwd->pw_uid, pwd->pw_gid);
	return (TRUE);

    case KDC_NULL_KEY:
	/* tell the luser to go register with kerberos */
	if (! hesiod_user)
	    return (FALSE);
	/* If we are changing password, he won't be logging in in this
	   process anyway, so we can reset */
	alarm (0);
	insert_pwent (pwd);
	forkval = fork();
	if (forkval < 0) {
	    perror("forking for registration program");
	    sleep(3);
	    exit(1);
	}
	if (forkval == 0)  { /* parent */
	    while (wait(NULL) != forkval);
	    remove_pwent(pwd); 
	    exit(0);
	}
	/* run the passwd program as the user */
	setuid(pwd->pw_uid);

	execl(_PATH_GOREGISTER, _PATH_GOREGISTER, username, 0);
	perror("executing registration program");
	sleep(2);
	exit(1);

    case KDC_PR_UNKNOWN:
    case KDC_PR_N_UNIQUE:
	break;
    case INTK_BADPW:
	/* just a bad password.  don't say anything extra. */
	/* fprintf(stderr, "%s\n", krb_err_txt[krbval]); */
	break;
    case INTK_W_NOTALL:
	/* xxx should this value be accepted? */
	fprintf(stderr, "Kerberos error: %s\n",	krb_err_txt[krbval]);
	break;
    default:
	fprintf(stderr, "Kerberos error: %s\n",	krb_err_txt[krbval]);
	break;
    }
    return (FALSE);
}

/*
 * Verify the Kerberos ticket-granting ticket just retrieved for the
 * user.  If the Kerberos server doesn't respond, assume the user is
 * trying to fake us out (since we DID just get a TGT from what is
 * supposedly our KDC).  If the rcmd.<host> service is unknown (i.e.,
 * the local srvtab doesn't have it), let her in.
 *
 * Returns 1 for confirmation, -1 for failure, 0 for uncertainty.
 */
int verify_krb_tgt (char *realm)
{
    char hostname[MAXHOSTNAMELEN], phost[BUFSIZ];
    struct hostent *hp;
    KTEXT_ST ticket;
    AUTH_DAT authdata;
    unsigned long addr;
    static /*const*/ char rcmd[] = "rcmd";
    char key[8];
    int krbval, retval, have_keys;

    if (gethostname(hostname, sizeof(hostname)) == -1) {
	perror ("cannot retrieve local hostname");
	return -1;
    }
    strncpy (phost, (char *) krb_get_phost (hostname), sizeof (phost));
    phost[sizeof(phost)-1] = 0;
    hp = gethostbyname (hostname);
    if (!hp) {
	perror ("cannot retrieve local host address");
	return -1;
    }
    bcopy ((char *)hp->h_addr, (char *) &addr, sizeof (addr));
    /* Do we have rcmd.<host> keys? */
    have_keys = read_service_key (rcmd, phost, realm, 0, KEYFILE, key)
	? 0 : 1;
    krbval = krb_mk_req (&ticket, rcmd, phost, realm, 0);
    if (krbval == KDC_PR_UNKNOWN) {
	/*
	 * Our rcmd.<host> principal isn't known -- just assume valid
	 * for now?  This is one case that the user _could_ fake out.
	 */
	if (have_keys)
	    return -1;
	else
	    return 0;
    }
    else if (krbval != KSUCCESS) {
	printf ("Unable to verify Kerberos TGT: %s\n", krb_err_txt[krbval]);
#ifndef SYSLOG42
	syslog (LOG_NOTICE|LOG_AUTH, "Kerberos TGT bad: %s",
		krb_err_txt[krbval]);
#endif
	return -1;
    }
    /* got ticket, try to use it */
    krbval = krb_rd_req (&ticket, rcmd, phost, addr, &authdata, "");
    if (krbval != KSUCCESS) {
	if (krbval == RD_AP_UNDEC && !have_keys)
	    retval = 0;
	else {
	    retval = -1;
	    printf ("Unable to verify `rcmd' ticket: %s\n",
		    krb_err_txt[krbval]);
	}
#ifndef SYSLOG42
	syslog (LOG_NOTICE|LOG_AUTH, "can't verify rcmd ticket: %s;%s\n",
		krb_err_txt[krbval],
		retval
		? "srvtab found, assuming failure"
		: "no srvtab found, assuming success");
#endif
	goto EGRESS;
    }
    /*
     * The rcmd.<host> ticket has been received _and_ verified.
     */
    retval = 1;
    /* do cleanup and return */
EGRESS:
    bzero (&ticket, sizeof (ticket));
    bzero (&authdata, sizeof (authdata));
    return retval;
}

/*
 *  encrypt_password () -- insert the password encrypted in the struct pwd.
 */
void encrypt_password (struct passwd *pwd, char *password)
{
    char saltc[2], c;
    long salt;
    short i;

    /* Modifications for Kerberos authentication -- asp */
    salt = 9 * getpid();
    saltc[0] = salt & 077;
    saltc[1] = (salt>>6) & 077;
    for (i = 0; i < 2; i++) {
	c = saltc[i] + '.';
	if (c > '9') c += 7;
	if (c > 'Z') c += 6;
	saltc[i] = c;
    }
    pwd->pw_passwd = crypt (password, saltc);
}

/*
 *  athena_get_homedir () -- if the user needs a networked or temporary
 *	homedir, attach or create it.
 */
void athena_get_homedir (struct passwd *pwd)
{
    /* If we already have a homedir, use it.
     * Otherwise, try to attach.  If that fails,
     * try to create.
     */
    attachedflag = tmpdirflag = FALSE;
    if (goodhomedir (pwd)) {
	if (hesiod_user)
	    puts("\nWarning: Using local home directory.");
	return;
    }
    if (attach_homedir (pwd) == 0) {
	attachedflag = TRUE;
	return;
    }
    puts ("\nWarning: Unable to attach home directory.");
    if (make_homedir (pwd) == 0) {
	puts ("\nNOTE -- Your home directory is temporary.");
	puts ("It will be deleted when this workstation deactivates.\n");
	tmpdirflag = TRUE;
	return;
    }
    chdir ("/");
    puts ("Can't find or build home directory! Logging in with home=/");
    pwd->pw_dir = "/";
    tmpdirflag = FALSE;
}

/* Attach the user's home directory if "attachable" is set.
 */
int attach_homedir (struct passwd *pwd)
{
    int status;
    int attachpid;

    if (! access (_PATH_NOATTACH, F_OK))
	return (-1);

    /*  XXX This is a temproary hack to fix the fact that home directories
     *  sometimes do not get attached if the user types his password wrong
     *  the first time. Some how working directory becomes the users home
     * directory BEFORE we try to attach. and it of course fails.
     */
    chdir ("/");

    if (!(attachpid = fork ())) {
	setuid (pwd->pw_uid);
	freopen ("/dev/null", "w", stdout);
	execl ("/bin/athena/attach", "attach", "-q", pwd->pw_name, NULL);
	exit (-1);
    } 
    while (wait (&status) != attachpid);
    if (WEXITSTATUS (status) == 0) {
	chown (pwd->pw_dir, pwd->pw_uid, pwd->pw_gid);
	return (0);
    }
    return (-1);
} 

/* Detach the user's home directory.
 */
void detach_homedir (struct passwd *pwd)
{
    int status;
    int pid;

    if (!(pid = fork ())) {
	setuid (pwd->pw_uid);
	freopen ("/dev/null", "w", stdout);
	freopen ("/dev/null", "w", stderr);
	execl("/bin/athena/fsid", "fsid", "-quiet", "-unmap", "-filsys",
	      pwd->pw_name, NULL);
	exit (-1);
    } 
    while (wait (&status) != pid);
}

int isremotedir (char *dir)
{
#ifdef linux
#define NFS_MAJOR 0
#define AFS_MAJOR 0x01ff
    struct stat stbuf;
  
    if (stat(dir, &stbuf))
	return(TRUE);
    if (major(stbuf.st_dev) == NFS_MAJOR)
	return(TRUE);
    if (stbuf.st_dev == AFS_MAJOR)
	return(TRUE);
    return(FALSE);
#endif
}

int goodhomedir (struct passwd *pwd)
{
    DIR *dp;
    int status;

    if (access (pwd->pw_dir, F_OK))
	return (0);
    if (isremotedir (pwd->pw_dir))
	return (0);

    /*  make sure the directory contains at least three entries.
     *  the first two are "." and "..".
     *  if the directory contains nothing else, it's considered a mountpoint
     *  and is therefore not a good homedir.
     */
    dp = opendir (pwd->pw_dir);
    if (!dp)
	return (0);
    readdir (dp);
    readdir (dp);
    status = (readdir (dp) != NULL);
    closedir (dp);
    return (status);
}
	
/*
 * Make a home directory, copying over files from PROTOTYPE_DIR.
 * Ownership and group will be set to the user's uid and gid.  Default
 * permission is TEMP_DIR_PERM.  Returns 0 on success, -1 on failure.
 */
int make_homedir (struct passwd *pwd)
{
    DIR *proto;
    struct dirent *dp;
    char tempname[MAXPATHLEN+1];
    char buf[1024];
    char killdir[40]; /* > sizeof(rmrf) + " /tmp/username" */
    struct stat statbuf;
    int fold, fnew;
    int n;

    if (access (_PATH_NOCREATE, F_OK) == 0)
	    return (-1);
    
    strcpy(pwd->pw_dir,"/tmp/");
    strcat(pwd->pw_dir,pwd->pw_name);
    setenv("TMPHOME", "", 1);

    /* Make sure there's not already a directory called pwd->pw_dir
       before we try to unlink it. This was OK under BSD, but it's
       a Bad Thing under Ultrix. */
    if (0 == lstat(pwd->pw_dir, &statbuf))
      {
	if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
	  {
	    if (statbuf.st_uid == pwd->pw_uid)
	      return (0); /* user's old temp homedir, presumably */

	    /* Hm. This looks suspicious... Kill it. */
	    sprintf(killdir, "/bin/rm -rf %s", pwd->pw_dir);
	    system(killdir); /* Check for success at mkdir below. */
	  }
	else
	  unlink(pwd->pw_dir); /* not a dir - unlink is safe */
      }
    else
      if (errno != ENOENT) /* == ENOENT --> there was nothing there */
	{
	  puts("Error while retrieving status of temporary homedir.");
	  return(-1);
	}
	
    /* Make the home dir and chdir to it */
    if(mkdir(pwd->pw_dir, TEMP_DIR_PERM) < 0) {
      puts("Error while creating temporary directory.");
      /* We want to die even if the error is that the directory
	 already exists - because it shouldn't. */
      return(-1);
    } 
    chown(pwd->pw_dir, pwd->pw_uid, pwd->pw_gid);
    chdir(pwd->pw_dir);
    
    /* Copy over the proto files */
    if((proto = opendir(PROTOTYPE_DIR)) == NULL) {
	puts("Can't open prototype directory!");
	rmdir(pwd->pw_dir); /* This was unlink() before - WTF? */
	return(-1);
    }

    for(dp = readdir(proto); dp != NULL; dp = readdir(proto)) {
	/* Don't try to copy . or .. */
	if(!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue;

	/* Copy the file */
	strcpy(tempname, PROTOTYPE_DIR);
	strcat(tempname, "/");
	strncat(tempname, dp->d_name, sizeof(tempname) - strlen(tempname) - 1);
	if(stat(tempname, &statbuf) < 0) {
	    perror(tempname);
	    continue;
	}
	/* Only copy plain files */
	if(!(statbuf.st_mode & S_IFREG)) continue;

	/* Try to open the source file */
	if((fold = open(tempname, O_RDONLY, 0)) < 0) {
	    perror(tempname);
	    continue;
	}

	/* Open the destination file */
	if((fnew = open(dp->d_name, O_WRONLY|O_CREAT|O_EXCL,
			statbuf.st_mode)) < 0) {
			    perror(dp->d_name);
			    continue;
			}

	/* Change the ownership */
	fchown(fnew, pwd->pw_uid, pwd->pw_gid);

	/* Do the copy */
	for (;;) {
	    n = read(fold, buf, sizeof buf);
	    if(n==0) break;
	    if(n<0) {
		perror(tempname);
		break;
	    }
	    if (write(fnew, buf, n) != n) {
		perror(dp->d_name);
		break;
	    }
	}
	close(fnew);
	close(fold);
    }
    return(0);
}

void insert_pwent (struct passwd *pwd)
{
    FILE *pfile;
    int cnt, fd;

    while (getpwuid(pwd->pw_uid))
      (pwd->pw_uid)++;

    cnt = 10;
    while (cnt-- > 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");
	printf("Failed to add you to /etc/passwd\n");
    }

    if((pfile=fopen("/etc/passwd", "a")) != NULL) {
	fprintf(pfile, "%s:%s:%d:%d:%s:%s:%s\n",
		pwd->pw_name,
		pwd->pw_passwd,
		pwd->pw_uid,
		pwd->pw_gid,
		pwd->pw_gecos,
		pwd->pw_dir,
		pwd->pw_shell);
	fclose(pfile);
    }
    close(fd);
    unlink("/etc/ptmp");
}

int remove_pwent (struct passwd *pwd)
{
    FILE *newfile;
    struct passwd *copypw;
    struct stat statb;
    int cnt, fd;

    cnt = 10;
    while (cnt-- > 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 remove");
	printf("Failed to remove you from /etc/passwd\n");
    }
    if ((newfile = fdopen(fd, "w")) != NULL) {
	setpwent();
	while ((copypw = getpwent()) != 0)
	    if (copypw->pw_uid != pwd->pw_uid)
		    fprintf(newfile, "%s:%s:%d:%d:%s:%s:%s\n",
			    copypw->pw_name,
			    copypw->pw_passwd,
			    copypw->pw_uid,
			    copypw->pw_gid,
			    copypw->pw_gecos,
			    copypw->pw_dir,
			    copypw->pw_shell);
	endpwent();
	fclose(newfile);
	if (stat("/etc/ptmp", &statb) != 0 || statb.st_size < 80) {
	    syslog(LOG_CRIT, "something stepped on /etc/ptmp");
	    printf("Failed to cleanup login\n");
	} else
	  rename("/etc/ptmp", "/etc/passwd");
	if (stat("/etc/passwd", &statb) != 0 || statb.st_size < 80) {
	    syslog(LOG_CRIT, "something stepped on /etc/passwd");
	    printf("Failed to cleanup login\n");
	    sleep(12);
	    if (stat("/etc/passwd", &statb) != 0 || statb.st_size < 80) {
		syslog(LOG_CRIT, "/etc/passwd still empty, adding root");
		newfile = fopen("/etc/passwd", "w");
#ifdef SOLARIS
		fprintf(newfile, "root:x:0:1:System PRIVILEGED Account:/:/bin/csh\n");
#else
		fprintf(newfile, "root:*:0:1:System PRIVILEGED Account:/:/bin/csh\n");
#endif
		fclose(newfile);
	    }
	}
      return(0);
    }else return(1);
}

#define MAXGNAMELENGTH	32

void get_groups (struct passwd *pwd)
{
    char **cp, *msg;

    if (access(_PATH_NOCREATE, F_OK) == 0)
	return;
	
    cp = (char **)hes_resolve(pwd->pw_name,"grplist");
    if (!cp || !*cp)
	return;

    msg = add_to_group(pwd->pw_name, pwd->pw_gid, *cp);
    if (msg != NULL)
	fprintf(stderr, "Warning: %s\n", msg);
}

char *add_to_group (char *name, int primary, char *glist)
{
    char *cp;			/* temporary */
    char *gnames[MAX_GROUPS], *gname, **hes_primary;
    char *gpwd, *gid, *guserlist = NULL;
    gid_t gids[MAX_GROUPS], id;
    int i, fd = -1, ngroups = 0;
    int namelen = strlen(name);
    FILE *etc_group, *etc_gtmp;
    static 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.");
    }

    /* Resolve primary group. */
    sprintf(data, "%d", primary);
    hes_primary = hes_resolve(data, "gid");
    if (hes_primary && *hes_primary) {
	cp = strchr(hes_primary[0], ':');
	if (cp) {
	    *cp = 0;
	    gnames[ngroups] = hes_primary[0];
	    gids[ngroups] = primary;
	    ngroups++;
	}
    }

    /* Read in local groups. */
    while (ngroups < MAX_GROUPS && fgets(data, sizeof(data), etc_group)) {
	if ((gpwd = strchr(data, ':')) == NULL ||
	    (gid = strchr(++gpwd, ':')) == NULL ||
	    (guserlist = strchr(++gid, ':')) == NULL)
	    continue;
	for (cp = guserlist; cp; cp = strchr(cp, ',')) {
	    cp++;
	    if (!strncmp(name, cp, namelen) && strchr("\n ,", cp[namelen]) &&
		(!hes_primary || !hes_primary[0] || atoi(gid) != primary)) {
		gnames[ngroups] = NULL;
		gids[ngroups++] = atoi(gid);
	    }
	}
    }

    /* Read in Hesiod groups. */
    cp = glist - 1;
    while (ngroups < MAX_GROUPS && cp) {
	gname = cp + 1;
	cp = strchr(gname, ':');
	if (cp) {
	    *cp = 0;
	    id = atoi(cp + 1);
	    cp = strchr(cp + 1, ':');
	    for (i = 0; i < ngroups; i++) {
		if (gids[i] == id)
		    break;
	    }
	    if (i == ngroups) {
		gnames[ngroups] = gname;
		gids[ngroups++] = id;
	    }
	}
    }

    /* If we terminated with cp non-NULL, we left out some groups. */
    if (cp) {
	fprintf(stderr, "Warning - you are in too many groups.  Some of them will be ignored.\n");
	ngroups = MAX_GROUPS;
    }

    rewind(etc_group);
    while (fgets(data, sizeof(data) - MAXGNAMELENGTH, etc_group)) {
	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 = strchr(data, ':')) &&
	    (gid = strchr(++gpwd, ':')) &&
	    (guserlist = strchr(++gid, ':'))) {
	    *guserlist = '\0';
	    id = atoi(gid);
	    /* step through our groups */
	    for (i = 0; i < ngroups; i++)
	      if (id == gids[i]) {
		  /* found it, now check users */
		  for (cp = guserlist; cp; cp = strchr(cp, ',')) {
		      cp++;
		      if (!strncmp(name, cp, namelen) &&
			  strchr(", \n", cp[namelen])) {
			  gnames[i] = NULL;
			  break;
		      }
		  }
		  if (gnames[i])
		    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;
	}
	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 (gnames[i])
	    fprintf(etc_gtmp, "%s:*:%d:%s\n", gnames[i], (int) gids[i], name);
    }

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

    (void) fclose(etc_group);
    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);
    return("Failed to update your access control groups");
}

void athena_init_wgfile (void)
{
        char wgfile[16];
        char *wgfile1;

	strcpy(wgfile, "/tmp/wg.XXXXXX");
	wgfile1 = mktemp(wgfile);
	setenv ("WGFILE", wgfile1, 1);
}

/*
 * This routine handles cleanup stuff, notification service, and the like.
 * It exits only in the child process.
 */
void athena_do_fork (struct passwd *pwd)
{
    int child;
    struct sigaction sa;

    if(!(child=fork()))
	    return; /* Child process */

    /* Setup stuff?  This would be things we could do in parallel with login */
    chdir("/");	/* Let's not keep the fs busy... */
    
    /* If we're the parent, watch the child until it dies */
    while(wait(NULL) != child)
	    ;

    /* Cleanup stuff */

    /* Send a SIGHUP to everything in the process group, but not us.
     * Originally included to support Zephyr over rlogin/telnet
     * connections, but it has some general use, since any personal
     * daemon can setpgrp(0, getpgrp(getppid())) before forking to be
     * sure of receiving a HUP when the user logs out.
     *
     * Note that we are assuming that the shell will set its process
     * group to its process id. Our csh does, anyway, and there is no
     * other way to reliably find out what that shell's pgrp is.
     */

    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sa.sa_handler = SIG_IGN;
    sigaction(SIGHUP, &sa, (struct sigaction *)0);

    if(-1 == killpg(child, SIGHUP))
      {
	/* EINVAL shouldn't happen (SIGHUP is a constant),
	 * ESRCH could, but we ignore it
	 * EPERM means something actually is wrong, so log it
	 * (in this case, the signal didn't get delivered but
	 * something might have wanted it...)
	 */
	if(errno == EPERM)
	  syslog(LOG_DEBUG,
		 "EPERM trying to kill login process group: child_pgrp %d",
		 child);
      }

    /* Run dest_tkt to destroy tickets */
    (void) dest_tkt();		/* If this fails, we lose quietly */

#ifdef SETPAG
    /* Destroy any AFS tokens */
    if (afs_user)
	ktc_ForgetAllTokens();
#endif

    /* Detach home directory if previously attached */
    if (attachedflag)
	    detach_homedir(pwd);

    if (hesiod_user) {
	    if (remove_pwent(pwd)) 
		    puts("Couldn't remove password entry");
	    /* if (remove_groups(pwd))
		    puts("Couldn't remove group entries"); */
    }

    /* Leave */
    exit(0);
}
