
/*
 *  passwd.c -- routines to read, clean, and write the /etc/passwd file.
 *
 *  options:
 *  -DBSD43		use old bsd header files
 *  -DDEBUG		make program verbose
 *  -D_AIX		use ibm aix style passwd routines.
 *  -DSHADOW_PWD	clean the /etc/shadow file too.
 *
 */

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <pwd.h>
#include <grp.h>
#include <AL/AL.h>
#include "cleanup.h"

#define MAXUSERS	1024
#define MAXLOCALGROUPS	1024
/* Get list of uids in /etc/passwd */

static int make_group P((struct cl_user *users));
#ifdef SHADOW_PWD
static int rewrite_shadow P((void));
#endif

struct cl_user *get_password_entries()
{
    int i = 0;
    static struct cl_user users[MAXUSERS];
    struct passwd *p;

    setpwent();
#ifdef DEBUG
    printf("In passwd:");
#endif

    while ((p = getpwent()) != NULL && i < MAXUSERS) {
	strncpy(users[i].name, p->pw_name, 8);
	users[i].name[8] = 0;
	users[i++].uid = p->pw_uid;
#ifdef DEBUG
	printf(", %d", p->pw_uid);
#endif
    }
    if (i >= MAXUSERS) {
	fprintf(stderr, "cleanup: Too many users in /etc/passwd\n");
	return(NULL);
    }

    endpwent();
    users[i].uid = -1;
#ifdef DEBUG
    printf("\n");
#endif

    return(users);
}


/* Update the passwd and group files.  The passed set of uids specifies
 * who should be in the passwd file.
 */

#ifndef _AIX

int rewrite_passwd(users, passwdfile, tempfile)
struct cl_user *users;
FILE *passwdfile, *tempfile;
{
    FILE *localfile;
    char tmpbuf[128], *buffer = NULL, *p;
    struct cl_user in_passwd[MAXUSERS];
    int user = 0, i, uid, lfd, bufsize = 0;
    sigset_t mask;

    sprintf(tmpbuf, "%s.local", PATH_PASSWD);
    localfile = fopen(tmpbuf, "r");
    if (localfile == NULL) {
	sprintf(tmpbuf, "cleanup: unable to open %s.local", PATH_PASSWD);
        perror(tmpbuf);
        return(0);				/* non fatal error */
    }

    /* copy local passwd file, keeping track of who is in it */
    while (ALgetLine(localfile, &buffer, &bufsize)) {
	fputs(buffer, tempfile);
	putc('\n', tempfile);
	p = strchr(buffer, ':');
	if (p) {
	    *p = 0;
	    p = strchr(p + 1, ':');
	    if (p) {
		p++;
		strncpy(in_passwd[user].name, buffer, 8);
		in_passwd[user].name[8] = 0;
		in_passwd[user++].uid = atoi(p);
	    }
	}
    }
    fclose(localfile);

    /* now process existing master passwd file, avoiding duplicates */
    while (ALgetLine(passwdfile, &buffer, &bufsize)) {
	uid = -1;
	p = strchr(buffer, ':');
	if (p) {
	    p = strchr(p + 1, ':');
	    if (p) {
		p++;
		uid = atoi(p);
	    }
	}
	/* if we can't find the uid in the entry, give up */
	if (uid == -1) {
#ifdef DEBUG
	    printf("Skipping malformed passwd entry: %s\n", buffer);
#endif
	    continue;
	}

	for (i = 0; i < user; i++) {
	    if (in_passwd[i].uid == uid)
		break;
	}
	/* if already in passwd file, skip */
	if (i < user) {
#ifdef DEBUG
	    printf("Skipping entry %d already in passwd file: %s",uid,buffer);
#endif
	    continue;
	}

        for (i = 0; users[i].uid >= 0; i++) {
	    if (users[i].uid == uid)
		break;
	}
	/* if not supposed to be in passwd file, skip */
	if (users[i].uid < 0) {
#ifdef DEBUG
	    printf("Skipping entry shouldn't be in passwd file: %s", buffer);
#endif
	    continue;
	}

	fputs(buffer, tempfile);
	putc('\n', tempfile);
	strcpy(in_passwd[user].name, users[i].name);
	in_passwd[user++].uid = uid;
    }

    if (buffer)
	free(buffer);

#ifdef SHADOW_PWD
    rewrite_shadow();
#endif
    in_passwd[user].uid = -1;
    make_group(in_passwd);

    return(0);
}

