// -*-c++-*-
/* $Id: auth2str.C,v 1.2 2001/11/19 06:02:03 dm Exp $ */

/*
 *
 * Copyright (C) 2001 David Mazieres (dm@uun.org)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

/*
 * The routines in this file transform an authentry from
 * authdb_types.x into a string and vice versa.  The strings have the
 * following formats.  For users:
 *
 *   name:version:audit-info:U:owner:pubkey:privs:pwauth:privkey
 *
 * Here privs is a semicolon-delimited list of the following:
 *
 *   admin - meaning administrative privileges on the database
 *   login=LOGNAME - privileges correspond to local user LOGNAME
 *   ids=UID,GID[,GID,...] - privileges correspond to user and group ids
 *
 * For groups:
 *
 *   name:version:audit-info:G:owners:gid:members
 */

#include "authdb.h"
#include "rxx.h"
#include "serial.h"
#include "parseopt.h"

#define AUTHNAME "[A-Za-z/]\\w{0,31}"
#define NOBAD "[^\\x00-\\x1f\\x7f-\\xff:]*"


static rxx namerx (AUTHNAME);
static rxx commaplus (",+");
static rxx srprx ("SRP,N=(0x[\\da-f]+),g=(0x[\\da-f]+),"
		  "s=(\\d+\\$[A-Za-z0-9+/]+={0,2}\\$[\\w\\.\\-]*),"
		  "v=(0x[\\da-f]+)");
static rxx nobadrx (NOBAD);

static bool
printlist (strbuf &res, const authnamelist &list)
{
  for (u_int i = 0; i < list.size (); i++) {
    if (!namerx.match (list[i]))
      return false;
    if (i)
      res << ",";
    res << list[i];
  }
  return true;
}

static bool
printuser (strbuf &res, const authdb_user &u)
{
  res << ":U::0x" << u.pubkey.getstr (16) << ":";
  bool cred = false;
  for (u_int i = 0; i < u.privs.size (); i++) {
    if (i)
      res << ";";
    switch (u.privs[i].type) {
    case PRIV_ADMIN:
      res << "admin";
      break;
    case PRIV_LOGIN:
      if (cred)
	return false;
      cred = true;
      if (!namerx.match (*u.privs[i].login))
	return false;
      res << "login=" << *u.privs[i].login;
      break;
    case PRIV_IDS:
      if (cred)
	return false;
      cred = true;
      res << "ids=" << u.privs[i].ids->uid << ","
	  << u.privs[i].ids->gid;
      for (u_int j = 0; j < u.privs[i].ids->groups.size (); j++)
	res << "," << u.privs[i].ids->groups[j];
      break;
    default:
      return false;
    }
  }
  res << ":";
  switch (u.pwauth.type) {
  case PWAUTH_NONE:
    break;
  case PWAUTH_SRP:
    {
      str s (u.pwauth.srp->base (), u.pwauth.srp->size ());
      if (!srprx.match (s))
	return false;
      res << s;
      break;
    }
  default:
    return false;
  }
  res << ":" << armor64 (u.privkey.base (), u.privkey.size ());
  return true;
}

str
authent2str (const authentry *ae)
{
  strbuf res;
  if (!namerx.match (ae->name))
    return NULL;
  res << ae->name << ":" << ae->vers << ":";
  str audit (ae->audit.base (), ae->audit.size ());
  if (nobadrx.match (audit))
    res << audit;
  switch (ae->info.type) {
  case AUTHDB_USER:
    if (!printuser (res, *ae->info.user))
      return NULL;
    break;
  case AUTHDB_GROUP:
    res << ":G:";
    if (!printlist (res, ae->info.group->owners))
      return NULL;
    res << ":" << ae->info.group->id << ":";
    if (!printlist (res, ae->info.group->members))
      return NULL;
    break;
  default:
    return NULL;
  }
  return res;
}

