#include "config.h"
/*
 Copyright (c) 1986, 1990 by The 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.

 Author: Howie Kaye
*/

/*
 * #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,
  }
};

PASSEDSTATIC char *partial ARGS((char *text, int textlen,
	 struct usr **u, int pcount, int *exact));
PASSEDSTATIC int usrhelp ARGS((char *text, int textlen, fdb *fdbp,
	 int cust, int lines));
PASSEDSTATIC int usrparse ARGS((char *text, int textlen, fdb *fdbp,
	 int *parselen, pval *value));

#ifdef NO_USERNAME_COMPLETION
static int username_test ARGS((char *text, int textlen, fdb *fdbp,
	 int *parselen, struct passwd **pwp, int completion));
#endif

static int usrcmp ARGS((struct usr **a, struct usr **b));
static int usrcomplete ARGS((char *text, int textlen, fdb *fdbp, int full,
	 const char **cplt, int *cpltlen));
static int xread ARGS((int fd, char *buf, int len));
static struct passwd **builduserlist ARGS((struct usr **u, int count));
static struct passwd *copyp ARGS((struct passwd *p));
static struct usr **maybe_read_usrs ARGS((int always, int dont));
static struct usr **rdcrunch ARGS((const char *fname));
static struct usr **read_usrs ARGS((void));
static void free_crunch_users ARGS((struct usr **usrs));
static void free_usrs ARGS((struct usr **usrs));

#define match Umatch	/* avoid conflict in this file with global match() */
static void match ARGS((char *text, int textlen, fdb *fdbp, struct usr ***who,
	 const char **term, int *pmatch, int *ematch));

static void sort_usrs ARGS((struct usr **usrs));

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

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

/*
 * common code for usrparse and usrcomplete
 */

static int
#if HAVE_STDC
username_test (char *text, int textlen, fdb *fdbp, int *parselen,
	       struct passwd **pwp, int completion)
#else /* K&R style */
username_test (text, textlen, fdbp, parselen, pwp, completion)
char *text;
int textlen, *parselen, completion;
fdb *fdbp;
struct passwd **pwp;			/* optional return value */
#endif /* HAVE_STDC */
{
    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 };

PASSEDSTATIC int
#if HAVE_STDC
usrparse (char *text, int textlen, fdb *fdbp, int *parselen, pval *value)
#else /* K&R style */
usrparse (text, textlen, fdbp, parselen, value)
char *text;
int textlen, *parselen;
fdb *fdbp;
pval *value;
#endif /* HAVE_STDC */
{
    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;
}

PASSEDSTATIC int
#if HAVE_STDC
usrhelp (char *text, int textlen, fdb *fdbp, int cust, int lines)
#else /* K&R style */
usrhelp (text, textlen, fdbp, cust, lines)
char *text;
int textlen, cust;
fdb *fdbp;
int lines;
#endif /* HAVE_STDC */
{
    if (!cust)
	cmxputs ("user name");

    return lines;
}

static int
#if HAVE_STDC
usrcomplete (char *text, int textlen, fdb *fdbp, int full, const char **cplt,
	     int *cpltlen)