#ifdef SHADOW_PWD

#define PATH_SHADOW	"/etc/shadow"

static int rewrite_shadow()
{
    FILE *shadowfile, *tempfile;
    char username[64], *buf = NULL, *p;
    struct passwd *pw;
    int uid, bufsize = 0;
    sigset_t mask;

    if (force_begin_update(PATH_SHADOW, PATH_PASSWD_LOCK, &shadowfile,
			   &tempfile, &mask) < 0) {
	fprintf(stderr, "cleanup: unable to begin shadow file update.\n");
	return(-1);
    }

    /* now process /etc/shadow, avoiding duplicates */
    while (ALgetLine(shadowfile, &buf, &bufsize)) {
	uid = -1;
	p = strchr(buf, ':');
	if (p) {
            strncpy(username, p - buf);
	    username[p - buf] = 0;
            pw = getpwnam(username);
            if (pw)
		uid = pw->pw_uid;
	}
	if (uid !=-1) {
	    fputs(buffer, tempfile);
	    putc('\n', tempfile);
	}
    }

    if (ALfinishFileUpdate(PATH_SHADOW, PATH_PASSWD_LOCK, shadowfile,
			   tempfile, &mask, 1) < 0) {
	fprintf(stderr, "cleanup: unable to finish shadow file update.\n");
	return(-1);
    }
    return(0);
}

#endif /* SHADOW_PWD */

/* Rebuild the group file, keeping any group which has a member who is 
 * in the passwd file.
 */

static int make_group(users)
struct cl_user *users;
{
    int i, n, emptied, bufsize = 0, numlocal = 0;
    char *buf = NULL, *p, *p1;
    struct passwd *pw;
    FILE *groupfile, *tempfile, *localfile;
    sigset_t mask;
    gid_t localgroups[MAXLOCALGROUPS + 1], gid;

    localfile = fopen("/etc/group.local", "r");
    if (localfile) {
	while (ALgetLine(localfile, &buf, &bufsize)) {
	    if ((p = strchr(buf, ':')) == 0 || (p = strchr(p + 1, ':')) == 0) {
		fprintf(stderr, "Cleanup: Corrupt local group entry \"%s\".\n",
			buf);
		continue;
	    }
	    if (numlocal < MAXLOCALGROUPS + 1)
		localgroups[numlocal++] = atoi(p + 1);
	}
	fclose(localfile);
    }

    if (force_begin_update(PATH_GROUP, PATH_GROUP_LOCK, &groupfile,
			   &tempfile, &mask) < 0) {
	fprintf(stderr, "cleanup: unable to begin group file update.\n");
	return(-1);
    }

    /* loop over each line in the group file */
    while (ALgetLine(groupfile, &buf, &bufsize)) {
	/* take out tailing \n */
	if ((p = strchr(buf, ':')) == 0 || (p = strchr(p + 1, ':')) == 0) {
	    fprintf(stderr, "cleanup: Corrupt group entry \"%s\".\n", buf);
	    continue;
        }
	gid = atoi(p + 1);
	if ((p = strchr(p + 1, ':')) == 0) {
	    fprintf(stderr, "cleanup: Corrupt group entry \"%s\".\n", buf);
	    continue;
	}

	/* loop over each member of the group */
	emptied = 0;
	while (p != NULL) {
	    p1 = strchr(p + 1, ',');
	    n = (p1) ? p1 - (p + 1) : strlen(p + 1);
	    /* compare against each user in the passwd file */
	    for (i = 0; users[i].uid >= 0; i++) {
		if (!strncmp(p + 1, users[i].name, n) && users[i].name[n] == 0)
		    break;
	    }
	    if (users[i].uid < 0) {
		if (p1) {
		    memmove(p + 1, p1 + 1, strlen(p1 + 1) + 1);
		} else {
		    if (*p == ',') {
			*p = 0;
		    } else {
			p[1] = 0;
			emptied = 1;
		    }
		    break;
		}
	    } else {
		p = p1;
	    }
	}
	if (!emptied || numlocal == 0 || numlocal > MAXLOCALGROUPS) {
	    fprintf(tempfile, "%s\n", buf);
	} else {
	    /* We were able to read in the local groups, so we can delete
	     * lines of the group file if they don't correspond to local
	     * groups. */
	    for (i = 0; i < numlocal; i++) {
		if (gid == localgroups[i])
		    break;
	    }
	    if (i < numlocal)
		fprintf(tempfile, "%s\n", buf);
#ifdef DEBUG
	    else
		printf("<<<%s\n", buf);
#endif
	}
    }

