RCS_ID_C="$Id: groupunit.c,v 3.2 1994/05/19 04:29:29 ppessi Exp $";
/*
 * groupunit.c --- unit for groups
 *
 * Author: ppessi <Pekka.Pessi@hut.fi>
 *
 * This file is part of the AmiTCP/IP netinfo.device.
 *
 * Copyright © 1993 AmiTCP/IP Group, <AmiTCP-Group@hut.fi>
 *                  Helsinki University of Technology, Finland.
 *
 * Created      : Wed Sep 15 01:25:26 1993 ppessi
 * Last modified: Thu May 19 04:01:42 1994 ppessi
 */

#include "entries.h"
#include <assert.h>
#include <string.h>

static void *CopyEntToGroup(struct NetInfoReq *req, struct Ent *e);
static struct Ent *CopyGroupToEnt(BASE, struct NetInfoReq *req);
static struct Ent *ParseGroup(BASE, register UBYTE *p);
static int PrintGroup(BASE, BPTR file, struct Ent *e);
static void GetGroups(BASE, struct NetInfoReq *req, struct NetInfoMap *nim);

const static struct MapMethods group_methods[1] = 
{
  {
    /* Other methods */
    ParseGroup,
    PrintGroup,
    CopyEntToGroup,
    CopyGroupToEnt,
    EntCleanup,
    GetGroups,
    EntHandleNotify,
  },
};

/*
 * Allocate and initialize group map
 */
struct NetInfoMap *InitGroupMap(BASE)
{
  struct NetInfoMap *nim = AllocVec(sizeof(*nim), MEMF_CLEAR|MEMF_PUBLIC);

  if (nim) {
    nim->nim_Methods = group_methods;
    nim->nim_Name = "group";
    nim->nim_Filename = _PATH_GROUP;
  }

  return nim;
}

/*
 * Copy an GroupEnt structure to group structure 
 */
static void *CopyEntToGroup(struct NetInfoReq *req, struct Ent *e)
{
  struct GroupEnt *ge = (struct GroupEnt *)e;
  struct NetInfoGroup *gr = req->io_Data;
  STRPTR *mto = (STRPTR *)(gr + 1);
  UBYTE  *to = (UBYTE *)(mto + ge->ge_nmembers);

  ULONG size = req->io_Length;
  ULONG actual = sizeof(*gr) + ge->ge_tlen + ge->ge_nmembers * sizeof(UBYTE *);

  UBYTE **mfrom;

  req->io_Actual = actual;
  
  if (size < actual) {
    return NULL;
  }

  /* copy group strings */
  to = stpcopy(gr->gr_name = to, ge->ge_group->gr_name);
  to = stpcopy(gr->gr_passwd = to, ge->ge_group->gr_passwd);
  /* .. and id */
  gr->gr_gid = ge->ge_group->gr_gid;

  /* .. and members */
  for (gr->gr_mem = mto, mfrom = ge->ge_group->gr_mem; 
       *mfrom && (*mto++ = to); ) {
    to = stpcopy(to, *mfrom++);
  }
  *mto = NULL;
  assert(to <= (UBYTE *)req->io_Data + req->io_Length);

  return gr;
}

/*
 * Copy an group structure into a internal entry
 */
static struct Ent *CopyGroupToEnt(BASE, struct NetInfoReq *req)
{
  struct NetInfoGroup *gr = (struct NetInfoGroup*)req->io_Data;
  struct GroupEnt *ge;
  UBYTE *to, **mem, **mto;
  short nmem = 1;
  ULONG txtlen = 0;
  
  /*
   * These cause EFAULT
   */
  if (gr == NULL || (1 & (LONG)gr) != 0 ||
      gr->gr_name == NULL || 
      gr->gr_passwd == NULL ||
      gr->gr_mem == NULL || (1 & (LONG)gr->gr_mem)) {
    req->io_Error = IOERR_BADADDRESS;
    return NULL;
  }

  for (mem = gr->gr_mem; *mem; mem++, nmem++) {
    if (*mem == NULL) {
      req->io_Error = IOERR_BADADDRESS;
      return NULL;
    }
    txtlen += strlen(*mem) + 1;
  }

  txtlen += strlen(gr->gr_name) + strlen(gr->gr_passwd) + 2;

  ge = AllocVec(sizeof(*ge) + nmem * sizeof(*mem) + txtlen, MEMF_CLEAR);
  if (ge == NULL) {
    req->io_Error = NIERR_NOMEM;
    return NULL;
  }

  ge->ge_node.ln_Type = ENT_GROUP;
  ge->ge_node.ln_Pri  = ENT_CHANGED;
  ge->ge_tlen = txtlen;
  ge->ge_group->gr_gid = gr->gr_gid;

  ge->ge_nmembers = nmem;
  ge->ge_group->gr_mem = mto = (UBYTE **)(ge + 1);
  to = (UBYTE *)(mto + nmem);
  /* Copy normal strings.. */
  to = stpcopy(ge->ge_group->gr_name   = to, gr->gr_name);
  to = stpcopy(ge->ge_group->gr_passwd = to, gr->gr_passwd);
  /* ..and members */
  for (mem = gr->gr_mem; *mem; mem++) {
    to = stpcopy(*mto++ = to, *mem);
    nmem--;
  }
  *mem = NULL;
  assert(to == ge->ge_group->gr_name + txtlen);
  assert(nmem == 1);

