/*
 Author: Howie Kaye

 Columbia University Center for Computing Activities, July 1986.
 Copyright (C) 1986, 1987, Trustees of Columbia University in the City
 of New York.  Permission is granted to any individual or institution
 to use, copy, or redistribute this software so long as it is not sold
 for profit, provided this copyright notice is retained.
*/

/*
 * #define NO_USERNAME_COMPLETION in machdep.h if you don't want to CCMD to
 * preload the passwd file before parsing user names.  This is generally a
 * lose for systems with large distributed password files where the time to
 * load the usernames (via YP, for instance) can be considerable and the
 * completion doesn't buy much.
 */

#define USRERR

#if unix

/*
 * ccmd user name parser.
 */

#define INCUSRS 100

#include "ccmdlib.h"			/* ccmd symbols */
#include "cmfncs.h"			/* ccmd internal symbols */
#include "cmusr.h"

static brktab usrbrk = {		/* all valid chars for users */
  {					/* alphanums, "~#/_-\[]" */
    0xff, 0xff, 0xff, 0xff, 0xef, 0xd2, 0x00, 0x3f,
    0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x09,
  },
  {
    0xff, 0xff, 0xff, 0xff, 0xef, 0xd2, 0x00, 0x3f,
    0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x09,
  }
};

int usrparse(), usrhelp(), usrcomplete();
ftspec ft_usr =  { usrparse, usrhelp, usrcomplete, 0, &usrbrk };
char *malloc(), *realloc();

#ifdef NO_USERNAME_COMPLETION
#define MAXUNAMELEN (sizeof ((struct utmp *)0)->ut_name)

/*
 * common code for usrparse and usrcomplete
 */

static int
username_test (text, textlen, fdbp, parselen, pwp, completion)
char *text;
int textlen, *parselen, completion;
fdb *fdbp;
struct passwd **pwp;			/* optional return value */
{
    int usrlen, i;
    brktab *btab = fdbp->_cmbrk ? fdbp->_cmbrk : &usrbrk;
    char name[MAXUNAMELEN+1];
    struct passwd *pw;

    for (usrlen = 0; usrlen < textlen; usrlen++)
	if ((text[usrlen] & CC_QUO) == 0) {
	    int c = text[usrlen] & CC_CHR;
	    if (usrlen == 0 ? BREAK1 (btab, c) : BREAKR (btab, c))
		break;
	}

    if (usrlen > MAXUNAMELEN)		/* check length */
	return USRxNM;

    if (!completion)
	if (usrlen == textlen)		/* terminator found? */
	    return CMxINC;

    if (usrlen == 0)			/* check for null username */
	return USRxNM;

#ifdef USR_PO
    /* if parse-only parse, just return what we have */
    if (fdbp->_cmffl & USR_PO) {
	if (pwp)
	    *pwp = NULL;
	*parselen = usrlen;
	return CMxOK;
    }
#endif

    /* extract username */
    for (i = 0; i < usrlen; i++)
	name[i] = text[i] & CC_CHR;
    name[usrlen] = 0;

    pw = getpwnam (name);
    if (pw) {
	*parselen = usrlen;
	if (pwp)
	    *pwp = pw;
	return CMxOK;
    }
    return completion ? CMxINC : USRxNM;
}

static struct passwd *kludge[2] = { 0, 0 };

usrparse (text, textlen, fdbp, parselen, value)
char *text;
int textlen, *parselen;
fdb *fdbp;
pval *value;
{
    int plen, result;

    result = username_test (text, textlen, fdbp, &plen, &kludge[0], 0);
    if (result != CMxOK)
	return result;

#ifdef USR_PO
    if (fdbp->_cmffl & USR_PO)
	value->_pvstr = cmcsb._cmabp;
    else
#endif
	value->_pvusr = &kludge[0];
    *parselen = plen;
    return CMxOK;
}

usrhelp (text, textlen, fdbp, cust)
char *text;
int textlen, cust;
fdb *fdbp;
{
    if (!cust)
	cmxputs ("user name");

    return CMxOK;
}

usrcomplete (text, textlen, fdbp, full, cplt, cpltlen)
char *text, **cplt;
int textlen, full, *cpltlen;
fdb *fdbp;
{
    int test, len;
    pval pvalue;
    struct passwd *pw;

    *cplt = NULL;
    *cpltlen = 0;
    
    test = username_test (text, textlen, fdbp, &len, &pw, 1);
    switch (test) {
      case CMxOK:			/* input is valid username */
	return full ? CMP_GO|CMP_SPC : CMP_GO;
      case CMxINC:			/* input not valid (yet) */
	return CMP_BEL;
    }
    return CMP_GO;			/* some error; usrparse will handle */
}