    if (buf)
	free(buf);

    if (ALfinishFileUpdate(PATH_GROUP, PATH_GROUP_LOCK, groupfile,
			   tempfile, &mask, 0) < 0) {
	fprintf(stderr, "cleanup: unable to finish group file update.\n");
	return(-1);
    }
    return(0);
}

#else /* _AIX */

int rewrite_passwd(users)
int *users;
{
    int i, uid, count;
    char *usr, *grp, *ulist;
    static char *empty = "\0";
    
    setuserdb(S_READ|S_WRITE);

    /* Note: This routine cannot properly track reference counts for
     * groups because there is no reference count on permanent users.
     */

    /* Remove any inactive temporary users */
    usr = nextuser(S_LOCAL, NULL);
    while (usr) {
	if (getuserattr(usr, S_ID, &uid, SEC_INT) == -1)
	    uid = -1;
#ifdef DEBUG
	printf("Checking user %s (uid %d)\n", usr, uid);
#endif
	if (getuserattr(usr, "athena_temp", &count, SEC_INT) == 0) {
	    for (i = 0; users[i] >= 0; i++)
		if (users[i] == uid)
		    break;
	    if (users[i] < 0 || users[i] != uid) {
#ifdef DEBUG
		printf("Deleting user %s from passwd file\n", usr);
#endif
		putuserattr(usr, S_GROUPS, (void *)empty, SEC_LIST);
		putuserattr(usr, (char *)0, (void *)0, SEC_COMMIT);
		rmufile(usr, 1, USER_TABLE);
	    }
	}
	usr=nextuser(NULL, NULL);
    }

    /* Check all temporary groups for inactive users. */
    /* Remove any empty temporary groups. */
    grp = nextgroup(S_LOCAL, NULL);
    while (grp) {
	if (getgroupattr(grp, "athena_temp", (void *)&count, SEC_INT) == 0 ||
	    getgroupattr(grp, "athena_temp", (void *)&count, SEC_BOOL) == 0) {
	    if (getgroupattr(grp, S_USERS, (void *)&usr, SEC_LIST)) {
		rmufile(grp, 0, GROUP_TABLE);
	    } else {
		count = 0;
		ulist = 0;
		while (*usr) {
		    if (getuserattr(usr, S_ID, (void *)&uid, SEC_INT) == 0) {
			for (i=0; users[i]>=0; i++)
			    if (uid==users[i]) {
				if (ulist)
				    ulist = (char *)realloc(ulist,strlen(ulist)+strlen(usr)+1);
				else {
				    ulist = (char *)malloc(strlen(usr)+2);
				    *ulist = 0;
				}
				strcat(ulist,usr);
				strcat(ulist,",");
				count++;
				break;
			    }
		    }
		    while (*usr) usr++;
		    usr++;
		}
		if (count) {
#ifdef DEBUG
		    printf("Group %s reduced to %s\n", grp, ulist);
#endif
		    for (usr=ulist; *usr; usr++)
			if (*usr==',') *usr=0;
		    putgroupattr(grp, S_USERS, (void *)ulist, SEC_LIST);
		    putgroupattr(grp, (char *)0, (void *)0, SEC_COMMIT);
		    free(ulist);
		} else {
#ifdef DEBUG
		    printf("Deleting group %s\n", grp);
#endif
		    putgroupattr(grp, S_USERS, (void *)empty, SEC_LIST);
		    putgroupattr(grp, (char *)0, (void *)0, SEC_COMMIT);
		    rmufile(grp, 0, GROUP_TABLE);
		}
	    }
	}
	grp = nextgroup(NULL, NULL);
    }
    enduserdb();
}

#endif /* _AIX */

