/*
 * axconv.c - A simple AX.25 convers client - version 1.3
 *
 * Copyright (C) 1997 Tomi Manninen, OH2BNS, <tomi.manninen@hut.fi>.
 *
 * Compile with: gcc -Wall -O6 -o axconv axconv.c
 *
 *
 * Axconv is a simple AX.25 convers client intended to be launched
 * from ax25d. It connects to the conversd system specified and
 * optionally logs in with the user and channel specified. It does
 * the necessary end-of-line conversions and also buffers the traffic
 * in order to optimise the AX.25 channel usage slightly.
 *
 * Usage: axconv [-c <channel>] [-l] [-n <name>] [-p <paclen>] [-t <timeout>]
 *        <host> [<service>]
 *
 * Options:
 *          -c <channel>          The channel to log into. Defaults
 *                                to nothing. Only valid if name is
 *                                also specified.
 *
 *          -l                    Use LF as end-of-line instead of CR.
 *
 *          -n <name>             Use <name> when logging in to the
 *                                convers server. If omitted, no
 *                                automatic logging in is done.
 *
 *          -p <paclen>           Maximum frame length for the client
 *                                connection. Defaults to 256.
 *
 *          -t <timeout>          After <timeout> seconds of inactivity
 *                                from the client and convers server the
 *                                connection is closed. Defaults to
 *                                0 ie. no timeout.
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>

#define	BUFFERSIZE	4096
#define FLUSHTIMEOUT	500000L		/* 0.5 sec */

static char eol = '\r';

static char *usage_string = "Usage: axconv [-c <channel>] [-l] [-n <name>] [-p <paclen>] [-t <timeout>] <host> [<service>]";

#define PERROR(s)	fprintf(stderr, "%s: %s%c", (s), strerror(errno), eol)

static void alarm_handler(int sig)
{
	printf("*** Inactivity timeout%c", eol);
	exit(0);
}

/*
 * Convert <eol> to <CR><LF>
 */
static int convert_eol_crlf(char *inbuf, char *outbuf, int len)
{
	int i = 0;

	while (len-- > 0) {
		if (*inbuf == eol) {
			outbuf[i++] = '\r';
			outbuf[i++] = '\n';
		} else
			outbuf[i++] = *inbuf;
		inbuf++;
	}
	return i;
}

/*
 * Convert <CR><LF>, <CR><NUL> or a plain <LF> to a eol
 */
static int convert_crlf_eol(char *inbuf, char *outbuf, int len)
{
	int i = 0;

	while (len-- > 0) {
		if (*inbuf == '\r' && (inbuf[1] == '\n' || inbuf[1] == '\0')) {
			/*
			 * <CR><LF> or <CR><NUL>
			 */
			outbuf[i++] = eol;
			inbuf++;
			len--;
		} else if (*inbuf == '\n') {
			/*
			 * <LF>
			 */
			outbuf[i++] = eol;
		} else
			outbuf[i++] = *inbuf;
		inbuf++;
	}
	return i;
}

int main(int argc, char **argv)
{
	char inbuf[BUFFERSIZE], outbuf[BUFFERSIZE];
	char *buf;
	char *name = NULL, *channel = NULL;
	int paclen = 256, timeout = 0, len, fd;
	FILE *fp;
	fd_set fdset;
	struct timeval tv;
	struct sockaddr_in sin;
        struct hostent *hp;
        struct servent *sp;

	signal(SIGALRM, alarm_handler);

	while ((len = getopt(argc, argv, "c:ln:p:t:")) != -1) {
		switch (len) {
		case 'n':
			name = optarg;
			break;
		case 'l':
			eol = '\n';
			break;
		case 'c':
			channel = optarg;
			break;
		case 'p':
			paclen = atoi(optarg);
			break;
		case 't':
			timeout = atoi(optarg);
			break;
		case ':':
		case '?':
			fprintf(stderr, "%s%c", usage_string, eol);
			return 1;
		}
	}

	if (optind == argc) {
		fprintf(stderr, "%s%c", usage_string, eol);
		return 1;
	}

	if (paclen < 1) {
		fprintf(stderr, "Invalid paclen: %d%c", paclen, eol);
		return 1;
	}

	if ((buf = malloc(paclen)) == NULL) {
		PERROR("axconv: malloc");
		return 1;
	}

	setvbuf(stdout, buf, _IOFBF, paclen);

	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		PERROR("axconv: socket");
		return 1;
	}

	if ((hp = gethostbyname(argv[optind])) == NULL) {
		fprintf(stderr, "Unknown host %s%c", argv[optind], eol);
		close(fd);
		return 1;
	}
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr))->s_addr;

	sin.sin_port = htons(3600);
	if (optind == argc - 2) {
		sp = getservbyname(argv[optind + 1], "tcp");
		if (sp != NULL)
			sin.sin_port = sp->s_port;
		else
			sin.sin_port = htons(atoi(argv[optind + 1]));

		if (ntohs(sin.sin_port) == 0) {
			fprintf(stderr, "Unknown service %s%c", argv[optind + 1], eol);
			return 1;
		}
	}

	printf("*** Connecting to %s, port %d%c", hp->h_name, ntohs(sin.sin_port), eol);
	fflush(stdout);

	len = sizeof(struct sockaddr_in);
	if (connect(fd, (struct sockaddr *) &sin, len) == -1) {
		PERROR("axconv: connect");
		return 1;
	}
	if ((fp = fdopen(fd, "w+")) == NULL) {
		PERROR("axconv: fdopen");
		return 1;
	}

	if (fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK) == -1) {
		PERROR("axconv: fcntl");
		exit(1);
	}
	if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
		PERROR("axconv: fcntl");
		exit(1);
	}

	if (name != NULL) {
		printf("*** Logging in as %s", name);
		if (channel != NULL)
			printf(", channel %s", channel);
		fputc(eol, stdout);
		fprintf(fp, "/n %s %s\r\n", name, channel ? channel : "");
		fflush(stdout);
		fflush(fp);
	}

	alarm(timeout);

	while (1) {
		FD_ZERO(&fdset);
		FD_SET(STDIN_FILENO, &fdset);
		FD_SET(fd, &fdset);
		tv.tv_sec = 0;
		tv.tv_usec = FLUSHTIMEOUT;

		len = select(fd + 1, &fdset, 0, 0, &tv);
		if (len == -1) {
			PERROR("axconv: select");
			return 1;
		}
		if (len == 0) {
			fflush(stdout);
			fflush(fp);
		}

		if (FD_ISSET(STDIN_FILENO, &fdset)) {
			alarm(timeout);
			len = read(STDIN_FILENO, inbuf, BUFFERSIZE);
			if (len < 0 && errno != EAGAIN) {
				PERROR("axconv: read");
				break;
			}
			if (len == 0)
				break;
			len = convert_eol_crlf(inbuf, outbuf, len);
			fwrite(outbuf, 1, len, fp);
		}

		if (FD_ISSET(fd, &fdset)) {
			alarm(timeout);
			len = read(fd, inbuf, BUFFERSIZE);
			if (len < 0 && errno != EAGAIN) {
				PERROR("axconv: read");
				break;
			}
			if (len == 0)
				break;
			len = convert_crlf_eol(inbuf, outbuf, len);
			fwrite(outbuf, 1, len, stdout);
		}
	}

	printf("*** Disconnected from %s%c", hp->h_name, eol);
	fflush(stdout);
	fclose(fp);
	free(buf);
	return 0;
}
