#include "config.h"
/*
 * Copyright (c) 1986, 2014 by The Trustees of Columbia University in
 * the City of New York.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  + Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  + Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 *  + Neither the name of Columbia University nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.

*/
/*
 * program to create a passwd.crunch file.  This is a passwd table which can
 * be read into memory very quickly.  Note this file is presorted by username.
 * it format is:
 * bytes 1-4: number passwd entries in the passwd file -- written as
 *		write(fd, &int, sizeof(int) -- that is, byteswapped on a vax.
 * n bytes:  passwd entries, with all string pointers as offsets into a single
 *		buffer.  This section will be of size:
 *			(n * sizeof struct passwd)
 * 		where n is the number of passwd entries read in above.
 * the rest of the file contains all of the strings from these passwd entries.
 * the address of the buffer this is read into should be added to all of
 * the pointer fields in each passwd entry.
 */

#include "args.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>

#include <stdio.h>
#if !HAVE_STDC
extern int close ARGS((int));
extern int open ARGS((const char *, int, ...));
extern int read ARGS((int , void *, unsigned));
extern int write ARGS((int , void *, unsigned));
#endif

#include <pwd.h>

#if HAVE_STDC
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/* Using memmove here because memcpy doesn't usually handle overlapping
   memory copies */
/* #define bcopy(a,b,c)	memcpy((b),(a),c) */
#define bcopy(a,b,c)	memmove((b),(a),c)
#endif

#define QSORT_FUN_TYPE int (*) ARGS((const void *, const void *))

#define GROW 100
#define BUFGROW 1000

static struct passwd *ents;
static char *buf;
static int pw_howmany = 0;
static int buflen = 0;
static int used = 0;
static max = 0;

extern int main ARGS((int argc, char **argv));
static int pwcmp ARGS((struct passwd *p1, struct passwd *p2));
static int xwrite ARGS((int fd, char *buf_, int len));
static void copystr ARGS((char **s));
static void crunchpw ARGS((const char *tofile));
static void crunchpwent ARGS((struct passwd *p));
static void grow_entries ARGS((void));
static void write_crunchfile ARGS((const char *tofile));

int
#if HAVE_STDC
main(int argc, char **argv)
#else /* K&R style */
main(argc,argv)
int argc;
char **argv;
#endif /* HAVE_STDC */
{
    switch(argc) {
    case 1:
	crunchpw("/etc/passwd.crunch");
	return (0);
    case 2:
	crunchpw(argv[1]);
	return (0);
    default:
	fprintf(stderr,"usage: crunchpw [crunchfile]");
	return (1);
    }
}

static void
#if HAVE_STDC
crunchpw(const char *tofile)
#else /* K&R style */
crunchpw(tofile)
const char *tofile;
#endif /* HAVE_STDC */
{
    struct passwd *p;
    struct stat b1, b2;

    if (stat("/etc/passwd", &b1) != 0) {
	perror("/etc/passwd");
	exit(1);
    }
    if (stat(tofile, &b2) == 0 && b2.st_mtime > b1.st_mtime)
	exit(0);
    while((p = getpwent(),p))		/* for all entries */
	crunchpwent(p);			/* compact it */
    write_crunchfile(tofile);		/* write out compacted version */
}

/*
 * compact a single passwd entry
 */
static void
#if HAVE_STDC
crunchpwent(struct passwd *p)
#else /* K&R style */
crunchpwent(p)
struct passwd *p;
#endif /* HAVE_STDC */
{
    struct passwd *p1;

    if (pw_howmany == max)		/* make space if we need it */
	grow_entries();
    p1 = &ents[pw_howmany];
    bcopy(p,p1,sizeof(struct passwd));	/* copy all the data */
    copystr(&p1->pw_name);		/* and then all of the strings */
    copystr(&p1->pw_passwd);		/* but fix the string ptrs as we go */
#ifdef SYSV
    copystr(&p1->pw_age);
#endif
    copystr(&p1->pw_comment);
    copystr(&p1->pw_gecos);
    copystr(&p1->pw_dir);
    copystr(&p1->pw_shell);
    pw_howmany++;
}

/*
 * make more space for entries.
 * we want a contiguous table.
 */
static void
grow_entries(VOID) {
    max += GROW;
    if (ents == NULL) {
	ents = (struct passwd *) malloc((max)*sizeof(struct passwd));
    }
    else {
	ents = (struct passwd *) realloc(ents,(max)*sizeof(struct passwd));
    }
}

/*
 * copy the string into the buffer we are making, and set the string pointer
 * to be the index into that buffer.
 */
static void
#if HAVE_STDC
copystr(char **s)
#else /* K&R style */
copystr(s)
char **s;
#endif /* HAVE_STDC */
{
    if (used + (int)strlen(*s) > buflen) { /* expand buffer if necessary */
	buflen += BUFGROW;
	if (buf == NULL) buf = (char*)malloc(buflen);
	else buf = (char*)realloc(buf,buflen);
    }
    strcpy(&buf[used], *s);		/* copy the string */
    *s = (char *) used;			/* set ptr to be the index */
    used += strlen(&buf[used])+1;	/* count the space */
}

static int
#if HAVE_STDC
pwcmp(register struct passwd *p1, register struct passwd *p2)
#else /* K&R style */
pwcmp(p1,p2)
register struct passwd *p1,*p2;
#endif /* HAVE_STDC */
{
    register char *b1 = &buf[(int)p1->pw_name], *b2 = &buf[(int)p2->pw_name];
    return(strcmp(b1,b2));
}

/*
 * write out the compacted file
 */
static void
#if HAVE_STDC
write_crunchfile(const char *tofile)
#else /* K&R style */
write_crunchfile(tofile)
const char *tofile;
#endif /* HAVE_STDC */
{
    int fd;
    fd = open(tofile,O_WRONLY|O_CREAT,0444);
    if (fd == -1) {
	perror(tofile);
	exit(1);
    }
    xwrite(fd,(char*)&pw_howmany,
	   (int)sizeof(pw_howmany)); /* write how many entries */
    qsort(ents, pw_howmany, sizeof(struct passwd), (QSORT_FUN_TYPE)pwcmp);
    xwrite(fd,(char*)ents,
	   (int)sizeof(struct passwd)*pw_howmany); /* write the entries */
    xwrite(fd,buf,used);		/* write the strings */
    close(fd);
}

static int
#if HAVE_STDC
xwrite(int fd, char *buf, int len)
#else /* K&R style */
xwrite(fd,buf,len)
int fd,len;
char *buf;
#endif /* HAVE_STDC */
{
    int sofar = 0,x;

    while(sofar < len) {
	x = write(fd, buf+sofar, len - sofar);
	if (x == -1) {
	    perror("write");
	    return(-1);
	}
	sofar += x;
    }
    return(len);
}
