/* $Id: authclient.C,v 1.52 2001/08/20 22:19:24 dm Exp $ */

/*
 *
 * Copyright (C) 1999 Michael Kaminsky (kaminsky@lcs.mit.edu)
 * Copyright (C) 1999 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 "authserv.h"
#include "rxx.h"
#if HAVE_GETSPNAM
#include <shadow.h>
#endif /* HAVE_GETSPNAM */

extern "C" char *crypt (const char *, const char *);

authclient::authclient (ptr<axprt_stream> _x, const authunix_parms *aup)
  : x (_x), authid_valid (false)
{
  if ((unixauth = aup))
    uid = aup->aup_uid;

  authsrv = asrv::alloc (x, sfsauth_program_1,
			 wrap (this, &authclient::dispatch));
  sfssrv = asrv::alloc (x, sfs_program_1,
			wrap (this, &authclient::dispatch));
}

void
authclient::dologin (svccb *sbp)
{
  sfs_loginarg *larg = sbp->template getarg<sfs_loginarg> ();

  sfsauth_loginres res (SFSLOGIN_BAD);
  sfs_autharg aarg;

  if (bytes2xdr (aarg, larg->certificate)) {
    switch (aarg.type) {
    case SFS_NOAUTH:
      res.set_status (SFSLOGIN_OK);
      bzero (&res.resok->authid, sizeof (res.resok->authid));
      res.resok->seqno = larg->seqno;
      res.resok->cred.set_type (SFS_NOCRED);
      break;
    case SFS_AUTHREQ:
      {
	// Check the signature of signed_req with usrkey
	sfs_signed_authreq authreq;
	rabin_pub usrkey (aarg.req->usrkey);
	str msg = usrkey.verify_r (aarg.req->signed_req, sizeof (authreq));
	authentry ae;
	ae.pubkey = "0x" << usrkey.n.getstr (16);

	if (msg && str2xdr (authreq, msg)
	    && authreq.type == SFS_SIGNED_AUTHREQ
	    && authreq.seqno == larg->seqno
	    && authlookup (&ae)) {
	  res.set_status (SFSLOGIN_OK);
	  res.resok->cred.set_type (SFS_UNIXCRED);
	  if (unixlookup (&ae, res.resok->cred.unixcred.addr ())) {
	    res.resok->authid = authreq.authid;
	    res.resok->seqno = authreq.seqno;
	  }
	  else
	    res.set_status (SFSLOGIN_BAD);
	}
	break;
      }
    default:
      res.set_status (SFSLOGIN_BAD);
      break;
    }
  }
  sbp->reply (&res);
}

