#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.

 Author: Howie Kaye
*/

#define GRPERR

/*
 * ccmd group name parser.
 */

#if unix
#include "ccmdlib.h"
#include "cmfncs.h"
#include "cmgrp.h"

#define INCGRPS 100

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

PASSEDSTATIC char *partial ARGS((const char *text, int textlen,
	struct s_grp **g, int pcount, int *exact));
PASSEDSTATIC int grphelp ARGS((char *text, int textlen, fdb *fdbp,
	int cust, int lines));
PASSEDSTATIC int grpparse ARGS((char *text, int textlen, fdb *fdbp,
	int *parselen, pval *value));
static int grpcmp ARGS((struct s_grp **a, struct s_grp **b));
static int grpcomplete ARGS((char *text, int textlen, fdb *fdbp,
	int full, const char **cplt, int *cpltlen));
static struct group **buildgrouplist ARGS((struct s_grp **g, int count));
static struct group *copyg ARGS((struct group *grp));
static struct s_grp **maybe_read_grps ARGS((flag_t always, flag_t dont));
static struct s_grp **read_grps ARGS((void));
static void free_grps ARGS((struct s_grp **grps));

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

ftspec ft_grp =  { grpparse, grphelp, grpcomplete, 0, &grpbrk };

/*
 * parse routine for group names.
 */
