// -*-c++-*- /* $Id: dbrec2str.C,v 1.2 2002/01/06 19:30:36 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
 *
 */

/*
 * Converts users to/from the following text representation:
 *   USER:name:uid:version:gid:owner:pubkey:privs:srpinfo:privkey:audit
 *
 * Converts groups to/from the following:
 *   GROUP:name:gid:version:owners:members:audit
 */

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

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

static rxx namerx (AUTHNAME);
static rxx colon (":");
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 ("[^"BADCHAR"]*");
static rxx badcharrx ("["BADCHAR"]");
static rxx hexrx ("0x[\\da-fA-F]+");
static rxx decrx ("\\d+");

static bool
printname (strbuf &sb, sfs_idname name)
{
  if (!namerx.match (name))
    return false;
  sb << name;
  return true;
}
static bool
parsename (sfs_idname *name, str s)
{
  if (!namerx.match (s))
    return false;
  *name = s;
  return true;
}

#if 0
static bool
printnum (strbuf &sb, u_int32_t num)
{
  sb.fmt ("%u", num);
  return true;
}
static bool
parsenum (u_int32_t *nump, str s)
{
  return convertint (s, nump);
}
#endif

template<class T, size_t N> static bool
printlist (strbuf &sb, const rpc_vec<T, N> &v, bool (*printfn) (strbuf &, T))
{
  bool first = true;
  for (const T *tp = v.base (); tp < v.lim (); tp++) {
    if (first)
      first = false;
    else
      sb << ",";
    if (!printfn (sb, *tp))
      return false;
  }
  return true;
}

template<class T, size_t N> static bool
parselist (rpc_vec<T, N> *vp, str s, bool (*parsefn) (T *, str))
{
  vec<str> sv;
  split (&sv, commaplus, s);
  if (!sv.empty () && !sv.front ().len ())
    sv.pop_front ();
  if (!sv.empty () && !sv.back ().len ())
    sv.pop_back ();
  vp->setsize (sv.size ());
  for (size_t i = 0; i < vp->size (); i++)
    if (!parsefn (&(*vp)[i], sv[i]))
      return false;
  return true;
}

static bool
userinfo2str (strbuf &sb, const sfsauth_userinfo *ui)
{
  if (!namerx.match (ui->name)
      || (ui->owner && !namerx.match (*ui->owner))
      || !nobadrx.match (ui->privs)
      || badcharrx.search (ui->pwauth)
      || badcharrx.search (ui->audit))
    return false;
  sb << ui->name;
  sb.fmt (":%u:%u:%u:", ui->id, ui->vers, ui->gid);
  if (ui->owner)
    sb << *ui->owner;
  sb << ":0x" << ui->pubkey.getstr (16) << ":"
     << ui->privs << ":";
  sb.tosuio ()->print (ui->pwauth.cstr (), ui->pwauth.len ());
  sb << ":";
  str priv = str2wstr (armor64 (ui->privkey.base (), ui->privkey.size ()));
  sb.tosuio ()->print (priv.cstr (), priv.len ());
  sb << ":" << ui->audit;
  return true;
}

bool
groupinfo2str (strbuf &sb, const sfsauth_groupinfo *gi)
{
  if (!namerx.match (gi->name)
      || badcharrx.search (gi->audit))
    return false;
  sb << gi->name;
  sb.fmt (":%u:%u:", gi->id, gi->vers);
  if (!printlist (sb, gi->owners, printname))
    return false;
  sb << ":";
  if (!printlist (sb, gi->members, printname))
    return false;
  sb << ":" << gi->audit;
  return true;
}

str
authdbrec2str (const sfsauth_dbrec *dbr)
{
  strbuf sb;
  switch (dbr->type) {
  case SFSAUTH_USER:
    sb << "USER:";
    if (userinfo2str (sb, dbr->userinfo))
      return str2wstr (sb);
    return NULL;
  case SFSAUTH_GROUP:
    sb << "GROUP:";
    if (groupinfo2str (sb, dbr->groupinfo))
      return sb;
    return NULL;
  default:
    return NULL;
  }
}

bool
str2userinfo (sfsauth_userinfo *ui, str s)
{
  vec<str> uv;
  if (split (&uv, colon, s, 11) != 10)
    return false;
  str2wstr (uv[7]);
  str2wstr (uv[8]);
  if (!namerx.match (uv[0])
      || !decrx.match (uv[1])
      || !decrx.match (uv[2])
      || !decrx.match (uv[3])
      || (uv[4].len () && !namerx.match (uv[4]))
      || !hexrx.match (uv[5])
      || badcharrx.search (uv[6])
      || badcharrx.search (uv[7])
      || badcharrx.search (uv[9]))
    return false;

  {
    str privkey = dearmor64 (uv[8]);
    if (!privkey)
      return false;
    str2wstr (privkey);
    ui->privkey.setsize (privkey.len ());
    memcpy (ui->privkey.base (), privkey, ui->privkey.size ());
  }

  ui->name = uv[0];
  if (!convertint (uv[1], &ui->id)
      || !convertint (uv[2], &ui->vers)
      || !convertint (uv[3], &ui->gid))
    return false;
  if (uv[4].len ())
    *ui->owner.alloc () = uv[4];
  else
    ui->owner.clear ();
  ui->pubkey = uv[5];
  ui->privs = uv[6];
  ui->pwauth = uv[7];
  ui->audit = uv[9];
  return true;
}

bool
str2groupinfo (sfsauth_groupinfo *gi, str s)
{
  vec<str> gv;
  if (split (&gv, colon, s, 7) != 6)
    return false;
  if (!namerx.match (gv[0])
      || badcharrx.search (gv[5]))
    return false;
  gi->name = gv[0];
  if (!convertint (gv[1], &gi->id)
      || !convertint (gv[2], &gi->vers)
      || !parselist (&gi->owners, gv[3], parsename)
      || !parselist (&gi->members, gv[4], parsename))
    return false;
  gi->audit = gv[5];
  return true;
}

bool
str2authdbrec (sfsauth_dbrec *dbr, str s)
{
  static rxx _userrx ("^USER:(.*)$");
  rxx userrx (_userrx);
  static rxx grouprx ("^GROUP:(.*)$");
  if (userrx.match (s)) {
    dbr->set_type (SFSAUTH_USER);
    return str2userinfo (dbr->userinfo, str2wstr (userrx[1]));
  }
  else if (grouprx.match (s)) {
    dbr->set_type (SFSAUTH_GROUP);
    return str2groupinfo (dbr->groupinfo, grouprx[1]);
  }
  else
    return false;
}
