
/* $Id: sfsauthmgr.C,v 1.10 2002/10/08 20:05:01 max Exp $ */

/*
 *
 * Copyright (C) 1998, 1999 David Mazieres (dm@uun.org)
 * Copyright (C) 1999, 2000 Michael Kaminsky (kaminsky@lcs.mit.edu)
 *
 * 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
 *
 */

#include "agent.h"
#include "sfsschnorr.h"

// XXX - change this into a configuration parameter.  for now, we'll leave
// it at 10 seconds
#define   SESS_TIMEOUT   10

bool 
auth_sess_mgr::exists (const authmeth *p) 
{ 
  return amgr->exists (p); 
}

void
auth_sess_mgr::remove (authsess *as)
{
  sq.remove (as);
  sh.remove (as);
  num --;
  delete as;
}

void
auth_sess_mgr::insert (authmeth *m, u_int id)
{
  authsess *as;
  if ((as = sh[id])) {
    as->am = m;
    touch (as);
  } else {
    as = New authsess (m, id);
    num ++;
    sq.insert_tail (as);
    sh.insert (as);
    timeout ();
  }
}

void
auth_sess_mgr::timeout (bool cb)
{
  if (!cb && exp_tmo) 
    timecb_remove (exp_tmo);
  exp_tmo = NULL;
  sfs_time now = time (NULL);

  authsess *nas, *as;
  for (as = sq.first; as; as = nas) {
    nas = sq.next (as);
    if (as->expire && as->expire < now) {
      remove (as);
    } else {
      break;
    }
  }
  if (as) {
    exp_tmo = timecb (as->expire, wrap (this, &auth_sess_mgr::timeout, true));
  }
}

// authmethods should be able to exist without an associate authmgr,
// although the applications are very limited. 
bool
authmeth::register_sess(const sfs_authinfo &ai, const sfs_seqno &seqno,
			sfs_hash &h, bool storeit)
{
  if (!amgr) {
    void *b = h.base();
    return sha1_hashxdr (b, ai);
  } else {
    return amgr->asmgr.register_sess (ai, seqno, this, h, storeit);
  }
}
  

bool
auth_sess_mgr::register_sess (const sfs_authinfo &ai, const sfs_seqno &seqno,
			      authmeth *m, sfs_hash &hsh, bool storeit)
{
  u_int id = compute_asid (ai, seqno, hsh);
  if (!id) 
    return false;
  if (storeit)
    insert (m, id);
  return true;
}

u_int
auth_sess_mgr::compute_asid (const sfs_authinfo &ai, const sfs_seqno &seqno, 
			     sfs_hash &h)
{
  void *b = h.base();
  if (!sha1_hashxdr (b, ai)) 
    return 0;
  u_int *p = (u_int *)b;
  u_int ui = p[0];
  ui += seqno;
  return ui;
}

authmeth *
auth_sess_mgr::retrieve (const sfs_authinfo &ai, const sfs_seqno &seqno,
			 sfs_hash &h)
{
  u_int id = compute_asid (ai, seqno, h);
  if (!id)
    return NULL;
  
  authsess *as = sh[id];
  if (!as) 
    return NULL;

  // the authmeth associated might have been remove due to an EOF on the
  // socket or even an sfskey remove
  authmeth *r = as->am;
  if (!exists (r)) {
    delete as;
    return NULL;
  }
  touch (as);
  return r;
}

void
auth_sess_mgr::touch (authsess *as)
{
  as->expire = time (NULL) + SESS_TIMEOUT;
  if (num > 1) {
    sq.remove (as);
    sq.insert_tail (as);
  }
}

authsess::authsess (authmeth *m, u_int i) 
  : am (m), id (i)
{
  expire = time (NULL) + SESS_TIMEOUT;
}

void
authmgr::timeout (bool cb)
{
  if (!cb)
    timecb_remove (exp_tmo);
  exp_tmo = NULL;
  exp_time = 0;
  sfs_time now = time (NULL);
  for (authmeth *k = authmeths.first, *nk; k; k = nk) {
    nk = authmeths.next (k);
    if (k->expire && k->expire <= now) {
      authmeths.remove (k);
      delete k;
    }
    else if (k->expire && (!exp_time || k->expire < exp_time))
      exp_time = k->expire;
  }
  if (exp_time)
    exp_tmo = timecb (exp_time, wrap (this, &authmgr::timeout, true));
}