#else

static int usedcrunch=FALSE;
/*
 * parse routine for user names.
 */
PASSEDSTATIC int
usrparse(text, textlen, fdbp, parselen, value)
char *text;
int textlen, *parselen;
fdb *fdbp;
pval *value;
{
  static struct usr **u;
  char *term;
  int pmatch, ematch,i;
  struct passwd **builduserlist();
  match(text, textlen, fdbp, &u, &term, &pmatch, &ematch); /* find matches */

  if (pmatch == 0) 
      return(USRxNM);

  if (term == NULL)			/* unterminated....not done */
    return(CMxINC);			/* return incompleteness */

  if (ematch == 0)			/* no matches */
    return(USRxNM);			/* return as such */



  if (ematch > 1)			/* more than one match...ambiguous */
    if (!(fdbp->_cmffl & USR_WILD))	/* this means 2 identical user names */
      return(USRxAMB);			/* unless a wild parse */

  value->_pvusr = builduserlist(u,ematch); /* return list of matches */
  *parselen = term-text;		/* and parsed length */
  return(CMxOK);			/* and return successfully */
}

/*
 * username help routine.
 */
PASSEDSTATIC int
usrhelp(text, textlen, fdbp, cust) 
char *text;
int textlen, cust;
fdb *fdbp;
{
  int ematch, pmatch,i,len,maxlen,cols,curcol;
  struct usr **u;
  char *term;

  if (!cust) {				/* standard help msg */
    cmxputs("user name, one of the following:");
  }

					/* find matches */
  match(text, textlen, fdbp, &u, &term, &pmatch, &ematch);
  if (pmatch == 0) {			/* no matches. */
    cmxnl();				/* just say so */
    cmxputs(" (No usernames match current input)"); /* none here */
    return(CMxOK);			/* all done */
  }

  if (pmatch > cmcsb._cmmax) {		/* too many to display */
    cmxnl();
    cmxprintf(" %d matching users.\n",pmatch); /* just say how many. */
    return(CMxOK);			/* all done */
  }
    
  maxlen = 0;				/* calculate number of columns */
  len = 0;				/* based on maximum name length */

  for (i = 0 ; u[i] != NULL; i++) {	/* scan through matches */
    if (u[i]->flags & USR_PARTIAL) {
      len = strlen(u[i]->pwd->pw_name);	/* find longest name */
      if (maxlen < len) maxlen = len;
    }
  }
  maxlen += 3;				/* put some space after the name */
  cols = (cmcsb._cmcmx+2) / maxlen;	/* number of columns per line */
  if (cols <= 0) cols = 1;		/* at least one column */
  curcol = 0;				/* currently printing first column */

  for ( i = 0; u[i] != NULL; i++) {	/* scan through again, and print 'em */
    if (u[i]->flags & USR_PARTIAL) {	/* found a match... */
      if (curcol == 0) {
	cmxnl();			/* new line for first column */
	cmxputs(" ");			/* and offset a bit */
      }
      cmxputs(u[i]->pwd->pw_name);	/* print the name */

      if (curcol < cols -1) {		/* if not last column */
	int j;				/* space out to end of column */
	for(j = strlen(u[i]->pwd->pw_name); j < maxlen; j++)
	  cmxputc(SPACE);
      }
      curcol = (curcol+1) % cols;		/* move to next column */
    }
  }
  cmxnl();				/* newline at the end */
  return(CMxOK);			/* all done */
}


/*
 * find a partial completion for a list of users
 */

PASSEDSTATIC char *
partial(text,textlen,u,pcount,exact) 
char *text; 
int textlen;
struct usr **u;
int pcount;
int *exact;
{
  int i,j,k;
  static char buf[50];
  char tbuf[50],fbuf[50],uname[50];
  int buflen,fbuflen;

  *exact = TRUE;			/* assume we find an exact match */
  strncpy(tbuf,text,textlen);
  tbuf[textlen] = '\0';

  buf[0] = '\0';			/* start off with no matches */
  for(i = 0, j = 0; u[i] != NULL && j < pcount; i++) {
    if (u[i]->flags & USR_PARTIAL) {
      strcpy(fbuf,u[i]->pwd->pw_name);

      fbuflen = strlen(fbuf);		/* copy then name */
      while(!fmatch(fbuf,tbuf,FALSE)) {	/* shorten until we match */
	fbuf[--fbuflen] = '\0';
      }
      strcpy(uname,u[i]->pwd->pw_name);
      if (j++ == 0)
	strcpy(buf,&uname[fbuflen]);	/* first time, grab completion */
      else {
	buflen = strlen(buf);
	for(k = 0; k < buflen; k++)	/* otherwise trim it to match */
	  if (buf[k] != uname[fbuflen+k]) {
	    buf[k] = '\0';		/* if end of a name, then exact */
	    if (uname[fbuflen+k] != '\0') *exact = FALSE;
	    else *exact = TRUE;
	    break;
	  }
      }
    }
  }
  return(buf);
}

