/*-
 * Copyright (c) 2000-2001 Joe Greco and sol.net Network Services
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

/*
 * NetRemote sample server
 *
 * This is Yet Another Famous Joe Greco Simple Stupid Protocol
 *  
 * My intended use was, due to a large number of reader machines that
 * maintained strong firewalls, to be able to centralize authentication
 * operations on a core set of servers so that maintenance would be
 * easier.
 *  
 * V2 protocol is a bit different from V1.  Server starts by sending the 
 * time as a 12-digit integer.  Client responds with a packet length as a
 * 12-digit integer, and a DES-encrypted packet containing a random number,
 * the server-sent time, the encryption password, a transaction type number, 
 * and (for the normal "authenticate!" transaction type) username being 
 * auth'd, and password being auth'd.  The server then returns a string to
 * report its status.  In plain text.
 *
 * Transaction types:  0 to actually auth, 1 if we are reporting status
 * of a user (i.e. disconnected and want to record stats)
 *
 * "authenticate!" transaction type:
 * dreaderd expects the status string to be in the format "100 Success" or
 * "110 readerdef" and the string may be somewhat long, since we plan to
 * allow additional tokens to be specified at some future point.
 *  
 * This bit of code is particularly appalling.
 */

#include        <stdio.h>
#include	<sys/types.h>
#include	<sys/wait.h>
#include	<sys/time.h>
#include	<sys/resource.h>
#include	<sys/uio.h>
#include	<unistd.h>
#include	<string.h>
#include	<stdlib.h>
#include	<syslog.h>
#include	<varargs.h>
#include	<errno.h>
#include	<signal.h>
#include	<des.h>
#include	"socket.h"






char *authenticate(char *user, char *pass)
{
#error "No auth defined"
	return("500 Fail");
}

void accountadd(char *user, char *acctdata)
{
#error "No accounting defined"
	/*
	 * acctdata will hold a newline-delimited string of:
	 *	ByteCount,
	 *	GrpCount,
	 *	ArtCount,
	 *	PostBytes,
	 *	PostCount,
	 *	PostFailCount,
	 *	ByteCountArticle,
	 *	ByteCountHead,
	 *	ByteCountBody,
	 *	ByteCountList,
	 *	ByteCountXover,
	 *	ByteCountXhdr,
	 *	ByteCountOther,
	 *	SessionLength,
	 *	ClientIPAddr
	 */
	return;
}





#define NETREMOTE_DATASIZE      1432

