/* 
 *
 * Copyright 1987 by the Massachusetts Institute of Technology.
 *
 * For copying and distribution information,
 * please see the file <mit-copyright.h>.
 *
 * $Revision: 1.4 $
 * $Date: 1997/01/26 04:03:37 $
 * $State: Exp $
 * $Source: /afs/sipb.mit.edu/project/sipb-athena/repository/src/kerberos/slave/kpropd.c,v $
 * $Author: svalente $
 * $Locker:  $
 *
 * $Log: kpropd.c,v $
 * Revision 1.4  1997/01/26 04:03:37  svalente
 * Use <string.h>, <stdlib.h>, size_t.
 *
 * Revision 1.3  1996/02/03 05:58:40  ghudson
 * Get the sense of the #if right for strerror().
 *
 * Revision 1.2  1996/02/03 05:56:40  ghudson
 * Do something sane with sys_errlist.
 *
 * Revision 1.1  1996/02/03 05:52:33  ghudson
 * Initial revision
 *
 * Revision 1.13  1993/10/27  19:18:06  eichin
 * better diagnostic log message for truncated file
 *
 * Revision 1.12  1993/10/27  16:51:37  eichin
 * hppa hpux appears to return the size of the sockaddr_in, instead of
 * the size of the actual address. This breaks gethostbyaddr, which later
 * needs a length. Work around it with hpux-specific check and assignment.
 *
 * Revision 1.11  1993/10/27  16:24:49  eichin
 * but noone declares h_errno
 *
 * Revision 1.10  1993/10/27  16:05:29  eichin
 * log h_errno after gethostbyaddr fails
 *
 * Revision 1.9  1993/03/30  22:28:16  eichin
 * fix uninitialized variable that just started breaking kpropd
 *
 * Revision 1.8  1993/03/08  18:52:22  eichin
 * add include of krbports.h, and fallback to symbolic names
 *
 * Revision 1.7  1993/01/20  19:41:28  eichin
 * if getservbyname fails, use hard-coded port number -- *don't* fail.
 *
 * Revision 1.6  1993/01/07  23:04:15  eichin
 * change bzero, bcopy, bcmp to memset, memcpy, memcmp.
 * (bcopy is never used in an overlap case, so we don't need memmove.)
 *
 * Revision 1.5  1992/12/23  05:00:35  eichin
 * Use NEED_SYS_FCNTL_H
 *
 * Revision 1.4  1992/12/09  01:08:10  eichin
 * Use emul_flock under POSIX
 *
 * Revision 1.3  1992/10/22  08:32:21  eichin
 * generally fix kprop so it works.
 *
 * Revision 1.2  1991/12/19  15:13:42  eichin
 * Athena changes as of 1991/11/1.
 * Fixes a few includes.
 *
 * Revision 1.1.1.2  1991/12/03  18:56:13  eichin
 * Athena Kerberos 4 patch 9
 *
 * Revision 4.3  89/05/16  15:06:04  wesommer
 * Fix operator precedence stuff.
 * Programmer: John Kohl.
 * 
 * Revision 4.2  89/03/23  10:24:00  jtkohl
 * NOENCRYPTION changes
 * 
 * Revision 4.1  89/01/24  20:33:48  root
 * name change
 * 
 * Revision 4.0  89/01/24  18:45:06  wesommer
 * Original version; programmer: wesommer
 * auditor: jon
 * 
 * Revision 4.5  88/01/08  18:07:46  jon
 * formatting and rcs header changes
 */

/*
 * This program is run on slave servers, to catch updates "pushed"
 * from the master kerberos server in a realm.
 */

#ifndef	lint
static char rcsid_kpropd_c[] =
"$Header: /afs/sipb.mit.edu/project/sipb-athena/repository/src/kerberos/slave/kpropd.c,v 1.4 1997/01/26 04:03:37 svalente Exp $";
#endif	lint

#include <mit-copyright.h>