/*
 * user name completion routine
 */
usrcomplete(text, textlen, fdbp, full, cplt, cpltlen)
char *text,**cplt;
int textlen,full,*cpltlen;
fdb *fdbp;
{
  int ematch, pmatch,i;
  struct usr **u;
  char *term;

  *cplt = NULL; *cpltlen = 0;		/* assume no completions */
					/* find matches */
  match(text, textlen, fdbp, &u, &term, &pmatch, &ematch);

  if (ematch >= 1) {			/* exact match? */
    *cplt = "";
    if (full) return(CMP_GO | CMP_SPC);	/* just use it */
    else return(CMP_PNC);
  }
  if (pmatch == 0) {			/* no match. */
    return(CMP_BEL|CMP_GO);		/* just beep and wake up */
  }
  if (pmatch > 1) {			/* more than one partial match. */
    int exact;
    *cplt = partial(text,textlen,u,pmatch,&exact);
    *cpltlen = strlen(*cplt);
    if (exact) return(CMP_GO|CMP_SPC);
    return(CMP_BEL);			/* we could partial complete */
  }					/* but for now,just beep */
  if (pmatch == 1) {			/* one match */
    int exact;
    for(i = 0; u[i] != NULL; i++) {
      if (u[i]->flags & USR_PARTIAL) {	/* find it */
					/* and return the completion */
	*cplt = partial(text,textlen,u,pmatch,&exact);
	*cpltlen = strlen(*cplt);
	if (full) return(CMP_GO | CMP_SPC);
	else return(CMP_PNC);
      }
    }
  }
  return(CMP_BEL);
}

/*
 * user name matching routine.
 */

static
match(text, textlen, fdbp, who, term, pmatch, ematch) 
char *text, **term;
int textlen, *pmatch,*ematch;
fdb *fdbp;
struct usr ***who;
{
  struct usr **u,**read_usrs();
  char user[100];
  int matches=0, i,inlen;
  brktab *btab;				/* break table to use */
  struct usr ** maybe_read_usrs(), **usrs;

  usrs = maybe_read_usrs(fdbp->_cmffl & USR_UPDONLY, fdbp->_cmffl & USR_NOUPD);
  *term = NULL;
  
					/* just update the user table */
  if (fdbp->_cmffl & USR_UPDONLY) {
      *ematch = *pmatch = 0;		/* no matches */
      return;
  }

  if ((btab = fdbp->_cmbrk) == NULL)	/* get supplied break table */
    btab = &usrbrk;			/* or use default */

  for (inlen = 0; inlen < textlen; inlen++) { /* find # of usable chars */
    					/* stop on first break char */
    if (text[inlen] & 0x80) {
      text[inlen] &= 0x7f;
      continue;
    }
    else
      if ((!BREAK(btab,0x7f&text[inlen],inlen))) 
	continue;
    else
      break;
  }

  if (inlen == textlen)			/* no break char? */
    *term = NULL;			/* then set no terminator */
  else
    *term = text+inlen;			/* else point to it for caller */

					/* copy the string to match */
  for(i = 0; i < inlen; i++) user[i] = text[i]&0x7f;
  user[inlen] = '\0';			/* null terminated of course */
  *pmatch = *ematch = 0;		/* and start with no matches */

  for (u = usrs; *u != NULL; u++) {	/* loop through all users */
    (*u)->flags &= ~(USR_PARTIAL | USR_EXACT); /* assume a mismatch */
    if (fmatch((*u)->pwd->pw_name,user,TRUE)) { /* partial match? */
      (*u)->flags |= USR_PARTIAL;	/* flag */
      (*pmatch)++;			/* and count it */
      if (fmatch((*u)->pwd->pw_name,user,FALSE)) { /* exact match? */
	(*u)->flags |= USR_EXACT;	/* flag and count it */
	(*ematch)++;
      }
    }
  }
  *who = usrs;				/* return the list */
  return;
}

