/*
 *
 *	Copyright (C) 1988, 1989 by the Massachusetts Institute of Technology
 *    	Developed by the MIT Student Information Processing Board (SIPB).
 *    	For copying information, see the file mit-copyright.h in this release.
 *
 */
/*
 *
 *  rpproc.c -- Procedures to implement a simple (perhaps brain-asleep) RPC
 *		protocol over a TCP connection.
 *	     	This file handles the server's side of the connection.
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#ifdef INETD
#define ASSOC 1
#endif

#ifdef SUBPROC
#define ASSOC 1
#endif

#include <stdio.h>
#include <stdlib.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <string.h>
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <errno.h>
#include <pwd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#ifdef KERBEROS
#include <krb.h>
#endif
#include <discuss/discuss.h>
#include <discuss/rpc.h>
#include <discuss/tnet.h>
#include <discuss/config.h>
#include "globals.h"
#include "dispatch.h"
#include "rpproc.h"

#ifndef SOMAXCONN
#define SOMAXCONN 5
#endif

#define SUCCESS 1
#define ERROR   -1
#define min(a, b) (a < b ? a : b)

/* global */
static long hostaddr;

extern int errno;
#if defined(KERBEROS) && !defined(SUBPROC)
static char serv_name[20];
extern int krb_rd_req();
extern int krb_err_base;
#endif /* defined(KERBEROS) && !defined(SUBPROC) */
int rpc_err;

/* Static variables */

#ifdef USPRPC
/* connections & socket info */
static USPStream *us = NULL;
#endif

static void handle_kerberos(USPCardinal, char *, long);
static void handle_weak(USPCardinal);


/*
 *
 * init_rpc () -- Initialize the RPC mechanism
 *
 */
void
init_server_rpc (service,code)
    char *service;
    int *code;
{
#ifdef INETD
    int d;
#endif
    int snew;			/* socket we're reading from */
    
#ifdef SUBPROC
    int uid;
    struct passwd *pwent;
#endif
    
#ifndef ASSOC
    struct protoent *pe;
    struct servent *se;
    struct sockaddr_in sai;
    int sock_len = sizeof (sai);
    int s;
    int service_port;
#endif
    
#if defined(KERBEROS) && !defined(SUBPROC)
    int fromlen,i;
    struct sockaddr_in from;
    char hostname[50];
    struct hostent *hp;
#endif /* KERBEROS && !SUBPROC */
    USPCardinal bt;

    init_rpc_err_tbl();

#ifdef INETD
    d = open ("/dev/null", 2);
    dup2(d, 1);
    dup2(d, 2);
    close(d);
    if (geteuid() == 0) {
	printf ("Can't run as root.\n"); /* just in case setuid bit off */
	exit(1);
    }
#endif
    
#ifdef ASSOC
    /* safety check -- 0 better be a socket, not a pipe or file */
    {
	if (isatty (0)) {
	    *code = RPC_NOT_SUBPROC;
	    return;
	}
    }

#ifdef SUBPROC
    {
	int s;
	for (s = 1; s < 10; s++)
	    (void) close (s);
    }
    {
	int fd;
	fd = open("/dev/null", 2);
	if (fd != 1) {
	    (void) dup2 (fd, 1);
	    (void) close (fd);
	}
	(void) dup2(1, 2);
    }
#ifdef _POSIX_VERSION
    setsid();
#else
    setpgrp(0, getpid());		/* So we don't get tty signals */
#endif
#endif /* SUBPROC */
    
    snew = 0;
    us = USP_associate (snew);
#else  /* !ASSOC */
    /* to be added */
    setprotoent(0);			    /* get protocol information */
    pe = getprotobyname("tcp");
    setservent(0);			    /* get service information */
    
    if((se = getservbyname(SERVICE_NAME, "tcp")) == NULL) {
	 service_port = SERVICE_PORT;
    } else {
	 service_port = se->s_port;
    }
    sai.sin_addr.s_addr = INADDR_ANY;
    sai.sin_port = service_port;		    /* set up socket */
    if((s = socket(AF_INET, SOCK_STREAM, pe->p_proto)) < 0) {
	*code = errno;
	return;
    }
    if(bind(s, (struct sockaddr *) &sai, sizeof(sai))) {
	*code = errno;
	return;
    }	
    listen(s, SOMAXCONN);		/* listen for connection */
    if((snew = accept(s, (struct sockaddr *) &sai, &sock_len)) < 0) {
	*code = errno;
	return;
    }
    
    us = USP_associate (snew);
    if (us == NULL) {
	*code = errno;
	return;
    }
#endif /* !ASSOC */
    
    strcpy (rpc_caller, "???");		/* safety drop */
    
#ifdef SUBPROC
    uid = getuid ();
    pwent = getpwuid(uid);
    if (pwent != 0) {
	strcpy (rpc_caller, pwent -> pw_name);
    }
#endif
    strcat (rpc_caller, "@");
    strcat (rpc_caller, local_realm());

#if defined(KERBEROS) && !defined(SUBPROC)

    strcpy(serv_name, service);
    fromlen = sizeof (from);
    if (getpeername (snew, (struct sockaddr *) &from, &fromlen) < 0) {
	*code = errno;
	return;
    }
    if (fromlen == 0) {		/* no len, UNIX domain = me */
	gethostname(hostname, sizeof(hostname));
	hp = gethostbyname(hostname);
	memcpy(&hostaddr, hp->h_addr, 4);
    } else {
	memcpy(&hostaddr, &from.sin_addr, 4); 
    }
    
    if ((USP_rcv_blk(us, &bt) != SUCCESS) || (bt != KRB_TICKET &&
#ifdef WEAK_REALM
					      bt != WEAK_TICKET &&
#endif /* WEAK_REALM */
					      bt != KRB_TICKET2)) {
	*code = RPC_PROTOCOL;
	return;
    }

#ifdef WEAK_REALM
    if (bt == WEAK_TICKET)
	 handle_weak(bt);
    else
#endif /* WEAK_REALM */
	 handle_kerberos(bt,serv_name,hostaddr);

#else /* !(KERBEROS && !SUBPROC) */
#ifdef WEAK_REALM
    if ((USP_rcv_blk(us, &bt) != SUCCESS) || (bt != WEAK_TICKET)) {
	*code = RPC_PROTOCOL;
	return;
    }
    handle_weak(bt);
#endif /* WEAK_REALM */
#endif /* KERBEROS */
    *code = 0;
    return;
}

