/* $Id: sfsclientauth.C,v 1.4 2001/03/16 03:53:58 dm Exp $ */

/*
 *
 * Copyright (C) 1999, 2000 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
 *
 */

#include "crypt.h"
#include "sfsclient.h"
#include "sfscd_prot.h"

ptr<rabin_priv> sfsserver_auth::key;
timecb_t *sfsserver_auth::keytmo;

void
sfsserver_auth::keyexpire ()
{
  keytmo = NULL;
  keygen ();
}

void
sfsserver_auth::keygen ()
{
  key = New refcounted<rabin_priv> (rabin_keygen (sfs_pubkeysize));
  if (!keytmo)
    keytmo = delaycb (3536 + (rnd.getword () & 0x3f), 0, wrap (&keyexpire));
}

void
sfsserver_auth::crypt (sfs_connectok cres, sfsserver::crypt_cb cb)
{
  if (!key)
    keygen ();
  sfs_client_crypt (sfsc, key, carg, cres, cb, xc);
}

bool
sfsserver_auth::authok (nfscall *nc)
{
  sfs_aid aid = nc->getaid ();
  if (sfs_specaid (aid) || authnos[aid])
    return true;
  ptr<userauth> uap = authpending[aid];
  if (!uap)
    uap = New refcounted<userauth> (aid, mkref (this));
  uap->pushreq (nc);
  return false;
}

void
sfsserver_auth::flushstate ()
{
  seqno = 0;
  authnos.clear ();
  authpending.clear ();
}

void
sfsserver_auth::authclear (sfs_aid aid)
{
  if (AUTH *a = authnos[aid]) {
    if (sfsc) {
      u_int32_t authno = authuint_getval (a);
      sfsc->call (SFSPROC_LOGOUT, &authno, NULL, aclnt_cb_null);
    }
    authnos.remove (aid);
  }
  else if (ptr<userauth> uap = authpending[aid]) {
    authpending.remove (aid);
    uap->abort ();
  }
}

sfsserver_auth::userauth::userauth (sfs_aid a, const ref<sfsserver_auth> &s)
  : aid (a), sp (s), cbase (NULL), aborted (false), ntries (0)
{
  sp->authpending.insert (aid, mkref (this));
  tmo = delaycb (3, 0, wrap (this, &sfsserver_auth::userauth::timeout));
  sendreq ();
}

sfsserver_auth::userauth::~userauth ()
{
  abort ();
}

void
sfsserver_auth::userauth::timeout ()
{
  tmo = NULL;
  while (!ncvec.empty ())
    ncvec.pop_front ()->error (NFS3ERR_JUKEBOX);
}

void
sfsserver_auth::userauth::pushreq (nfscall *nc)
{
  if (tmo)
    ncvec.push_back (nc);
  else
    nc->error (NFS3ERR_JUKEBOX);
}

void
sfsserver_auth::userauth::abort ()
{
  if (!aborted) {
    aborted = true;
    if (tmo) {
      timecb_remove (tmo);
      tmo = NULL;
    }
    if (cbase) {
      cbase->cancel ();
      cbase = NULL;
    }
    while (!ncvec.empty ())
      sp->getnfscall (ncvec.pop_front ());
  }
}

void
sfsserver_auth::userauth::sendreq ()
{
  if (!ntries)
    seqno = ++sp->seqno;
  sfscd_agentreq_arg arg;
  arg.aid = aid;
  arg.agentreq.set_type (AGENTCB_AUTHINIT);
  arg.agentreq.init->ntries = ntries;
  arg.agentreq.init->requestor = "";
  arg.agentreq.init->authinfo = sp->authinfo;
  arg.agentreq.init->seqno = seqno;
  cbase = sp->prog.cdc->call (SFSCDCBPROC_AGENTREQ, &arg, &ares,
			      wrap (this, &sfsserver_auth::userauth::aresult));
}

void
sfsserver_auth::userauth::aresult (clnt_stat err)
{
  cbase = NULL;
  if (err) {
    warn << "sfscd: " << err << "\n";
    finish (0);
  }
  else if (!ares.authenticate)
    finish (0);
  else {
    sfs_loginarg arg;
    arg.seqno = seqno;
    arg.certificate = *ares.certificate;
    sp->sfsc->call (SFSPROC_LOGIN, &arg, &sres,
		    wrap (mkref (this),
			  &sfsserver_auth::userauth::sresult));
  }
}

void
sfsserver_auth::userauth::sresult (clnt_stat err)
{
  cbase = NULL;
  if (err)
    finish (0);
  else if (aborted) {
    if (sres.status == SFSLOGIN_OK && sp->x && !sp->x->ateof () && sp->sfsc)
      sp->sfsc->call (SFSPROC_LOGOUT, sres.authno.addr (), NULL,
		      aclnt_cb_null);
  }
  else
    switch (sres.status) {
    case SFSLOGIN_OK:
      finish (*sres.authno);
      break;
    case SFSLOGIN_MORE:
      {
	sfscd_agentreq_arg arg;
	arg.aid = aid;
	arg.agentreq.set_type (AGENTCB_AUTHMORE);
	arg.agentreq.more->authinfo = sp->authinfo;
	arg.agentreq.more->seqno = seqno;
	arg.agentreq.more->challenge = *sres.resmore;
	cbase = sp->prog.cdc->call (SFSCDCBPROC_AGENTREQ, &arg, &ares,
				    wrap (this,
					  &sfsserver_auth::userauth::aresult));
	break;
      }
    case SFSLOGIN_BAD:
      ntries++;
      sendreq ();
      break;
    case SFSLOGIN_ALLBAD:
      finish (0);
      break;
    default:
      warn << "userauth: bad status in loginres!\n";
      finish (0);
    }
}

void
sfsserver_auth::userauth::finish (u_int32_t authno)
{
  assert (!aborted);
  auto_auth aa (authuint_create (authno));
  sp->authnos.insert (aid, aa);
  sp->authpending.remove (aid);
}