#include <ctype.h>
#include <sys/types.h>
#include <sys/file.h>
#ifdef NEED_SYS_FCNTL_H
#include <sys/fcntl.h>
#endif
#include <sys/socket.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <netdb.h>
#include <krb.h>
#include <krbports.h>
#include <krb_db.h>
#include <klog.h>
#include <prot.h>
#include <kdc.h>
#include <errno.h>

#include "kprop.h"

extern char *inet_ntoa();

#ifdef POSIX
/* POSIX means we don't have flock... */
#define flock(f,c)	emul_flock(f,c)
#ifndef LOCK_SH
#define   LOCK_SH   1    /* shared lock */
#define   LOCK_EX   2    /* exclusive lock */
#define   LOCK_NB   4    /* don't block when locking */
#define   LOCK_UN   8    /* unlock */
#endif
#endif

#if defined(sun) && !defined(__svr4__)
extern char *sys_errlist[];
#define strerror(e) (sys_errlist[e])
#endif

static char kprop_version[KPROP_PROT_VERSION_LEN] = KPROP_PROT_VERSION;

int     debug = 0;

char   *logfile = K_LOGFIL;

char    errmsg[256];
int     pause_int = 300;	/* 5 minutes in seconds */
unsigned long get_data_checksum();
static void SlowDeath();
static char    buf[KPROP_BUFSIZ+64 /* leave room for private msg overhead */];

static void usage()
{
    fprintf(stderr, "\nUsage: kpropd [-r realm] [-s srvtab] [-l logfile] fname\n\n");
    SlowDeath();
}