int call_processor(fd, pwd)
int fd;
char *pwd;
{
	char buffer[NETREMOTE_DATASIZE], output[NETREMOTE_DATASIZE], *ptr, *optr;
	time_t reqtime = time(NULL);
	int pktlen;
	des_cblock key;
	des_key_schedule sched;
	char *user, *pass, *acctdata;
	char *res;
	char transtype;

	alarm(60);

	/* Send 12-digit time for exchange */
	snprintf(buffer, sizeof(buffer), "%012d\n", reqtime);
	write(fd, buffer, strlen(buffer));

	/* Read 12-digit packet length in response, plus \n */
	bzero(buffer, 14);
	if (xread(fd, buffer, 13) != 13) {
		syslog(LOG_ERR, "short read from client, pktlen");
		close(fd);
		exit(1);
	}
	pktlen = atoi(buffer);
	if (pktlen < 16 || pktlen > NETREMOTE_DATASIZE) {
		syslog(LOG_ERR, "pktlen read from client, ridiculous size %d", pktlen);
		close(fd);
		exit(1);
	}

	if (xread(fd, buffer, pktlen) != pktlen) {
		syslog(LOG_ERR, "short read from client, packet");
		close(fd);
		exit(1);
	}

	des_string_to_key(pwd, &key);
	des_set_key(&key, sched);

	bzero(output, sizeof(output));
	ptr = buffer;
	optr = output;
	while (ptr < buffer + sizeof(buffer)) {
		des_ecb_encrypt((des_cblock *)ptr,(des_cblock *)optr, sched, 0);
		bzero(ptr, 8);
		ptr += 8;
		optr += 8;
	}
	ptr = output;
	while (*ptr != '\n' && ptr < (output + pktlen)) {
		ptr++;
	}
	if (*ptr == '\n') {
		ptr++;
	} else {
		syslog(LOG_ERR, "packet data, no line break found, probably invalid password");
		close(fd);
		exit(1);
	}

	/*
	 * We sorta ignore the random number, which is mostly there to
	 * generate more randomness in the data stream should you ever
	 * choose to use something other than des_ecb
	 */
	snprintf(buffer, sizeof(buffer), "%d\n%s\n", reqtime, pwd);

	if (strncmp(ptr, buffer, strlen(buffer))) {
		syslog(LOG_ERR, "packet data, probably invalid password");
		close(fd);
		exit(1);
	}

	/* Move past the time */
	while (*ptr != '\n' && ptr < (output + pktlen)) {
		ptr++;
	}
	if (*ptr == '\n') {
		ptr++;
	} else {
		syslog(LOG_ERR, "packet data, no line break found, probably invalid password");
		close(fd);
		exit(1);
	}

	/* Move past the encrypt p/w */
	while (*ptr != '\n' && ptr < (output + pktlen)) {
		ptr++;
	}
	if (*ptr == '\n') {
		ptr++;
	} else {
		syslog(LOG_ERR, "packet data, no line break found, probably invalid password");
		close(fd);
		exit(1);
	}

	/* Move past the transaction type */
	transtype = *ptr;
	while (*ptr != '\n' && ptr < (output + pktlen)) {
		ptr++;
	}
	if (*ptr == '\n') {
		ptr++;
	} else {
		syslog(LOG_ERR, "packet data, no line break found, probably invalid password");
		close(fd);
		exit(1);
	}

	if (transtype == '1') {
		/* Pull out the username and password */
		user = ptr;

		while (*ptr != '\n' && ptr < (output + pktlen)) {
			ptr++;
		}

		if (*ptr == '\n') {
			*ptr++ = '\0';	/* null terminate the username */
			pass = ptr;
			while (*ptr != '\n' && ptr < (output + pktlen)) {
				ptr++;
			}
			if (*ptr == '\n') {
				*ptr++ = '\0';	/* null terminate the password */
				res = authenticate(user, pass);
				snprintf(buffer, sizeof(buffer), "%s\n", res);
				write(fd, buffer, strlen(buffer));
				close(fd);
				exit(0);
			} else {
				syslog(LOG_ERR, "packet data, just plain odd");
				close(fd);
				exit(1);
			}
		} else {
			syslog(LOG_ERR, "packet data, just plain odd");
			close(fd);
			exit(1);
		}

		close(fd);
		exit(1);
	} else if (transtype == '2') {
		/* Pull out the username and byte count */
		user = ptr;

		while (*ptr != '\n' && ptr < (output + pktlen)) {
			ptr++;
		}

		if (*ptr == '\n') {
			*ptr++ = '\0';	/* null terminate the username */
			acctdata = ptr;	/* byte count, other stats */
			while (*ptr != '\n' && ptr < (output + pktlen)) {
				ptr++;
			}
			if (*ptr == '\n') {
				accountadd(user, acctdata);
				close(fd);
				exit(0);
			} else {
				syslog(LOG_ERR, "packet data, just plain odd");
				close(fd);
				exit(1);
			}
		} else {
			syslog(LOG_ERR, "packet data, just plain odd");
			close(fd);
			exit(1);
		}

		close(fd);
		exit(1);
	} else {
		syslog(LOG_ERR, "packet data, unknown transtype %c", transtype);
		close(fd);
		exit(1);
	}
}





int handle_connection(fd, lfd, pwd)
int fd, lfd;
char *pwd;
{
	int rval;

	if ((rval = fork()) < 0) {
		perror("fork");
		close(fd);
		return(-1);
	}
	if (rval) {
		close(fd);
		return(0);
	}
	close(lfd);
	call_processor(fd, pwd);
	exit(1);
}





void collect_dead_kids(val)
int val;
{
	int i;

	if (wait3(&i, WNOHANG, NULL) < 0) {
		perror("wait3");
	}
}




int xread(fd, buf, siz)
int fd;
char *buf;
int siz;
{
        int rval;
        int chrs = 0;

        while (siz) {
                if ((rval = read(fd, buf, siz)) <= 0) {
                        return(rval);
                }
                chrs += rval;
                siz -= rval;
                buf += rval;
        }
}

int main(argc, argv)
int argc;
char *argv[];   
{       
	int fd, lfd, i;
	char *progname = argv[0];
	char peer[64];

	if (strrchr(progname, '/')) {
		progname = strrchr(progname, '/') + 1;
	}

	if (argc < 3) {
		fprintf(stderr, "usage: netremoted listen-port password\n");
		exit(1);
	}
	if ((lfd = create_tcp_listen_socket(atoi(argv[1]), OPT_REUSEADDR, NULL)) < 0) {
                fprintf(stderr, "%s: create_tcp_listen_socket: couldn't open port\n", progname);
                exit(1);
        }

	if (signal(SIGCHLD, collect_dead_kids) < 0) {
		perror("signal");
		exit(1);
	}
	if (daemon(0, 1) < 0) {
                perror("daemon");
                exit(1);
        }
	openlog("netremoted", LOG_PID, LOG_NEWS);
	while (1) {
                if ((fd = accept_tcp_connection(lfd, 0)) < 0) {
                        syslog(LOG_ERR, "cant accept tcp connection on %d", lfd);
                        sleep(1);
                } else {
			handle_connection(fd, lfd, argv[2]);
                        close(fd);
                }
		collect_dead_kids(0);
        }
}


syntax highlighted by Code2HTML, v. 0.9.1