authmeth *
authmgr::klookup (const sfspub &key)
{
  authmeth *m;
  for (m = authmeths.first; m ; m = m->link.next) {
    if (m->kcmp (key))
      return m;
  }
  return NULL;
}

authmeth *
authmgr::plookup (const int p) 
{
  authmeth *k;
  for (k = authmeths.first; k ; k = k->link.next) {
    if (k->pcmp (p)) 
      return k;
  }
  return NULL;
}

authmeth *
authmgr::clookup (const str &c)
{
  authmeth *k;
  for (k = authmeths.first; k; k = k->link.next)
    if (k->name == c)
      return k;
  return NULL;
}

bool
authmgr::exists (const authmeth *p)
{
  for (authmeth *a = authmeths.first; a; a = a->link.next)
    if (a == p)
      return true;
  return false;
}

authmeth *
authmgr::lookup_by_index (const u_int na)
{
  u_int n = na;
  authmeth *k = authmeths.first;
  while (k && n--)
    k = k->link.next;
  return k;
}

void 
authmgr::remove_all ()
{
  while (authmeth *a = authmeths.first) {
    authmeths.remove (a);
    delete a;
  }
}

void 
authmgr::fill_keylist (sfs_keylist *kl) 
{
  sfs_keylist *klp = kl;

  for (authmeth *a = authmeths.first; a; a = a->link.next) {
    (*klp).alloc ();
    (*klp)->desc = a->get_desc ();
    (*klp)->expire = a->expire;
    (*klp)->name = a->name;
    klp = &(*klp)->next;
  }
}

void
extauth::eofcb (authmgr *a)
{
  eof = true;
  a->remove (this);
  delete this;
}

extauth::extauth (ref<axprt> xx, authmgr *a, extauth_server *s = NULL)
  : x (xx), eas (s)
{
  ac = aclnt::alloc (x, sfsextauth_prog_1);
  ac->seteofcb (wrap (this, &extauth::eofcb, a));
  amgr = a;
  eof = false;
}

extauth::~extauth ()
{
  if (eas) 
    eas->eof ();
}

void
authmgr::authinit (svccb *s)
{
  sfsagent_authinit_arg *aa = s->getarg<sfsagent_authinit_arg> ();

  authmeth *k = lookup_by_index (aa->ntries);
  if (!k || aa->authinfo.type != SFS_AUTHINFO) {
    s->replyref (sfsagent_auth_res (false));
    return;
  }
  ref<sfsagent_auth_res> res = New refcounted<sfsagent_auth_res> (true);
  k->authwarn (aa);
  k->authinit (aa, res, wrap (this, &authmgr::authdone_cb, s, res));
}

void
authmgr::authmore (svccb *s)
{
  sfsagent_authmore_arg *aa = s->getarg<sfsagent_authmore_arg> ();
  sfs_hash dummy;
  authmeth *k = asmgr.retrieve (aa->authinfo, aa->seqno, dummy);
  if (!k || aa->authinfo.type != SFS_AUTHINFO) {
    s->replyref (sfsagent_auth_res (false));
    return;
  }
  ref<sfsagent_auth_res> res = New refcounted<sfsagent_auth_res> (true);
  k->authmore(aa, res, wrap (this, &authmgr::authdone_cb, s, res));
}

void
authmgr::authdone_cb (svccb *sbp, ptr<sfsagent_auth_res> res)
{
  assert (sbp);
  sbp->reply (res);
}

void
extauth::authinit (sfsagent_authinit_arg *aa, 
		   ptr<sfsagent_auth_res> res, cbv cb)
{
  sfsextauth_init arg;
  arg.autharg = *aa;
  arg.name = name;

  sfs_hash dummy;
  if (!register_sess (aa->authinfo, aa->seqno, dummy, true)) {
    warn ("sfsagent::authinit: xdr failed\n");
    res->set_authenticate (false);
    (*cb) () ;
  } else {
    ac->call (SFSEXTAUTH_AUTHINIT, &arg, res, 
	      wrap (this, &extauth::eacb, res, cb, destroyed));
  }
}

