/*
 *  Kerberized NNTP connection forwarder; client side.
 *
 *  $Id: client.c,v 1.1 1999/12/12 08:36:52 kolya Exp $
 *
 *  kolya@MIT.EDU, 11 December 1999
 */

#include "krb5.h"
#include "com_err.h"

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#include <stdlib.h>
#include <syslog.h>

#include "knntp.h"

int main(int argc, char *argv[])
{
    struct hostent *hp;
    struct sockaddr_in server_sin, server_lsin, sin, lsin;
    int sock, namelen;
    krb5_data cksum_data;
    krb5_error_code retval;
    krb5_ccache ccdef;
    krb5_principal client, server;
    krb5_error *err_ret;
    krb5_ap_rep_enc_part *rep_ret;
    char *service = KNNTP_SERVICE;
    int listen_sock = 0;
    int client_sock = 0;
    int on = 1;
    fd_set all_fds;
    char read_buf[BUFSIZ];
    krb5_context context;
    krb5_auth_context auth_context = NULL;

    if (argc != 2 && argc != 3) {
	fprintf(stderr, "usage: %s <hostname> [port]\n",argv[0]);
	exit(1);
    }

    listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_sock < 0) {
	perror("creating socket");
	exit(1);
    }
    setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));

    server_sin.sin_port = htons(LISTEN_PORT);
    server_sin.sin_family = AF_INET;
    server_sin.sin_addr.s_addr = htonl(INADDR_ANY);

    if(bind(listen_sock, (struct sockaddr *)&server_sin, sizeof(server_sin))) {
	perror("binding to socket");
	exit(1);
    }

    if(listen(listen_sock, 5) < 0) {
	perror("listening on socket");
	exit(1);
    }

    namelen = sizeof(server_lsin);
    while((client_sock = accept(listen_sock, (struct sockaddr *)&server_lsin, &namelen)) >= 0) {
	if(!fork())
	    break;
	else
	    close(client_sock);
    }

    if(client_sock < 0) {
	perror("accepting connection");
	exit(1);
    }

    if(ntohl(server_lsin.sin_addr.s_addr) == INADDR_LOOPBACK) {
	printf("Forwarding KNNTP connection from %s..\n", inet_ntoa(server_lsin.sin_addr));
    } else {
	char *msg = "502 KNNTP only forwards connections from localhost.\n";
	printf("Connection from %s refused.\n", inet_ntoa(server_lsin.sin_addr));
	write(client_sock, msg, strlen(msg));
	exit(1);
    }

    retval = krb5_init_context(&context);
    if (retval) {
	    com_err(argv[0], retval, "while initializing krb5");
	    exit(1);
    }

    (void) signal(SIGPIPE, SIG_IGN);
    if (!valid_cksumtype(CKSUMTYPE_CRC32)) {
	com_err(argv[0], KRB5_PROG_SUMTYPE_NOSUPP, "while using CRC-32");
	exit(1);
    }

    /* clear out the structure first */
    (void) memset((char *)&sin, 0, sizeof(sin));

    if (argc > 2) {
	sin.sin_family = AF_INET;
	sin.sin_port = htons(atoi(argv[2]));
    } else {
	/* copy the port number */
	sin.sin_port = htons(KNNTP_PORT);
	sin.sin_family = AF_INET;
    }

    /* look up the server host */
    hp = gethostbyname(argv[1]);
    if (!hp) {
	fprintf(stderr, "unknown host %s\n",argv[1]);
	exit(1);
    }

    if (retval = krb5_sname_to_principal(context, argv[1], service,
					 KRB5_NT_SRV_HST, &server)) {
	com_err(argv[0], retval, "while creating server name for %s/%s",
		argv[1], service);
	exit(1);
    }

    /* set up the address of the foreign socket for connect() */
    sin.sin_family = hp->h_addrtype;
    (void) memcpy((char *)&sin.sin_addr,
		  (char *)hp->h_addr,
		  sizeof(hp->h_addr));

    /* open a TCP socket */
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
	perror("socket");
	exit(1);
    }

    /* connect to the server */
    if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
	perror("connect");
	close(sock);
	exit(1);
    }

    /* find out who I am, now that we are connected and therefore bound */
    namelen = sizeof(lsin);
    if (getsockname(sock, (struct sockaddr *) &lsin, &namelen) < 0) {
	perror("getsockname");
	close(sock);
	exit(1);
    }

    cksum_data.data = argv[1];
    cksum_data.length = strlen(argv[1]);

    if (retval = krb5_cc_default(context, &ccdef)) {
	com_err(argv[0], retval, "while getting default ccache");
	exit(1);
    }

    if (retval = krb5_cc_get_principal(context, ccdef, &client)) {
	com_err(argv[0], retval, "while getting client principal name");
	exit(1);
    }
    retval = krb5_sendauth(context, &auth_context, (krb5_pointer) &sock,
			   KNNTP_VERSION, client, server,
			   AP_OPTS_MUTUAL_REQUIRED,
			   &cksum_data,
			   0,		/* no creds, use ccache instead */
			   ccdef, &err_ret, &rep_ret, NULL);

    krb5_free_principal(context, server);	/* finished using it */

    if (retval && retval != KRB5_SENDAUTH_REJECTED) {
	com_err(argv[0], retval, "while using sendauth");
	exit(1);
    }
    if (retval == KRB5_SENDAUTH_REJECTED) {
	/* got an error */
	printf("*** sendauth rejected, error reply is:\n\t\"%*s\"\n",
	       err_ret->text.length, err_ret->text.data);
    } else if (rep_ret) {
	/* got a reply */
	FD_ZERO(&all_fds);
	FD_SET(client_sock, &all_fds);
	FD_SET(sock, &all_fds);
	
	while(1) {
	    fd_set rfds = all_fds;
	    int maxFds = MAX(sock, client_sock) + 1;

	    retval = select(maxFds, &rfds, NULL, NULL, NULL);
	    if(retval < 0)
		perror("selecting fds");

	    if(FD_ISSET(sock, &rfds)) {
		retval = read(sock, read_buf, sizeof(read_buf));
		if(retval < 0) {
		    perror("receiving from socket");
		    exit(1);
		}
		if(retval == 0) {
		    exit(0);
		}
		write(client_sock, read_buf, retval);
	    }

	    if(FD_ISSET(client_sock, &rfds)) {
		retval = read(client_sock, read_buf, sizeof(read_buf));
		if(retval < 0) {
		    perror("receiving from client");
		    exit(1);
		}
		if(retval == 0) {
		    exit(0);
		}
		write(sock, read_buf, retval);
	    }
	}
    } else {
	com_err(argv[0], 0, "no error or reply from sendauth!");
	exit(1);
    }
    exit(0);
}