main(argc, argv)
    int     argc;
    char  **argv;
{
    struct sockaddr_in from;
    struct sockaddr_in sin;
    struct servent *sp;
    int     s, s2, fd, n, fdlock;
    size_t  from_len;
    char    local_file[256];
    char    local_temp[256];
    char   *strcpy(), *krb_get_phost();
    struct hostent *hp;
    char    hostname[256];
    unsigned long cksum_read;
    unsigned long cksum_calc;
    char    from_str[128];
    u_long  length;
    long    kerror;
    AUTH_DAT auth_dat;
    KTEXT_ST ticket;
    char my_instance[INST_SZ];
    char *my_p_instance;
    char my_realm[REALM_SZ];
    char cmd[1024];
    short net_transfer_mode, transfer_mode;
    Key_schedule session_sched;
    char version[9];
    int c;
    extern char *optarg;
    extern int optind;
    int rflag = 0;
    char *srvtab = "";
    char *local_db = DBM_FILE;

    if (argv[argc - 1][0] == 'k' && isdigit(argv[argc - 1][1])) {
	argc--;			/* ttys file hack */
    }
    while ((c = getopt(argc, argv, "r:s:d:l:")) != EOF) {
	switch(c) {
	case 'r':
	    rflag++;
	    strcpy(my_realm, optarg);
	    break;
	case 's':
	    srvtab = optarg;
	    break;
	case 'd':
	    local_db = optarg;
	    break;
	case 'l':
	    logfile = optarg;
	    break;	    
	default:
	    usage();
	    break;
	}
    }
    if (optind != argc-1)
	usage();

    kset_logfile(logfile);

    klog(L_KRB_PERR, "\n\n***** kpropd started *****");

    strcpy(local_file, argv[optind]);
    strcat(strcpy(local_temp, argv[optind]), ".tmp");

    if (sp = getservbyname("krb_prop", "tcp"))
      sin.sin_port = sp->s_port;
    else
      sin.sin_port = htons(KRB_PROP_PORT); /* krb_prop/tcp */

    sin.sin_addr.s_addr = INADDR_ANY;
    sin.sin_family = AF_INET;

    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	sprintf(errmsg, "kpropd: socket: %s", strerror(errno));
	klog(L_KRB_PERR, errmsg);
	SlowDeath();
    }
    if (bind(s, &sin, sizeof sin) < 0) {
	sprintf(errmsg, "kpropd: bind: %s", strerror(errno));
	klog(L_KRB_PERR, errmsg);
	SlowDeath();
    }
 
    if (!rflag) {
	kerror = krb_get_lrealm(my_realm,1);
	if (kerror != KSUCCESS) {
	    sprintf (errmsg, "kpropd: Can't get local realm. %s",
		     krb_err_txt[kerror]);
	    klog (L_KRB_PERR, errmsg);
	    SlowDeath();
	}
    }
    
    if (gethostname(my_instance, sizeof(my_instance)) != 0) {
	sprintf(errmsg, "kpropd: gethostname: %s", strerror(errno));
	klog(L_KRB_PERR, errmsg);
	SlowDeath();
    }
    my_p_instance = krb_get_phost (my_instance);
    strcpy(my_instance, my_p_instance);
    
    klog(L_KRB_PERR, "Established socket");

    listen(s, 5);
    for (;;) {
	from_len = sizeof from;
	if ((s2 = accept(s, &from, &from_len)) < 0) {
	    sprintf(errmsg, "kpropd: accept: %s", strerror(errno));
	    klog(L_KRB_PERR, errmsg);
	    continue;
	}
	strcpy(from_str, inet_ntoa(from.sin_addr));
#ifdef hpux
	if (from_len == sizeof from) {
	    /* hppa hpux appears to return the size of the sockaddr_in,
	       instead of the size of the actual address. */
	    from_len = sizeof from.sin_addr.s_addr;
	}
#endif

	if ((hp = gethostbyaddr((char *)(&(from.sin_addr.s_addr)),
				from_len, AF_INET)) == NULL) {
	    extern int h_errno;
	    strcpy(hostname, "UNKNOWN");
	    sprintf(errmsg, 
		    "Failed to reverse-resolve connection %s (reason %d)",
		    from_str, h_errno);
	    klog(L_KRB_PERR, errmsg);
	} else {
	    strcpy(hostname, hp->h_name);
	}

	sprintf(errmsg, "Connection from %s, %s", hostname, from_str);
	klog(L_KRB_PERR, errmsg);

	/* for krb_rd_{priv, safe} */
	n = sizeof sin;
	if (getsockname (s2, &sin, &from_len) != 0) {
	    fprintf (stderr, "kpropd: can't get socketname.\n");
	    perror ("getsockname");
	    SlowDeath();
	}
	if (from_len != sizeof (sin)) {
	    fprintf (stderr, "kpropd: can't get socketname. len");
	    SlowDeath();
	}

	if ((fdlock = open(local_temp, O_WRONLY | O_CREAT, 0600)) < 0) {
	    sprintf(errmsg, "kpropd: open: %s", strerror(errno));
	    klog(L_KRB_PERR, errmsg);
	    SlowDeath();
	}
	if (flock(fdlock, LOCK_EX | LOCK_NB)) {
	    sprintf(errmsg, "kpropd: flock: %s", strerror(errno));
	    klog(L_KRB_PERR, errmsg);
	    SlowDeath();
	}
	if ((fd = creat(local_temp, 0600)) < 0) {
	    sprintf(errmsg, "kpropd: creat: %s", strerror(errno));
	    klog(L_KRB_PERR, errmsg);
	    SlowDeath();
	}
	if ((n = read (s2, buf, sizeof (kprop_version)))
	    != sizeof (kprop_version)) {
	    klog (L_KRB_PERR, "kpropd: can't read kprop protocol version str.");
	    SlowDeath();
	}
	if (strncmp (buf, kprop_version, sizeof (kprop_version))
	    != 0) {
	    sprintf (errmsg, "kpropd: unsupported version %s", buf);
	    klog (L_KRB_PERR, errmsg);
	    SlowDeath();
	}

	if ((n = read (s2, &net_transfer_mode, sizeof (net_transfer_mode)))
	    != sizeof (net_transfer_mode)) {
	    klog (L_KRB_PERR, "kpropd: can't read transfer mode.");
	    SlowDeath();
	}
	transfer_mode = ntohs (net_transfer_mode);
	kerror = krb_recvauth(KOPT_DO_MUTUAL, s2, &ticket,
			      KPROP_SERVICE_NAME,
			      my_instance,
			      &from,
			      &sin,
			      &auth_dat,
			      srvtab,
			      session_sched,
			      version);
	if (kerror != KSUCCESS) {
	    sprintf (errmsg, "kpropd: %s: Calling getkdata", 
		     krb_err_txt[kerror]);
	    klog (L_KRB_PERR, errmsg);
	    SlowDeath();
	}
	
	sprintf (errmsg, "kpropd: Connection from %s.%s@%s",
		 auth_dat.pname, auth_dat.pinst, auth_dat.prealm);
	klog (L_KRB_PERR, errmsg);

	/* AUTHORIZATION is done here.  We might want to expand this to
	 * read an acl file at some point, but allowing for now
	 * KPROP_SERVICE_NAME.KRB_MASTER@local-realm is fine ... */
	/* actually, check rcmd and the realm here. Use admin host below. */

	if ((strcmp (KPROP_SERVICE_NAME, auth_dat.pname) != 0) ||
	    (strcmp (my_realm, auth_dat.prealm) != 0)) {
	    klog (L_KRB_PERR, "Authorization denied!");
	    SlowDeath();
	}
	/* Overload the "admin" host listing in krb.conf. */
	{
	  char authorized[INST_SZ];
	  int i = 0;
	  while(KFAILURE != krb_get_admhst(authorized,my_realm,i)) {
	    if(strcmp(krb_get_phost(authorized), auth_dat.pinst) == 0) {
	      i = (-1); break;
	    }
	    i++;
	  }
	  if (i != -1) {
	    klog (L_KRB_PERR, "kprop from non-admin host, rejected");
	    SlowDeath();
	  }
	}

	switch (transfer_mode) {
	case KPROP_TRANSFER_PRIVATE: 
	    recv_auth (s2, fd, 1 /* private */, &from, &sin, &auth_dat);
	    break;
	case KPROP_TRANSFER_SAFE: 
	    recv_auth (s2, fd, 0 /* safe */, &from, &sin, &auth_dat);
	    break;
	case KPROP_TRANSFER_CLEAR: 
	    recv_clear (s2, fd);
	    break;
	default: 
	    sprintf (errmsg, "kpropd: bad transfer mode %d", transfer_mode);
	    klog (L_KRB_PERR, errmsg);
	    SlowDeath();
	}

	if (transfer_mode != KPROP_TRANSFER_PRIVATE) {
	    klog(L_KRB_PERR, "kpropd: non-private transfers not supported\n");
	    SlowDeath();
#ifdef doesnt_work_yet
	    lseek(fd, (long) 0, L_SET);
	    if (auth_dat.checksum != get_data_checksum (fd, session_sched)) {
		klog(L_KRB_PERR, "kpropd: checksum doesn't match");
		SlowDeath();
	    }
#endif
	} else

        {
	    struct stat st;
	    fstat(fd, &st);
	    if (st.st_size != auth_dat.checksum) {
	        sprintf(errmsg, "kpropd: length 0x%x doesn't match cks 0x%x",
			st.st_size, auth_dat.checksum);
	        klog(L_KRB_PERR, errmsg);
		SlowDeath();
	    }
	}
	close(fd);
	close(s2);
	klog(L_KRB_PERR, "File received.");

	if (rename(local_temp, local_file) < 0) {
	    sprintf(errmsg, "kpropd: rename: %s", strerror(errno));
	    klog(L_KRB_PERR, errmsg);
	    SlowDeath();
	}
	klog(L_KRB_PERR, "Temp file renamed to %s", local_file);

	if (flock(fdlock, LOCK_UN)) {
	    sprintf(errmsg, "kpropd: flock (unlock): %s", strerror(errno));
	    klog(L_KRB_PERR, errmsg);
	    SlowDeath();
	}
	close(fdlock);
	sprintf(cmd, "kdb_util load %s %s\n", local_file,
		local_db);
	if (system (cmd) != 0) {
	    klog (L_KRB_PERR, "Couldn't load database");
	    SlowDeath();
	}
    }
}