void
authclient::doregister (svccb *sbp)
{
  sfsauth_registerarg *arg = sbp->template getarg<sfsauth_registerarg> ();
  str2wstr (arg->msg.password);

  /* Make sure that we talking over the unix domain socket? */
  if (!unixauth) {
    sbp->replyref (SFSAUTH_NOTSOCK);
    return;
  }
  if (!accepts_reg) {
    sbp->replyref (SFSAUTH_NOCHANGES);
    return;
  }

  rabin_pub usrkey (arg->msg.pubkey);
  str rawmsg = xdr2str (arg->msg, true);
  if (!rawmsg || arg->msg.type != SFS_AUTHREGISTER
      || !usrkey.verify (rawmsg, arg->sig)) {
    sbp->replyref (SFSAUTH_BADSIGNATURE);
    return;
  }
  rawmsg = NULL;

  struct passwd *pe;
  if ((pe = getpwnam (arg->msg.username)) == NULL) {
    sbp->replyref (SFSAUTH_BADUSERNAME);
    return;
  }

  /* XXX - In FreeBSD, pw_uid is an int.  Go figure.  This isn't a
   * correct fix.  Maybe uid should be a u_int32_t and pw_uid cast to
   * that. */
  if (uid && implicit_cast<uid_t> (pe->pw_uid) != uid) {
    sbp->replyref (SFSAUTH_WRONGUID);
    return;
  }

  if (!pe->pw_uid) {
    sbp->replyref (SFSAUTH_DENYROOT);
    return;
  }

  if (uid && !validshell (pe->pw_shell)) {
    sbp->replyref (SFSAUTH_BADSHELL);
    return;
  }

  if (uid) {
#if HAVE_GETSPNAM
    if (struct spwd *spe = getspnam (arg->msg.username)) {
      if (strcmp (spe->sp_pwdp,
		  crypt (arg->msg.password.cstr (), spe->sp_pwdp))) {
	sbp->replyref (SFSAUTH_BADPASSWORD);
	return;
      }
    }
    else
#endif /* HAVE_GETSPNAM */
      if (strcmp (pe->pw_passwd, crypt (arg->msg.password.cstr (),
					pe->pw_passwd))) {
	sbp->replyref (SFSAUTH_BADPASSWORD);
	return;
      }
  }
  
  authentry ae;
  ae.keyname = arg->msg.username;

  if (authlookup (&ae)) {
    sbp->replyref (SFSAUTH_USEREXISTS);
    return;
  }

  if (indenyfile (arg->msg.username)) {
    sbp->replyref (SFSAUTH_REGISTER_DENYFILE);
    return;
  }

  ae.pubkey = "0x" << usrkey.n.getstr (16);
  ae.keyname = arg->msg.username;
  ae.privs = arg->msg.username;

  if (arg->msg.srpinfo) {
    ae.srpinfo = arg->msg.srpinfo->info;
    ae.privkey = arg->msg.srpinfo->privkey;
  }

  if (!authadd (&ae)) {
    sbp->replyref (SFSAUTH_FAILED);
    return;
  }

  sbp->replyref (SFSAUTH_OK);
  return;
}

void
authclient::doupdate (svccb *sbp)
{
  sfsauth_updatearg *arg = sbp->template getarg<sfsauth_updatearg> ();

  // Check the signature of signed_req with usrkey
  rabin_pub oldkey (arg->msg.oldkey);
  rabin_pub newkey (arg->msg.newkey);
  str rawupdatereq = xdr2str (arg->msg);

  if (!oldkey.verify (rawupdatereq, arg->osig)
      || !newkey.verify (rawupdatereq, arg->nsig)) {
    sbp->replyref (sfsauth_stat (SFSAUTH_BADSIGNATURE));
    return;
  }

  if (arg->msg.type != SFS_AUTHUPDATE) {
    sbp->replyref (sfsauth_stat (SFSAUTH_PROTOERR));
    return;
  }

  if (!authid_valid || arg->msg.authid != authid) {
    sbp->replyref (sfsauth_stat (SFSAUTH_BADAUTHID));
    return;
  }

  if (!accepts_changes) {
    sbp->replyref (sfsauth_stat (SFSAUTH_NOCHANGES));
    return;
  }

  authentry olde;
  olde.pubkey = "0x" << oldkey.n.getstr (16);

  if (!authlookup (&olde)) {
    sbp->replyref (sfsauth_stat (SFSAUTH_NOTTHERE));
    return;
  }

  if (indenyfile (olde.keyname)) {
    sbp->replyref (sfsauth_stat (SFSAUTH_DENYFILE));
    return;
  }

  authentry newe;
  newe.pubkey = "0x" << newkey.n.getstr (16);
  newe.keyname = olde.keyname; /* disallow changing of privs or name */
  newe.privs   = olde.privs;

  if (arg->msg.srpinfo) {
    newe.srpinfo = arg->msg.srpinfo->info;
    newe.privkey = arg->msg.srpinfo->privkey;
  }

  if (!authupdate (&newe)) {
    sbp->replyref (sfsauth_stat (SFSAUTH_FAILED));
    return;
  }

  sbp->replyref (sfsauth_stat (SFSAUTH_OK));
}