  return (struct Ent *)ge;
}

/****** netinfo.device/group ***********************************************

   NAME
        group - format of the group permissions file

   DESCRIPTION     
        The file <AmiTCP:db/group> consists of newline separated ASCII
        records, one per group, containing four bar `|' separated fields.
        These fields are as follows:

              group     Name of the group.
              passwd    Group's encrypted password.
              gid       The group's decimal ID.
              member    Group members.

        The group field is the group name used for granting file access to
        users who are members of the group.  The gid field is the number
        associated with the group name.  They should both be unique across
        the system (and often across a group of systems) since they control
        file access.  The passwd field is an optional encrypted password.
        This field is rarely used and an asterisk is normally placed in it
        rather than leaving it blank.  The member field contains the names
        of users granted the priviledges of group. The member names are
        separated by commas with out spaces or newlines.  A user is
        automatically in a group if that group was specified in their
        AmiTCP:db/passwd entry and does not need to be added to that group
        in the AmiTCP:db/group file.

   FILES
        AmiTCP:db/group

   SEE ALSO
        passwd

   HISTORY
        A group file format appeared in Version 6 AT&T UNIX.

****************************************************************************
*/

/* 
 * Parse the group entry
 */
static struct Ent *ParseGroup(BASE, register UBYTE *p)
{
  int l;
  UBYTE *np, *name, *pass, *id_s, *memlist, **members;
  ULONG nmembers;
  LONG gid, txtlen = 0;

  struct GroupEnt *ge;

  np = p;

  name = strsep(&np, "|\n");
  pass = strsep(&np, "|\n");
  id_s = strsep(&np, "|\n");
  memlist = np;
  if ((l = StrToLong(id_s, &gid)) <= 0 || id_s[l] != '\0')
    return NULL;
  for (nmembers = 1; (p = strsep(&np, ", \n")) && np;) {
    if (*p) nmembers++;
    else txtlen--;
    if (*np == '\0')
      break;
  }

  assert(np != NULL);

  if (!name || !pass || !id_s || !np)
    return NULL;

  txtlen += np - name - (memlist - id_s);
  ge = AllocVec(sizeof(*ge) + nmembers * sizeof(UBYTE*) + txtlen, MEMF_CLEAR);
  if (!ge)
    return NULL;

  ge->ge_node.ln_Type = ENT_GROUP;

  ge->ge_nmembers = nmembers;
  ge->ge_tlen = txtlen;

  members = (UBYTE **)(ge + 1);
  p = (UBYTE *)(members + nmembers);

  /* Copy name */
  p = stpcopy(ge->ge_group->gr_name = p, name);
  p = stpcopy(ge->ge_group->gr_passwd = p, pass);

  ge->ge_group->gr_gid = gid;
  ge->ge_group->gr_mem = members;
  
  /* Copy the memberlist */
  while (nmembers > 1) {
    if (*memlist) {
      nmembers--;
      *members++ = p;
      while (*p++ = *memlist++)
	;
    } else {
      memlist++;
    }
  }
  *members = NULL;

  assert(p == ge->ge_group->gr_name + txtlen);

  return (struct Ent *)ge;
}

/*
 * Print out an group entry
 */
static int PrintGroup(BASE, BPTR file, struct Ent *e)
{
  struct GroupEnt *ge = (struct GroupEnt *)e;
  UBYTE **member = ge->ge_group->gr_mem;
  UBYTE *fmt = "%s";

  VFPrintf(file, "%s|%s|%ld|", (LONG*)ge->ge_group);
  while (*member) {
    FPrintf(file, fmt, *member++);
    fmt = ",%s";
  }
  FPutC(file, '\n');

  return 0;
}

/*
 * Execute NI_MEMBERS command
 */
static void GetGroups(BASE, struct NetInfoReq *req, struct NetInfoMap *nim)
{
  int maxgroups = req->io_Length / sizeof(LONG);
  LONG *groups = (LONG *)req->io_Data;
  STRPTR uname = (STRPTR)req->io_Offset;
  register int ngroups = 0;
  register struct GroupEnt *ge;
  register int i;
  BYTE retval = 0;

  ge = (struct GroupEnt *)InternalSetEnts(nid, nim);

  while (ge = (struct GroupEnt *)GetNextEnt((struct Ent *)ge)) {
    for (i = 0; ge->ge_group->gr_mem[i]; i++)
      if (strcmp(ge->ge_group->gr_mem[i], uname) == 0) {
	if (ngroups == maxgroups) {
	  retval = NIERR_TOOSMALL;
	  goto toomany;
	}
	groups[ngroups++] = ge->ge_group->gr_gid;
      }
  }

 toomany:
  InternalEndEnts(nid, nim);

  req->io_Actual = ngroups * sizeof(LONG);
  req->io_Error = retval;
  TermIO(req);
}