recv_auth (in, out, private, remote, local, ad)
     int in, out;
     int private;
     struct sockaddr_in *remote, *local;
     AUTH_DAT *ad;
    {
      u_long length;
      long kerror;
      int n;
      MSG_DAT msg_data;
      Key_schedule session_sched;

      if (private)
#ifdef NOENCRYPTION
	memset((char *)session_sched, 0, sizeof(session_sched));
#else
	if (key_sched (ad->session, session_sched)) {
	  klog (L_KRB_PERR, "kpropd: can't make key schedule");
	  SlowDeath();
	}
#endif

      while (1) {
	n = krb_net_read (in, &length, sizeof length);
	if (n == 0) break;
	if (n < 0) {
	  sprintf (errmsg, "kpropd: read: %s", strerror(errno));
	  klog (L_KRB_PERR, errmsg);
	  SlowDeath();
	}
	length = ntohl (length);
	if (length > sizeof buf) {
	  sprintf (errmsg, "kpropd: read length %d, bigger than buf %d",
		   length, sizeof buf);
	  klog (L_KRB_PERR, errmsg);
	  SlowDeath();
	}
	n = krb_net_read(in, buf, length);
	if (n < 0) {
	  sprintf(errmsg, "kpropd: read: %s", strerror(errno));
	  klog(L_KRB_PERR, errmsg);
	    SlowDeath();
	  }
	if (private)
	  kerror = krb_rd_priv (buf, n, session_sched, ad->session, 
				remote, local, &msg_data);
	else
	  kerror = krb_rd_safe (buf, n, ad->session,
				remote, local, &msg_data);
	if (kerror != KSUCCESS) {
	  sprintf (errmsg, "kpropd: %s: %s",
		   private ? "krb_rd_priv" : "krb_rd_safe",
		   krb_err_txt[kerror]);
	  klog (L_KRB_PERR, errmsg);
	  SlowDeath();
	}
	if (write(out, msg_data.app_data, msg_data.app_length) != 
	    msg_data.app_length) {
	  sprintf(errmsg, "kpropd: write: %s", strerror(errno));
	  klog(L_KRB_PERR, errmsg);
	  SlowDeath();
	}
      }
    }