#if defined(KERBEROS) && !defined(SUBPROC)
static void
handle_kerberos(bt,service,haddr)
USPCardinal bt;
char *service;
long haddr;
{
    int i,result;
    char hostname[50];
    char filename[50];
    char instance[INST_SZ];
    AUTH_DAT kdata;
    KTEXT_ST ticket;

    strcpy (rpc_caller, "???@");		/* safety drop */
    strcat (rpc_caller, local_realm());

    /* read authenticator off net */
    ticket.length = recvshort();
    if ((ticket.length<=0) || (ticket.length>MAX_KTXT_LEN)) {
	result = RPC_PROTOCOL;
	goto punt_kerberos;
    }
    for (i=0; i<ticket.length; i++) {
	ticket.dat[i] = recvshort();
    }
    
    /* make filename from service */
    strcpy (filename, "/usr/spool/");
    strcat (filename, service);
    strcat (filename, "/srvtab");
    
    strcpy(instance,"*");
    result = krb_rd_req (&ticket, service, instance, haddr, &kdata, filename);
    if (result == 0) {
	strcpy(rpc_caller, kdata.pname);
	if (kdata.pinst[0] != '\0') {
	    strcat(rpc_caller, ".");
	    strcat(rpc_caller, kdata.pinst);
	}
	strcat(rpc_caller, "@");
	strcat(rpc_caller, kdata.prealm);
    }
    else {
	result += krb_err_base;
	goto punt_kerberos;
    }
punt_kerberos:
    USP_flush_block(us);
    if (bt == KRB_TICKET2) {
	 USP_begin_block(us,TICKET_REPLY);
	 USP_put_long_integer(us, i);
	 USP_end_block(us);
    }
}
#endif /* KERBEROS */

