/* $Id: sfskeyupdate.C,v 1.30 2001/01/13 19:46:09 dm Exp $ */

/*
 *
 * 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 "sfskey.h"
#include "srp.h"

struct updatecmd {
  bool opt_nosrp;
  vec<str> authservers;
  str oldkeyname;
  str newkeyname;
  ptr<sfskey> oldkey;
  ptr<sfskey> newkey;
  sfsauth_srpparmsres srpparms;
  u_int npending;
  bool errors;

  updatecmd () : opt_nosrp (false), errors (false) { 
    srpparms.set_status (SFSAUTH_FAILED); 
  }

  void setsrp () {
    if (opt_nosrp) {
      srpparms.set_status (SFSAUTH_FAILED);
      return;
    }
    if (srpparms.status != SFSAUTH_OK) {
      if (str srpfile = sfsconst_etcfile ("sfs_srp_parms"))
	if (str parms = file2str (srpfile)) {
	  srpparms.set_status (SFSAUTH_OK);
	  if (!import_srp_params (parms, &srpparms.parms->N, 
				  &srpparms.parms->g))
	    fatal ("%s: cannot parse srp parameters\n", srpfile.cstr ());
	}
	else
	  fatal ("%s: %m\n", srpfile.cstr ());
    }
    if (!srp_base::checkparam (srpparms.parms->N, srpparms.parms->g))
      fatal ("invalid SRP parameters!\n");
  }

  void done (bool ok) {
    if (!ok)
      errors = true;
    if (!--npending)
      exit (errors);
  }

  bool setuparg (sfsauth_updatearg *arg, ref<const sfscon> sc) const {
    arg->msg.type = SFS_AUTHUPDATE;
    arg->msg.authid = sc->authid;
    arg->msg.oldkey = oldkey->key->n;
    arg->msg.newkey = newkey->key->n;
    if (srpparms.status == SFSAUTH_OK) {
      srp_client srpc;
      arg->msg.srpinfo.alloc ();
      arg->msg.srpinfo->info = srpc.create (srpparms.parms->N, 
					    srpparms.parms->g,
					    newkey->pwd,
					    sc->servinfo.host.hostname,
					    newkey->cost, 0);
      if (!arg->msg.srpinfo->info) {
	warn << sc->servinfo.host.hostname << ": could not create SRP info\n";
	return false;
      }
      arg->msg.srpinfo->privkey = export_rabin_encrypt_sec (*newkey->key,
							    &srpc.eksb);
      if (!arg->msg.srpinfo->privkey) {
	warn << sc->servinfo.host.hostname << ": encrypt private key\n";
	return false;
      }
    }
    else
      arg->msg.srpinfo.clear ();
    str rawmsg (xdr2str (arg->msg, true));
    if (!rawmsg) {
      warn << sc->servinfo.host.hostname << ": could not marshal update arg\n";
      return false;
    }
    arg->osig = oldkey->key->sign (rawmsg);
    arg->nsig = newkey->key->sign (rawmsg);
    return true;
  }

  void start () { fetchkey (newkeyname, wrap (this, &updatecmd::getnewkey)); }

  void getnewkey (ptr<sfskey> sk, str err, ptr<sfscon> sc) {
    if (!sk)
      fatal << err << "\n";
    newkey = sk;
    if (!opt_nosrp && !newkey->pwd)
      fatal << "New key must have passphrase when using SRP\n";
    if (!opt_nosrp && srpparms.status != SFSAUTH_OK && sc && sc->hostid_valid) {
      ref<aclnt> c (aclnt::alloc (sc->x, sfsauth_program_1));
      c->scall (SFSAUTHPROC_SRP_GETPARAMS, NULL, &srpparms);
    }
    if (oldkeyname == newkeyname)
      getoldkey (sk, err, sc);
    else
      fetchkey (oldkeyname, wrap (this, &updatecmd::getoldkey));
  }

  void getoldkey (ptr<sfskey> sk, str err, ptr<sfscon> sc) {
    if (!sk)
      fatal << err << "\n";
    oldkey = sk;
    if (!opt_nosrp && srpparms.status != SFSAUTH_OK && sc && sc->hostid_valid) {
      ref<aclnt> c (aclnt::alloc (sc->x, sfsauth_program_1));
      c->scall (SFSAUTHPROC_SRP_GETPARAMS, NULL, &srpparms);
    }
    setsrp ();

    if (authservers.empty ()) {
      if (!sc)
	fatal ("must specify authserver -a if SRP fails on old key\n");
      npending = 1;
      getcon (sc->path, sc, NULL);
    }
    else {
      npending = authservers.size ();
      for (const str *sp = authservers.base (); sp < authservers.lim (); sp++)
	sfs_connect_path (*sp, SFS_AUTHSERV,
			  wrap (this, &updatecmd::getcon, *sp));
    }
  }

  void getcon (str path, ptr<sfscon> sc, str err) {
    if (!sc) {
      if (path == "-")
	path = "localhost";
      warnx << path << ": FAILED (" << err << ")\n";
      done (false);
      return;
    }
    if (path == "-")
      path = sc->path;
    sfsauth_updatearg arg;
    if (!setuparg (&arg, sc)) {
      warnx << path << ": FAILED (could not set up args)\n";
      done (false);
      return;
    }
    sfsauth_stat *resp = New sfsauth_stat;
    ref<aclnt> c (aclnt::alloc (sc->x, sfsauth_program_1));
    c->call (SFSAUTHPROC_UPDATE, &arg, resp,
	     wrap (this, &updatecmd::getres, path, resp));
  }

  void getres (str path, sfsauth_stat *resp, clnt_stat err) {
    sfsauth_stat stat = *resp;
    delete resp;
    if (err) {
      warnx << path << ": FAILED (" << err << ")\n";
      done (false);
    }
    else if (stat != SFSAUTH_OK) {
      // XXX
      warnx << path << ": FAILED (update err " << int (stat) << ")\n";
      done (false);
    }
    else {
      warnx << path << ": updated\n";
      done (true);
    }
  }
};

void
sfskey_update (int argc, char **argv)
{
  updatecmd *uc = New updatecmd ();

  int ch;
  while ((ch = getopt (argc, argv, "Ss:a:")) != -1)
    switch (ch) {
    case 'S':
      uc->opt_nosrp = true;
      if (uc->srpparms.status == SFSAUTH_OK)
	usage ();
      break;
    case 's':
      {
	if (uc->opt_nosrp)
	  usage ();
	if (str parms = file2str (optarg)) {
	  uc->srpparms.set_status (SFSAUTH_OK);
	  if (!import_srp_params (parms, &uc->srpparms.parms->N, 
				  &uc->srpparms.parms->g))
	    fatal ("%s: cannot parse srp parameters\n", optarg);
	}
	else
	  fatal ("%s: %m\n", optarg);
	break;
      }
    case 'a':
      uc->authservers.push_back (optarg);
      break;
    default:
      usage ();
      break;
    }

  if (optind + 2 < argc || optind >= argc)
    usage ();
  uc->oldkeyname = argv[optind++];
  if (optind < argc)
    uc->newkeyname = argv[optind];
  else
    uc->newkeyname = defkey ();

  if (uc->authservers.empty () && !isremote (uc->oldkeyname))
    fatal ("must specify authserver -a if old key not via SRP\n");

  uc->start ();
}