recv_clear (in, out)
    int in, out;
    {
      int n;

      while (1) {
	n = read (in, buf, sizeof buf);
	if (n == 0) break;
	if (n < 0) {
	    sprintf (errmsg, "kpropd: read: %s", strerror(errno));
	    klog (L_KRB_PERR, errmsg);
	    SlowDeath();
	  }
	if (write(out, buf, n) != n) {
	      sprintf(errmsg, "kpropd: write: %s", strerror(errno));
	      klog(L_KRB_PERR, errmsg);
	      SlowDeath();
	    }
      }
    }

static void 
SlowDeath()
{
    klog(L_KRB_PERR, "kpropd will pause before dying so as not to loop init");
    sleep(pause_int);
    klog(L_KRB_PERR, "AAAAAHHHHhhhh....");
    exit(1);
}
#ifdef doesnt_work_yet
unsigned long get_data_checksum(fd, key_sched)
int fd;
Key_schedule key_sched;
{
	unsigned long cksum = 0;
	unsigned long cbc_cksum();
	int n;
	char buf[BUFSIZ];
	char obuf[8];

	while (n = read(fd, buf, sizeof buf)) {
	    if (n < 0) {
		sprintf(errmsg, "kpropd: read (in checksum test): %s",
						strerror(errno));
		klog(L_KRB_PERR, errmsg);
		SlowDeath();
	    }
#ifndef NOENCRYPTION
	    cksum += cbc_cksum(buf, obuf, n, key_sched, key_sched);
#endif
	  }
	return cksum;
}
#endif