static bool
parselist (authnamelist *lp, str txt)
{
  vec<str> list;
  split (&list, commaplus, txt);
  lp->setsize (list.size ());
  for (u_int i = 0; i < list.size (); i++) {
    if (!namerx.match (list[i]))
      return false;
    (*lp)[i] = list[i];
  }
  return true;
}

static bool
parseids (authids *ids, str txt, bool strict)
{
  vec<str> list;
  split (&list, commaplus, txt);
  if (list.size () < 2)
    return false;
  if (list.size () - 2 > ids->groups.maxsize) {
    if (strict)
      return false;
    ids->groups.setsize (ids->groups.maxsize);
  }
  else
    ids->groups.setsize (list.size () - 2);
  if (!convertint (list.pop_front (), &ids->uid)
      || !convertint (list.pop_front (), &ids->gid))
    return false;
  for (u_int i = 0; i < list.size (); i++)
    if (!convertint (list[i], &ids->groups[i]))
      return false;
  return true;
}

static bool
parsepriv (privlist *privs, str txt, bool strict)
{
  static rxx semiplus (";+");
  static rxx loginrx ("login=("AUTHNAME")");
  static rxx idsrx ("ids=([\\d,]+)");

  vec<str> txtv;
  split (&txtv, semiplus, txt);
  vec<privilege> pv;
  bool cred = false;
  for (str *tp = txtv.base (); tp < txtv.lim (); tp++) {
    if (*tp == "admin")
      pv.push_back (privilege (PRIV_ADMIN));
    else if (loginrx.match (*tp)) {
      if (cred)
	return false;
      cred = true;
      pv.push_back (privilege (PRIV_LOGIN));
      *pv.back ().login = loginrx[1];
    }
    else if (idsrx.match (*tp)) {
      if (cred)
	return false;
      cred = true;
      pv.push_back (privilege (PRIV_IDS));
      if (!parseids (pv.back ().ids, idsrx[1], strict))
	return false;
    }
    else if (strict)
      return false;
  }
  return true;
}

static bool
parseuser (authentry *ae, str txt, bool strict)
{
  static rxx userrx ("([^:]+)?:(0x[0-9a-f]+):([^:]*):([^:]+)?"
		     ":([0-9a-zA-Z+/=]*)");
  if (!userrx.match (txt))
    return false;
  if (str owner = userrx[1])
    *ae->info.user->owner.alloc () = owner;
  else
    ae->info.user->owner.clear ();
  ae->info.user->pubkey = userrx[2];

  if (!parsepriv (&ae->info.user->privs, userrx[3], strict))
    return false;

  ae->info.user->pwauth.set_type (PWAUTH_NONE);
  if (str pwa = userrx[4]) {
    if (srprx.match (pwa)) {
      ae->info.user->pwauth.set_type (PWAUTH_SRP);
      *ae->info.user->pwauth.srp = pwa;
    }
    else if (strict)
      return false;
  }
  if (str privkey = dearmor64 (userrx[5]))
    ae->info.user->privkey = privkey;
  else
    return false;
  return true;
}

bool
str2authent (authentry *ae, str txt, bool strict)
{
  static rxx authline ("("AUTHNAME"):(\\d+)?:("NOBAD"):([A-Z]):(.*)\n?");
  static rxx grouprx ("([^:]*):(\\d+):([^:]*)");

  if (!authline.match (txt))
    return false;
  ae->name = authline[1];
  ae->vers = 0;
  if (str v = authline[2])
    convertint (v, &ae->vers);
  ae->audit = authline[3];
  switch (authline[4][0]) {
  case 'U':
    if (!parseuser (ae, authline[5], strict))
      return false;
    break;
  case 'G':
    ae->info.set_type (AUTHDB_GROUP);
    if (!grouprx.match (authline[5])
	|| !convertint (grouprx[2], &ae->info.group->id)
	|| !parselist (&ae->info.group->owners, grouprx[1])
	|| !parselist (&ae->info.group->members, grouprx[3]))
      return false;
    break;
  default:
    return false;
  }
  return true;
}