void
authclient::srp_getparms (svccb *sbp)
{
  sfsauth_srpparmsres res(SFSAUTH_OK);
  res.parms->N = srpglobal.N;
  res.parms->g = srpglobal.g;
  sbp->replyref (res);
}

void
authclient::srpinit (svccb *sbp)
{
  sfssrp_init_arg *arg = sbp->template getarg<sfssrp_init_arg> ();
  sfsauth_srpres res (SFSAUTH_OK);

  authentry e;
  e.keyname = arg->username;
  if (authid_valid && authlookup (&e)
      && srpserv.init (res.msg, &arg->msg, authid,
		       e.keyname, e.srpinfo) == SRP_NEXT)
    protected_privkey = e.privkey;
  else
    res.set_status (SFSAUTH_FAILED);
  sbp->replyref (res);
}

void
authclient::dosrp (svccb *sbp)
{
  sfssrp_bytes *arg = sbp->template getarg<sfssrp_bytes> ();
  sfsauth_srpres res (SFSAUTH_OK);

  switch (srpserv.next (res.msg, arg)) {
  case SRP_NEXT:
    sbp->replyref (res);
    break;
  case SRP_LAST:
    sbp->replyref (res);
    privkey = protected_privkey;
    break;
  default:
    res.set_status (SFSAUTH_FAILED);
    sbp->replyref (res);
    break;
  }
}

void
authclient::dofetch (svccb *sbp)
{
  sfsauth_fetchres res (SFSAUTH_FAILED);
  if (privkey) {
    res.set_status (SFSAUTH_OK);
    res.resok->privkey = privkey;
    res.resok->hostid = hostid;
  }
  sbp->replyref (res);
}

void
authclient::dispatch (svccb *sbp)
{
  if (!sbp) {
    delete this;
    return;
  }

  switch (sbp->prog ()) {
  case SFS_PROGRAM:
    switch (sbp->proc ()) {
    case SFSPROC_NULL:
      sbp->reply (NULL);
      return;
    case SFSPROC_CONNECT:
      {
	ci = *sbp->template getarg<sfs_connectarg> ();
        rnd.getbytes (charge.target.base (), charge.target.size ());
	charge.bitcost =  sfs_hashcost;
	sfs_connectres res (SFS_OK);
	res.reply->servinfo = servinfo;
        res.reply->charge = charge;
	sbp->reply (&res);
	break;
      }
    case SFSPROC_ENCRYPT:
      if (!unixauth) {
	sfs_hash sessid;
	sfs_server_crypt (sbp, authsrvkey, ci, servinfo, &sessid, charge);
	sfs_get_authid (&authid, SFS_AUTHSERV, servinfo.host.hostname,
			&hostid, &sessid);
	authid_valid = true;
      }
      else {
	/* XXX - sfs_server_crypt would panic, as transport not axprt_crypt */
	sbp->reject (PROC_UNAVAIL);
      }
      break;
    default:
      sbp->reject (PROC_UNAVAIL);
      break;
    }
    break;
  case SFSAUTH_PROGRAM:
    switch (sbp->proc ()) {
    case SFSAUTHPROC_NULL:
      sbp->reply (NULL);
      break;
    case SFSAUTHPROC_LOGIN:
      dologin (sbp);
      break;
    case SFSAUTHPROC_REGISTER:
      doregister (sbp);
      break;
    case SFSAUTHPROC_UPDATE:
      doupdate (sbp);
      break;
    case SFSAUTHPROC_SRP_GETPARAMS:
      srp_getparms (sbp);
      break;
    case SFSAUTHPROC_SRP_INIT:
      srpinit (sbp);
      break;
    case SFSAUTHPROC_SRP_MORE:
      dosrp (sbp);
      break;
    case SFSAUTHPROC_FETCH:
      dofetch (sbp);
      break;
    default:
      sbp->reject (PROC_UNAVAIL);
      break;
    }
    break;
  default:
    sbp->reject (PROG_UNAVAIL);
    break;
  }
}