/*
 * free up user list read from passwd file
 */
free_usrs(usrs) struct usr **usrs; {
  struct usr *u;
  if (usrs == NULL) return;		/* no list.  never mind */
  while (*usrs != NULL) {		/* for all users */
    u = *usrs;
    if (u->pwd != NULL) {		/* if there is a passwd entry here */
					/* free string space (note this 
					   is all malloc as one chunk, so
					   it is all freed as one chunk */
      if (u->pwd->pw_name != NULL) free(u->pwd->pw_name);
      free(u->pwd);			/* free the passwd entry */
      usrs++;				/* and go to the next user */
    }
  }
  free(usrs);
}


struct usr **
read_usrs() {
  struct usr **usrs = NULL;
  int maxusrs = 0;
  int nusrs = 0;
  struct passwd *p,*getpwent(),*copyp();

  setpwent();
  while (1) {
    p = getpwent();
    if (nusrs == maxusrs) {
      usrs = (struct usr **)cmrealloc(usrs,
				      sizeof(struct usr *)*(maxusrs+INCUSRS));
      maxusrs+=INCUSRS;
    }
    if (p == NULL) break;
    usrs[nusrs] = (struct usr *)malloc(sizeof(struct usr));
    usrs[nusrs]->pwd = copyp(p);
    usrs[nusrs++]->flags = 0;
  }
  usrs[nusrs] = NULL;
  endpwent();
  sort_usrs(usrs);			/* sort them */
  return(usrs);
}

usrcmp(a,b) register struct usr **a,**b; {
  return(strcmp((*a)->pwd->pw_name,(*b)->pwd->pw_name));
}

sort_usrs(usrs) struct usr **usrs; {
  int i;
  for (i = 0; usrs[i] != NULL; i++);
  qsort(usrs,i,sizeof(struct usr *), usrcmp);
}



struct passwd *
copyp(p) struct passwd *p; {
  struct passwd *p1;
  char *buf,*bp;
  p1 = (struct passwd *) malloc(sizeof (struct passwd));
  buf = (char *)malloc(6 + strlen(p->pw_name) + strlen(p->pw_passwd) +
	       strlen(p->pw_gecos) + strlen(p->pw_dir) + strlen(p->pw_shell));
  bp = buf;
  strcpy(bp,p->pw_name);  p1->pw_name = bp; bp += strlen(bp)+1;
  strcpy(bp,p->pw_passwd);  p1->pw_passwd = bp; bp += strlen(bp)+1;
  strcpy(bp,p->pw_gecos);  p1->pw_gecos = bp; bp += strlen(bp)+1;
  strcpy(bp,p->pw_dir);  p1->pw_dir = bp; bp += strlen(bp)+1;
  strcpy(bp,p->pw_shell);  p1->pw_shell = bp; bp += strlen(bp)+1;
  p1->pw_uid = p->pw_uid;
  p1->pw_gid = p->pw_gid;
  return(p1);
}

struct passwd**
builduserlist(u,count) struct usr **u; {
  static struct passwd **p=NULL;
  int i,j;
  if (p != NULL) free(p);
  p = (struct passwd **)malloc((count+1)*sizeof(struct passwd*));
  for (i = 0,j = 0; u[i] != NULL; i++) {
    if (u[i]->flags & USR_EXACT)
      p[j++] = u[i]->pwd;
  }
  p[count] = NULL;
  return(p);
}


struct usr **
maybe_read_usrs(always,dont)
int always, dont;
{
    struct stat buf;
    static struct usr **usrs=NULL;
    static int passwd_time=0;
    int temp = always;
    struct usr **rdcrunch();
    struct stat sbuf;
    
    stat("/etc/passwd",&buf);		/* reread the passwd file? */
    always = passwd_time != 0 && always && !dont;
    dont = passwd_time != 0 && dont && !temp;
    if ((passwd_time == 0 || buf.st_mtime > passwd_time || always) && !dont) {
	passwd_time = buf.st_mtime;
	if (usedcrunch)
	    free_crunch_users(usrs);
	else
	    free_usrs(usrs);		/* free up old entries */
	if (!((stat("/etc/passwd.crunch", &sbuf) == 0) &&
	      (sbuf.st_mtime > buf.st_mtime) &&
	      (usrs = rdcrunch("/etc/passwd.crunch")))) {
	    usrs = read_usrs();		/* and re read the file */
	    usedcrunch = FALSE;
	}
	else
	    usedcrunch = TRUE;
    }
    return(usrs);
}