#ifdef WEAK_REALM
static void
handle_weak(bt)
USPCardinal bt;
{
     int length,result,i;
     char user_name[17];

     strcpy (rpc_caller, "???@");		/* safety drop */
     strcat (rpc_caller, local_realm());

     length = recvshort();
     if ((length<=0) || (length>= sizeof(user_name) - 1 - sizeof(WEAK_REALM))) {
	  result = RPC_PROTOCOL;
	  goto punt_weak;
     }
     for (i=0; i<length; i++) {
	  user_name[i] = recvshort();
     }
     for (i=0; i<length; i++) {
	  if (!isascii(user_name[i]) || !isgraph(user_name[i]) || user_name[i] == '@') {
	       goto punt_weak;
	  }
     }
     strcpy(rpc_caller,user_name);
     strcat(rpc_caller,"@");
     strcat(rpc_caller, WEAK_REALM);

punt_weak:
    USP_flush_block(us);
}
#endif /* WEAK_REALM */

/*
 *
 * recvit ()  -- Routine to accept an RPC call.
 *
 */
void
recvit (code)
    int *code;
{
    USPCardinal bt;
    int procno;

    if (USP_rcv_blk(us, &bt) != SUCCESS) {
	if (errno = ECONNRESET) {		/* he went away, so do we */
	    *code = errno;
	}
	*code = errno;
	return;
    }

#if defined(KERBEROS) && !defined(SUBPROC)
    if (bt == KRB_TICKET || bt == KRB_TICKET2) {
	 handle_kerberos(bt, serv_name, hostaddr);
	 *code = 0;
	 return;
    }
#endif /* KERBEROS */

    procno = bt - PROC_BASE;

    if (procno == 0) {
	*code = RPC_PROTOCOL;
	return;
    }
    if (procno > numprocs) {
	USP_flush_block(us);
	senddunno();
	*code = 0;
	return;
    }

    rpc_err = 0;
    dispatch (procno);
    *code = rpc_err;
    return;
}

/*
 *
 * recvfile() -- Receive a file in an RPC call.
 *
 */
tfile recvfile ()
{
    USPLong_integer tfs;
    tfile tf;
    
    if (USP_get_long_integer(us, &tfs) != SUCCESS) {
	rpc_err = errno;
	return(0);
    }
    
    tf = net_tfile (tfs,us);
    
    return (tf);
}

/*
 *
 * startreply()  -- Get ready to send reply of an RPC call.
 *
 */
startreply()
{
    USP_begin_block(us,REPLY_TYPE);
    
    return;
}

/*
 *
 * sendreply () -- Make the final call.
 *
 */
sendreply()
{
    if (USP_end_block(us) != SUCCESS) {
	rpc_err = rpc_err_base + errno;
	return;
    }
    return;
}

/*
 *
 * senddunno () -- Send a 'I don't know this call' reply
 *
 */
senddunno()
{
    USP_begin_block(us,UNKNOWN_CALL);
    if (USP_end_block(us) != SUCCESS) {
	rpc_err = rpc_err_base + errno;
	return;
    }
    return;
}

int recvint ()
{
    USPLong_integer li;
    
    if (USP_get_long_integer(us, &li) != SUCCESS) {
	rpc_err = errno;
	return(0);
    }
    return (li);
}

int recvshort ()
{
    USPInteger li;
    
    if (USP_get_integer(us, &li) != SUCCESS) {
	rpc_err = errno;
	return(0);
    }
    return (li);
}

char *recvstr ()
{
    USPString str;
    
    if (USP_get_string(us, &str) != SUCCESS) {
	rpc_err = errno;
	return("");
    }
    return (str);
}

int recvbool()
{
    USPBoolean flag;
    
    if (USP_get_boolean(us, &flag) != SUCCESS) {
	rpc_err = errno;
	return(0);
    }
    return (flag);
}

void sendint(i)
    int i;
{
    if (USP_put_long_integer(us, i) != SUCCESS) {
	rpc_err = errno + rpc_err_base;
    }
}

void sendstr(str)
    char *str;
{
    if (USP_put_string(us, str) != SUCCESS) {
	rpc_err = rpc_err_base + errno;
    }
}

void sendbool(b)
    int b;
{
    if (USP_put_boolean(us, (USPBoolean)b) != SUCCESS) {
	rpc_err = rpc_err_base + errno;
    }
}