PASSEDSTATIC int
#if HAVE_STDC
grpparse(char *text, int textlen, fdb *fdbp, int *parselen, pval *value)
#else /* K&R style */
grpparse(text, textlen, fdbp, parselen, value)
char *text;
int textlen, *parselen;
fdb *fdbp;
pval *value;
#endif /* HAVE_STDC */
{
  static struct s_grp **g;
  const char *term;
  int pmatch, ematch;

  match(text, textlen, fdbp, &g, &term, &pmatch, &ematch); /* find matches */

  if (pmatch == 0)			/* no partial matches. */
      return(GRPxNM);			/* then no matches */

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

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

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

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

/*
 * group name help routine.
 */
PASSEDSTATIC int
#if HAVE_STDC
grphelp(char *text, int textlen, fdb *fdbp, int cust, int lines)
#else /* K&R style */
grphelp(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 s_grp **g;
  const char *term;
  int mylines = 1;
  if (!cust) {				/* standard help msg */
    cmxputs("group name, one of the following:");
  }

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

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

  maxlen = 0;				/* calculate number of columns */
  len = 0;				/* based on maximum name length */

  for (i = 0 ; g[i] != NULL; i++) {	/* scan through matches */
    if (g[i]->flags & GRP_PARTIAL) {
      len = strlen(g[i]->grp->gr_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; g[i] != NULL; i++) {	/* scan through again, and print 'em */
    if (g[i]->flags & GRP_PARTIAL) {	/* found a match... */
      if (curcol == 0) {
	cmxnl();			/* new line for first column */
	if (mylines >= lines) {
	  if (!cmhelp_more("--space to continue, Q to stop--"))
	      return(-1);
	  else {
	      lines = cmcsb._cmrmx;
	      mylines = 0;
	  }
	}
	mylines++;
	cmxputs(" ");			/* and offset a bit */
      }
      cmxputs(g[i]->grp->gr_name);	/* print the name */

      if (curcol < cols -1) {		/* if not last column */
	int j;				/* space out to end of column */
	for(j = strlen(g[i]->grp->gr_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 groups
 */

PASSEDSTATIC char *
#if HAVE_STDC
partial(const char *text, int textlen, struct s_grp **g, int pcount, int *exact)
#else /* K&R style */
partial(text,textlen,g,pcount,exact)
const char *text;
int textlen;
struct s_grp **g;
int pcount;
int *exact;
#endif /* HAVE_STDC */
{
  int i,j,k;
  static char buf[50];
  char tbuf[50],fbuf[50],gname[50];
  int buflen,fbuflen=0;

  *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; g[i] != NULL && j < pcount; i++) {
    if (g[i]->flags & GRP_PARTIAL) {
      strcpy(fbuf,g[i]->grp->gr_name);
      fbuflen = strlen(fbuf);		/* copy then name */

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

/*
 * group name completion routine
 */
static int
#if HAVE_STDC
grpcomplete(char *text, int textlen, fdb *fdbp, int full, const char **cplt,
	    int *cpltlen)
#else /* K&R style */
grpcomplete(text, textlen, fdbp, full, cplt, cpltlen)
char *text;		/* not const: high-order bit lopped by match() */
const char **cplt;
int textlen,full,*cpltlen;
fdb *fdbp;
#endif /* HAVE_STDC */
{
  int ematch, pmatch,i;
  struct s_grp **g;
  const char *term;
  static char empty_string[] = "";

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

  if (ematch >= 1) {			/* exact match? */
    *cplt = empty_string;
    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,g,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; g[i] != NULL; i++) {
      if (g[i]->flags & GRP_PARTIAL) {	/* find it */
					/* and return the completion */
	*cplt = partial(text,textlen,g,pmatch,&exact);
	*cpltlen = strlen(*cplt);
	if (full) return(CMP_GO | CMP_SPC);
	else return(CMP_PNC);
      }
    }
  }
  return(CMP_BEL);
}

/*
 * group name matching routine.
 */

static void
#if HAVE_STDC
match(char *text, int textlen, fdb *fdbp, struct s_grp ***who,
      const char **term, int *pmatch, int* ematch)
#else /* K&R style */
match(text, textlen, fdbp, who, term, pmatch, ematch)
char *text;				/* high-order bit is lopped off on return */
const char **term;
int textlen, *pmatch,*ematch;
fdb *fdbp;
struct s_grp ***who;
#endif /* HAVE_STDC */
{
  struct s_grp **grps=NULL;
  struct s_grp **g;
  char gp[100];
  int i,inlen;
  brktab *btab;				/* break table to use */

  grps = maybe_read_grps(fdbp->_cmffl & GRP_UPDONLY, fdbp->_cmffl & GRP_NOUPD);
  *term = NULL;

					/* just update the user table */
  if (fdbp->_cmffl & GRP_UPDONLY) {
      *ematch = *pmatch = 0;		/* no matches */
      return;
  }

  *term = NULL;

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

  for (inlen = 0; inlen < textlen; inlen++) { /* find # of usable chars */
    if (text[inlen] & 0x80) {
      text[inlen] &= 0x7f;
      continue;
    }
    else
      if (index("[]{}*?^,",(int)(text[inlen])))
 	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++) gp[i] = text[i]&0x7f;
  gp[inlen] = '\0';			/* null terminated of course */
  *pmatch = *ematch = 0;		/* and start with no matches */

 for (g = grps; *g != NULL; g++) {	/* loop through all groups */
    (*g)->flags &= ~(GRP_PARTIAL | GRP_EXACT); /* assume a mismatch */
    if (fmatch((*g)->grp->gr_name,gp,TRUE)) { /* partial match? */
      (*g)->flags |= GRP_PARTIAL;	/* flag */
      (*pmatch)++;			/* and count it */
      if (fmatch((*g)->grp->gr_name,gp,FALSE)) { /* exact match? */
	(*g)->flags |= GRP_EXACT;	/* flag and count it */
	(*ematch)++;
      }
    }
  }
  *who = grps;				/* return the list */
  return;
}

/*
 * free up group list read from group file
 */
static void
#if HAVE_STDC
free_grps(struct s_grp **grps)
#else /* K&R style */
free_grps(grps)
struct s_grp **grps;
#endif /* HAVE_STDC */
{
  struct s_grp *g;
  int i;
  if (grps == NULL) return;		/* no list.  never mind */
  while (*grps != NULL) {		/* for all groups */
    g = *grps;
    if (g->grp != NULL) {		/* if there is a group entry here */
					/* free string space (note this
					   is all malloc as one chunk, so
					   it is all freed as one chunk */
      if (g->grp->gr_name != NULL) free(g->grp->gr_name);
      if (g->grp->gr_passwd != NULL) free(g->grp->gr_passwd);
      for (i = 0; g->grp->gr_mem[i] != NULL; i++)
	free(g->grp->gr_mem[i]);
      free(g->grp);			/* free the group entry */
      grps++;				/* and go to the next group */
    }
  }
  free(grps);
}


static struct s_grp **
read_grps(VOID) {
  struct s_grp **grps = NULL;
  int maxgrps = 0;
  int ngrps = 0;
  struct group *g;

  setgrent();
  while (1) {
    g = (struct group *)getgrent();
    if (ngrps == maxgrps) {
      grps = (struct s_grp **)cmrealloc((char*)grps,
		(int)sizeof(struct s_grp *)*(maxgrps+INCGRPS));
      maxgrps+=INCGRPS;
    }
    if (g == NULL) break;
    grps[ngrps] = (struct s_grp *)malloc(sizeof(struct s_grp));
    grps[ngrps]->grp = copyg(g);
    grps[ngrps++]->flags = 0;
  }
  grps[ngrps] = NULL;
  endgrent();
  return(grps);
}

static int
#if HAVE_STDC
grpcmp(register struct s_grp **a, register struct s_grp **b)
#else /* K&R style */
grpcmp(a,b)
register struct s_grp **a,**b;
#endif /* HAVE_STDC */
{
  return(strcmp((*a)->grp->gr_name,(*b)->grp->gr_name));
}

static void
#if HAVE_STDC
sort_grps(struct s_grp **grps)
#else /* K&R style */
sort_grps(grps)
struct s_grp **grps;
#endif /* HAVE_STDC */
{
  int i;
  for (i = 0; grps[i] != NULL; i++);
  qsort(grps,i,sizeof(struct s_grp *), (QSORT_FUN_TYPE)grpcmp);
}

static struct group *
#if HAVE_STDC
copyg(struct group *grp)
#else /* K&R style */
copyg(grp)
struct group *grp;
#endif /* HAVE_STDC */
{
  struct group *g;
  int i;
  static int cnt = 0;

  g = (struct group *) malloc(sizeof (struct group)); /* get a group */
  g->gr_name = (char*)malloc(strlen(grp->gr_name)+1); /* space for the name */
  strcpy(g->gr_name,grp->gr_name);	/* copy the name */
  g->gr_passwd = (char*)malloc(strlen(grp->gr_passwd)+1); /* space for the passwd */
  strcpy(g->gr_passwd,grp->gr_passwd);	/* copy the passwd */

  for (i = 0; grp->gr_mem[i] != NULL; i++); /* count the members */
  g->gr_mem = (char **)malloc((i+1)*sizeof (char *)); /* space for ptr */
  for (i = 0; grp->gr_mem[i] != NULL; i++) { /* copy the members */
    g->gr_mem[i] = (char*)malloc(strlen(grp->gr_mem[i])+1); /* alloc space */
    strcpy(g->gr_mem[i], grp->gr_mem[i]); /* copy the name */
  }
  g->gr_mem[i] = NULL;
  g->gr_gid = grp->gr_gid;
  cnt++;
  return(g);
}


static struct group**
#if HAVE_STDC
buildgrouplist(struct s_grp **g, int count)
#else /* K&R style */
buildgrouplist(g,count)
struct s_grp **g;
int count;
#endif /* HAVE_STDC */
{
  static struct group **gr=NULL;
  int i,j;
  if (gr != NULL) free(gr);
  gr = (struct group **)malloc((count+1)*sizeof(struct group*));
  for (i = 0,j = 0; g[i] != NULL; i++) {
    if (g[i]->flags & GRP_EXACT)
      gr[j++] = g[i]->grp;
  }
  gr[count] = NULL;
  return(gr);
}


static struct s_grp **
#if HAVE_STDC
maybe_read_grps(flag_t always, flag_t dont)
#else /* K&R style */
maybe_read_grps(always,dont)
flag_t always, dont;
#endif /* HAVE_STDC */
{
    static struct s_grp **grps=NULL;
    static time_t group_time=0;
    struct stat buf;
    int temp = always;

    stat("/etc/group",&buf);		/* reread the group file? */
    always = group_time != 0 && always && !dont;
    dont = group_time != 0 && dont && !temp;
    if ((group_time == 0 || buf.st_mtime > group_time || always) && !dont) {
	group_time = buf.st_mtime;
	free_grps(grps);		/* free up old entries */
	grps = read_grps();		/* and re read the file */
	sort_grps(grps);		/* sort them */
    }
    return(grps);
}

#else /* !unix */

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

static brktab grpbrk = {		/* all valid chars for groups */
  {					/* 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_grp =  { grpparse, grphelp, grpcomplete, 0, &grpbrk };

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

static int
#if HAVE_STDC
grpcomplete(char *text, int textlen, fdb *fdbp, int full,
	    const char **cplt, int *cpltlen)
#else /* K&R style */
grpcomplete(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
grphelp(char *text, int textlen, fdb *fdbp, int cust, int lines)
#else /* K&R style */
grphelp(text, textlen, fdbp, cust,lines)
char *text;
int textlen, cust;
fdb *fdbp;
int lines;
#endif /* HAVE_STDC */
{

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

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

#endif /* !unix */