#else /* K&R style */
usrcomplete (text, textlen, fdbp, full, cplt, cpltlen)
char *text;
const char **cplt;
int textlen, full, *cpltlen;
fdb *fdbp;
#endif /* HAVE_STDC */
{
    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
#if HAVE_STDC
usrparse(char *text, int textlen, fdb *fdbp, int *parselen, pval *value)
#else /* K&R style */
usrparse(text, textlen, fdbp, parselen, value)
char *text;
int textlen, *parselen;
fdb *fdbp;
pval *value;
#endif /* HAVE_STDC */
{
  static struct usr **u;
  const char *term;
  int pmatch, ematch;
  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
#if HAVE_STDC
usrhelp(char *text, int textlen, fdb *fdbp, int cust, int lines)
#else /* K&R style */
usrhelp(text, textlen, fdbp, cust, lines)
char *text;
int textlen, cust;
fdb *fdbp;
int lines;
#endif /* HAVE_STDC */
{
  int ematch, pmatch,i,len,maxlen,cols,curcol;
  struct usr **u;
  const char *term;
  int mylines = 0;

  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(lines - 1);			/* all done */
  }

  if (pmatch > cmcsb._cmmax) {		/* too many to display */
    cmxnl();
    cmxprintf(" %d matching users.\n",pmatch); /* just say how many. */
    return(lines -2);			/* all done */
  }

  maxlen = 0;				/* calculate number of columns */
  len = 0;				/* based on maximum name length */
  mylines++;
  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 */
	mylines++;
	if (mylines >= lines) {
	  if (!cmhelp_more("--space to continue, Q to stop--"))
	      return(-1);
	  else {
	      lines = cmcsb._cmrmx;
	      mylines = 0;
	  }
        }

	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(lines-mylines);		/* all done */
}


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

PASSEDSTATIC char *
#if HAVE_STDC
partial(char *text, int textlen, struct usr **u, int pcount, int *exact)
#else /* K&R style */
partial(text,textlen,u,pcount,exact)
char *text;
int textlen;
struct usr **u;
int pcount;
int *exact;
#endif /* HAVE_STDC */
{
  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
 */
static int
#if HAVE_STDC
usrcomplete(char *text, int textlen, fdb *fdbp, int full, const char **cplt, int *cpltlen)
#else /* K&R style */
usrcomplete(text, textlen, fdbp, full, cplt, cpltlen)
char *text;
const char **cplt;
int textlen,full,*cpltlen;
fdb *fdbp;
#endif /* HAVE_STDC */
{
  int ematch, pmatch,i;
  struct usr **u;
  const 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 void
#if HAVE_STDC
match(char *text, int textlen, fdb *fdbp, struct usr ***who,
      const char **term, int *pmatch, int *ematch)
#else /* K&R style */
match(text, textlen, fdbp, who, term, pmatch, ematch)
char *text;
const char **term;
int textlen, *pmatch,*ematch;
fdb *fdbp;
struct usr ***who;
#endif /* HAVE_STDC */
{
  struct usr **u;
  char user[100];
  int i,inlen;
  brktab *btab;				/* break table to use */
  struct usr **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
 */
static void
#if HAVE_STDC
free_usrs(struct usr **usrs) {
#else /* K&R style */
free_usrs(usrs) struct usr **usrs; {
#endif /* HAVE_STDC */
  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);
}


static struct usr **
read_usrs(VOID)
{
  struct usr **usrs = NULL;
  int maxusrs = 0;
  int nusrs = 0;
  struct passwd *p;

  setpwent();
  while (1) {
    p = (struct passwd *)getpwent();
    if (nusrs == maxusrs) {
      usrs = (struct usr **)cmrealloc((char*)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);
}

static int
#if HAVE_STDC
usrcmp(register struct usr **a, register struct usr **b) {
#else /* K&R style */
usrcmp(a,b) register struct usr **a,**b; {
#endif /* HAVE_STDC */
  return(strcmp((*a)->pwd->pw_name,(*b)->pwd->pw_name));
}

static void
#if HAVE_STDC
sort_usrs(struct usr **usrs)
#else /* K&R style */
sort_usrs(usrs) struct usr **usrs;
#endif /* HAVE_STDC */
{
  int i;

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



static struct passwd *
#if HAVE_STDC
copyp(struct passwd *p)
#else /* K&R style */
copyp(p) struct passwd *p;
#endif /* HAVE_STDC */
{
  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);
}

static struct passwd**
#if HAVE_STDC
builduserlist(struct usr **u, int count)
#else /* K&R style */
builduserlist(u,count)
struct usr **u;
int count;
#endif /* HAVE_STDC */
{
  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);
}


static struct usr **
#if HAVE_STDC
maybe_read_usrs(int always, int dont)
#else /* K&R style */
maybe_read_usrs(always,dont)
int always, dont;
#endif /* HAVE_STDC */
{
    struct stat buf;
    static struct usr **usrs=NULL;
    static time_t passwd_time=0;
    int temp = always;
    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);
}

static struct passwd *ents;
static struct usr *usrstore;
static char *ubuf;

#define fixstr(s) ((*s) += (long) ubuf)

#ifdef howmany
#undef howmany
#endif

static struct usr**
#if HAVE_STDC
rdcrunch(const char *fname)
#else /* K&R style */
rdcrunch(fname)
const char *fname;
#endif /* HAVE_STDC */
{
    int fd,howmany;
    size_t len;
    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,(char*)&howmany,sizeof(howmany)) <= 0) {
	perror("read");
	close(fd);
	return(NULL);
    }

    ents = (struct passwd *) malloc(howmany*sizeof(struct passwd));
    if (xread(fd, (char*)ents, (int)(howmany*sizeof(struct passwd))) <= 0) {
	perror("read");
	close(fd);
	free(ents);
	return(NULL);
    }
    ubuf = (char*)malloc(len = (size_t)sbuf.st_size -
			 (sizeof(howmany) + howmany * sizeof(struct passwd)));
    if (xread(fd, ubuf, len) <= 0) {
	perror("read");
	close(fd);
	free(ents);
	free(ubuf);
	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);
#if !defined(HAVE_GNU_LINUX)
#ifdef SYSV
	fixstr(&p->pw_age);
#endif
#endif
#if !defined(_IBMR2)	/* IBM RS/6000 AIX lacks this element */
#if !defined(HAVE_GNU_LINUX) && !defined(HAVE_NEW_BSD)
	fixstr(&p->pw_comment);
#endif
#endif
	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);
}

static void
#if HAVE_STDC
free_crunch_users(struct usr **usrs)
#else /* K&R style */
free_crunch_users(usrs)
struct usr **usrs;
#endif /* HAVE_STDC */
{
    free(ents);
    free(ubuf);
    free(usrs);
    free(usrstore);
    ents = NULL;
    ubuf = NULL;
    usrs = NULL;
    usrstore = NULL;
}

#undef read

static int
#if HAVE_STDC
xread(int fd, char *buf, int len)
#else /* K&R style */
xread(fd,buf,len)
int fd,len;
char *buf;
#endif /* HAVE_STDC */
{
    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,
  }
};

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

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

static int
#if HAVE_STDC
usrcomplete(char *text, int textlen, fdb *fdbp, int full, const char **cplt,
	    int *cpltlen)
#else /* K&R style */
usrcomplete(text, textlen, fdbp, full, cplt, cpltlen)
char *text;
const char **cplt;
int textlen,full,*cpltlen;
fdb *fdbp;
#endif /* HAVE_STDC */
{
  *cplt = NULL;
  *cpltlen = 0;
  return(CMP_BEL|CMP_SPC|CMP_GO);
}

PASSEDSTATIC int
#if HAVE_STDC
usrhelp(char *text, int textlen, fdb *fdbp, int cust, int lines)
#else /* K&R style */
usrhelp(text, textlen, fdbp, cust, lines)
char *text;
int textlen, cust;
fdb *fdbp;
int lines;
#endif /* HAVE_STDC */
{

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

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

#endif /*  !unix */