void
extauth::authmore (sfsagent_authmore_arg *aa, 
		   ptr<sfsagent_auth_res> res, cbv cb)
{
  sfsextauth_more arg;
  arg.autharg = *aa;
  arg.name = name;
  ac->call (SFSEXTAUTH_AUTHMORE, &arg, res,
	    wrap (this, &extauth::eacb, res, cb, destroyed));
}

void
key::authinit (sfsagent_authinit_arg *aa, 
	       ptr<sfsagent_auth_res> res, cbv cb)
{
  if (vers == 2 || privkey->is_v2 ()) {
    _authinit_v2 (aa, res, cb);
  } else {
    _authinit_v1 (aa, res, cb);
  }
}

// XXX - would like to get rid of this as soon as possible
void
key::_authinit_v1 (sfsagent_authinit_arg *aa, 
		   ptr<sfsagent_auth_res> res, cbv cb)
{

  res->set_authenticate (true);
  sfs_autharg ar (SFS_AUTHREQ);
  sfs_signed_authreq sar;
  str rawsar;

  privkey->export_pubkey (&ar.req->usrkey);
  sar.type = SFS_SIGNED_AUTHREQ;
  sar.seqno = aa->seqno;
  bzero (sar.usrinfo.base (), sar.usrinfo.size ());

  // Last parameter false because current embedded protocol only has
  // one round; if multi-round, we'll pass true.
  bool rc = register_sess (aa->authinfo, aa->seqno, sar.authid, false);

  if (!rc || !(rawsar = xdr2str (sar)) ||
      !privkey->sign_r (&ar.req->signed_req, rawsar) ||
      !xdr2bytes (*(res->certificate), ar)) {
    warn ("sfsagent::authinit: xdr failed\n");
    res->set_authenticate (false);
  }
  
  (*cb) ();
}


void
authmeth::authwarn (sfsagent_authinit_arg *aa) 
{
  warn << name << "!" << aa->requestor << ": @" 
       << aa->authinfo.name  << ","
       << armor32 (str (aa->authinfo.hostid.base (),
			aa->authinfo.hostid.size ()))
       << " (" << implicit_cast<int> (aa->authinfo.service) << ")\n";
}

void
key::_authinit_v2 (sfsagent_authinit_arg *aa, 
		   ptr<sfsagent_auth_res> res, cbv cb)
{

  res->set_authenticate (false);
  ptr<sfs_autharg2> ar = New refcounted<sfs_autharg2> (SFS_AUTHREQ2);
  ar->sigauth->req.type = SFS_SIGNED_AUTHREQ;
  ar->sigauth->req.seqno = aa->seqno;
  sfs_hash &authid = ar->sigauth->req.authid;
  sfsauth2_sigreq sr (SFS_SIGNED_AUTHREQ);
  bzero (authid.base (), authid.size ());
  if (!register_sess (aa->authinfo, aa->seqno, authid, false)) {
    warn << "SHA1 hashing of authinfo failed\n";
    return;
  }
  *sr.authreq = ar->sigauth->req;
  if (!privkey->export_pubkey (&(ar->sigauth->key))) {
    warn << "Cannot get public key\n";
    return;
  }
  privkey->sign (sr, aa->authinfo, 
		 wrap (this, &key::sigcb, ar, res, cb));
}

void
key::sigcb (ptr<sfs_autharg2> ar, ptr<sfsagent_auth_res> res, cbv cb, 
	    str err, ptr<sfs_sig2> sig)
{
  if (err) {
    warn << err << "\n";
    (*cb) ();
    return ;
  }
  assert (sig);
  res->set_authenticate (true);
  ar->sigauth->sig = *sig;

  xdr2bytes (*(res->certificate), *ar);
  (*cb) ();
}

void
key::authmore (sfsagent_authmore_arg *aa, ptr<sfsagent_auth_res> res,
	      cbv cb)
{
  res->set_authenticate (false);
  (*cb) ();
}


// call back after external agent responds
void
extauth::eacb (ptr<sfsagent_auth_res> res, cbv cb, ref<bool> destroyed, 
	       clnt_stat err)
{
  if (*destroyed || err) {
    res->set_authenticate (false);
  }
  (*cb) ();
}