struct passwd *ents;
struct usr *usrstore;
char *buf;

#define fixstr(s) ((*s) += (int) buf)

#ifdef howmany
#undef howmany
#endif
struct usr**
rdcrunch(fname)
char *fname;
{
    int fd,len,howmany;
    register int i;
    struct stat sbuf;
    register struct passwd *p;
    register struct usr **usrs;

    if (stat(fname,&sbuf) < 0) {
	return(NULL);
    }
    fd = open(fname,O_RDONLY,0);
    if (fd == -1) {
	perror(fname);
	return(NULL);
    }
    if (xread(fd,&howmany,sizeof(howmany)) <= 0) {
	perror("read");
	close(fd);
	return(NULL);
    }
	
    ents = (struct passwd *) malloc(howmany*sizeof(struct passwd));
    if (xread(fd, ents, howmany*sizeof(struct passwd)) <= 0) {
	perror("read");
	close(fd);
	free(ents);
	return(NULL);
    }
    buf = malloc(len = sbuf.st_size -
		 (sizeof(howmany) + howmany * sizeof(struct passwd)));
    if (xread(fd, buf, len) <= 0) {
	perror("read");
	close(fd);
	free(ents);
	free(buf);
	return(NULL);
    }
    close(fd);
    usrs = (struct usr**)malloc((howmany+1) * sizeof (struct usr *));
    usrstore = (struct usr *)malloc(howmany * sizeof (struct usr));
    for ( i = 0; i < howmany; i++) {
	p = &ents[i];
	fixstr(&p->pw_name);
	fixstr(&p->pw_passwd);
#ifdef SYSV
	fixstr(&p->pw_age);
#endif
	fixstr(&p->pw_comment);
	fixstr(&p->pw_gecos);
	fixstr(&p->pw_dir);
	fixstr(&p->pw_shell);
	usrs[i] = &usrstore[i];
	usrs[i]->pwd = p;
	usrs[i]->flags = 0;
    }
    usrs[howmany] = NULL;
    return(usrs);
}

free_crunch_users(usrs) 
struct usr **usrs;
{
    free(ents);
    free(buf);
    free(usrs);
    free(usrstore);
    ents = NULL;
    buf = NULL;
    usrs = NULL;
    usrstore = NULL;
}

#undef read

xread(fd,buf,len)
int fd,len;
char *buf;
{
    int sofar = 0,x;

    while(sofar < len) {
	x = read(fd, buf+sofar, len - sofar);
	if (x == -1) {
	    return(-1);
	}
	sofar += x;
	if (x == 0) {
	    break;
	}
    }
    return(sofar);
}

#endif /* NO_USERNAME_COMPLETION */
#else /*  !unix */

#include "ccmd.h"			/* ccmd symbols */
#include "cmfncs.h"			/* ccmd internal symbols */

static brktab usrbrk = {		/* all valid chars for users */
  {					/* alphanums, "~#/_-\[]," */
    0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, 0x00, 0x3f,
    0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x0b,
  },
  {
    0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, 0x00, 0x3f,
    0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x0b,
  }
};

int usrparse(), usrhelp(), usrcomplete();
ftspec ft_usr =  { usrparse, usrhelp, usrcomplete, 0, &usrbrk };

PASSEDSTATIC int
usrparse(text, textlen, fdbp, parselen, value)
char *text;
int textlen, *parselen;
fdb *fdbp;
pval *value;
{
  int i;
  *parselen = 0;
  value->_pvusr = NULL;
  for (i = 0; i < textlen; i++) 
    if (isspace(text[textlen-1])) 
      return(USRxNM);
  return(CMxINC);
}

usrcomplete(text, textlen, fdbp, full, cplt, cpltlen)
char *text,**cplt;
int textlen,full,*cpltlen;
fdb *fdbp;
{
  *cplt = NULL; 
  *cpltlen = 0;
  return(CMP_BEL|CMP_SPC|CMP_GO);
}

PASSEDSTATIC int
usrhelp(text, textlen, fdbp, cust) 
char *text;
int textlen, cust;
fdb *fdbp;
{

  if (!cust) {				/* standard help msg */
    cmxputs("user name, one of the following:");
  }

  cmxnl();
  cmxputs(" (No usernames match current input)");
  return(CMxOK);
}

#endif /*  !unix */
