#include "kauth.h"

RCSID("$Id: rkinit.c,v 1.7 1996/05/12 18:45:43 joda Exp $");

extern char *prog;

static struct in_addr *
getalladdrs (char *hostname, unsigned *count)
{
     struct hostent *hostent;
     struct in_addr **h;
     struct in_addr *addr;
     unsigned naddr;
     unsigned maxaddr;

     hostent = gethostbyname (hostname);
     if (hostent == NULL) {
	  fprintf (stderr, "%s: gethostbyname '%s' failed: ",
		   prog, hostname);
#ifdef HAVE_HERROR
	  herror(NULL);
#endif /* HAVE_HERROR */
	  return NULL;
     }
     maxaddr = 1;
     naddr = 0;
     addr = malloc(sizeof(*addr) * maxaddr);
     if (addr == NULL) {
	  fprintf (stderr, "%s: out of memory\n", prog);
	  return NULL;
     }
     for (h = (struct in_addr **)(hostent->h_addr_list);
	  *h != NULL;
	  h++) {
	  if (naddr >= maxaddr) {
	       maxaddr *= 2;
	       addr = realloc (addr, sizeof(*addr) * maxaddr);
	       if (addr == NULL) {
		    fprintf (stderr, "%s: out of memory\n", prog);
		    return NULL;
	       }
	  }
	  addr[naddr++] = **h;
     }
     addr = realloc (addr, sizeof(*addr) * naddr);
     if (addr == NULL) {
	  fprintf (stderr, "%s: out of memory\n", prog);
	  return NULL;
     }
     *count = naddr;
     return addr;
}

static int
doit_host (char *name, char *inst, char *realm, int lifetime,
	   char *locuser, char *tktfile, des_cblock *key,
	   int s, char *hostname)
{
     char buf[BUFSIZ];
     int inlen;
     KTEXT_ST text;
     CREDENTIALS cred;
     MSG_DAT msg;
     int status;
     des_key_schedule schedule;
     struct sockaddr_in thisaddr, thataddr;
     int addrlen;
     void *ret;

     addrlen = sizeof(thisaddr);
     if (getsockname (s, (struct sockaddr *)&thisaddr, &addrlen) < 0 ||
	 addrlen != sizeof(thisaddr)) {
	  fprintf (stderr, "%s: getsockname(%s) failed: %s\n",
		   prog, hostname, strerror(errno));
	  return 1;
     }
     addrlen = sizeof(thataddr);
     if (getpeername (s, (struct sockaddr *)&thataddr, &addrlen) < 0 ||
	 addrlen != sizeof(thataddr)) {
	  fprintf (stderr, "%s: getpeername(%s) failed: %s\n",
		   prog, hostname, strerror(errno));
	  return 1;
     }

     status = krb_sendauth (KOPT_DO_MUTUAL, s, &text, "rcmd",
			    hostname, krb_realmofhost (hostname),
			    getpid(), &msg, &cred, schedule,
			    &thisaddr, &thataddr, "RKINIT.0");
     if (status != KSUCCESS) {
	  fprintf (stderr, "%s: %s: %s\n", prog, hostname,
		   krb_get_err_text(status));
	  return 1;
     }
     inlen = pack_args (buf, name, inst, realm, lifetime, locuser, tktfile);

     if (write_encrypted(s, buf, inlen, schedule, &cred.session,
			 &thisaddr, &thataddr) < 0) {
	  fprintf (stderr, "%s: write to %s failed: %s\n",
		   prog, hostname, strerror(errno));
	  return 1;
     }

     inlen = read_encrypted (s, buf, sizeof(buf), &ret, schedule,
			     &cred.session, &thataddr, &thisaddr);
     if (inlen < 0) {
	  fprintf (stderr, "%s: read from %s failed: %s\n",
		   prog, hostname, strerror(errno));
	  return 1;
     }

     if (strncmp(ret, "ok", inlen) != 0) {
	  fprintf (stderr, "%s: error from %s: %.*s\n",
		   prog, hostname, inlen, (char *)ret);
	  return 1;
     }

     inlen = read_encrypted (s, buf, sizeof(buf), &ret, schedule,
			     &cred.session, &thataddr, &thisaddr);
     
     {
	  des_key_schedule key_s;

	  des_key_sched(key, key_s);
	  des_pcbc_encrypt(ret, ret, inlen, key_s, key, DES_DECRYPT);
	  memset(key_s, 0, sizeof(key_s));
     }
     write_encrypted (s, ret, inlen, schedule, &cred.session,
		      &thisaddr, &thataddr);

     inlen = read_encrypted (s, buf, sizeof(buf), &ret, schedule,
			     &cred.session, &thataddr, &thisaddr);
     if (inlen < 0) {
	  fprintf (stderr, "%s: read from %s failed: %s\n",
		   prog, hostname, strerror(errno));
	  return 1;
     }

     if (strncmp(ret, "ok", inlen) != 0) {
	  fprintf (stderr, "%s: error from %s: %.*s\n",
		   prog, hostname, inlen, (char *)ret);
	  return 1;
     }
     return 0;
}

int
rkinit (char *name, char *inst, char *realm, int lifetime,
	char *locuser, char *tktfile, des_cblock *key, char *hostname)
{
     struct in_addr *addr;
     unsigned naddr;
     unsigned i;
     int port;
     int success;

     addr = getalladdrs (hostname, &naddr);
     if (addr == NULL)
	  return 1;
     port = k_getportbyname ("kauth", "tcp", htons(2110));
     success = 0;
     for (i = 0; !success && i < naddr; ++i) {
	  struct sockaddr_in a;
	  int s;

	  memset(&a, 0, sizeof(a));
	  a.sin_family = AF_INET;
	  a.sin_port   = port;
	  a.sin_addr   = addr[i];

	  s = socket (AF_INET, SOCK_STREAM, 0);
	  if (s < 0) {
	       fprintf (stderr, "%s: Cannot create socket\n", prog);
	       return 1;
	  }
	  if (connect(s, (struct sockaddr *)&a, sizeof(a)) < 0) {
	       fprintf (stderr, "%s: Cannot connect to %s\n",
			prog, hostname);
	       continue;
	  }

	  success = success || !doit_host (name, inst, realm, lifetime,
					   locuser, tktfile, key,
					   s, hostname);
	  close (s);
     }
     return !success;
}
