add "ipaccess-telnet" support, i.e. telnetting to an ip.access nanoBTS

There is a special MD5-challenge/response authentication after
establishing the telnet session to an ip.access nanoBTS / TCP port 3210.

The ipaccess-telnet program supports this mode of operation
diff --git a/util/Makefile.am b/util/Makefile.am
index 4500cea..7134d68 100644
--- a/util/Makefile.am
+++ b/util/Makefile.am
@@ -1,6 +1,9 @@
 AM_CFLAGS = -I..
 AM_LDFLAGS = -L..
 
+ipaccess_telnet_SOURCES = telnet-client-ipa.c ../libtelnet.h
+ipaccess_telnet_LDADD = ../libtelnet.la -lssl
+
 telnet_client_SOURCES = telnet-client.c ../libtelnet.h
 telnet_client_LDADD = ../libtelnet.la
 
@@ -10,4 +13,4 @@
 telnet_proxy_SOURCES = telnet-proxy.c ../libtelnet.h
 telnet_proxy_LDADD = ../libtelnet.la
 
-bin_PROGRAMS = telnet-client telnet-chatd telnet-proxy
+bin_PROGRAMS = telnet-client telnet-chatd telnet-proxy ipaccess-telnet
diff --git a/util/telnet-client-ipa.c b/util/telnet-client-ipa.c
new file mode 100644
index 0000000..4e22e20
--- /dev/null
+++ b/util/telnet-client-ipa.c
@@ -0,0 +1,327 @@
+/*
+ * Sean Middleditch
+ * sean@sourcemud.org
+ *
+ * The author or authors of this code dedicate any and all copyright interest
+ * in this code to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and successors. We
+ * intend this dedication to be an overt act of relinquishment in perpetuity of
+ * all present and future rights to this code under copyright law. 
+ */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <poll.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <termios.h>
+#include <unistd.h>
+
+#ifdef HAVE_ZLIB
+#include "zlib.h"
+#endif
+
+#include "libtelnet.h"
+
+#define NANO_BTS_CLI_CLIENT 1
+
+#if NANO_BTS_CLI_CLIENT
+  #include <openssl/md5.h>
+#endif
+
+static struct termios orig_tios;
+static telnet_t *telnet;
+static int do_echo;
+
+static const telnet_telopt_t telopts[] = {
+	{ TELNET_TELOPT_ECHO,		TELNET_WONT, TELNET_DO   },
+	{ TELNET_TELOPT_TTYPE,		TELNET_WILL, TELNET_DONT },
+	{ TELNET_TELOPT_COMPRESS2,	TELNET_WONT, TELNET_DO   },
+	{ TELNET_TELOPT_MSSP,		TELNET_WONT, TELNET_DO   },
+	{ -1, 0, 0 }
+};
+
+static void _cleanup(void) {
+	tcsetattr(STDOUT_FILENO, TCSADRAIN, &orig_tios);
+}
+
+static void _input(char *buffer, int size) {
+	static char crlf[] = { '\r', '\n' };
+	int i;
+
+	for (i = 0; i != size; ++i) {
+		/* if we got a CR or LF, replace with CRLF
+		 * NOTE that usually you'd get a CR in UNIX, but in raw
+		 * mode we get LF instead (not sure why)
+		 */
+		if (buffer[i] == '\r' || buffer[i] == '\n') {
+			if (do_echo)
+				printf("\r\n");
+			telnet_send(telnet, crlf, 2);
+		} else {
+			if (do_echo)
+				putchar(buffer[i]);
+			telnet_send(telnet, buffer + i, 1);
+		}
+	}
+	fflush(stdout);
+}
+
+static void _send(int sock, const char *buffer, size_t size) {
+	int rs;
+
+	/* send data */
+	while (size > 0) {
+		if ((rs = send(sock, buffer, size, 0)) == -1) {
+			fprintf(stderr, "send() failed: %s\n", strerror(errno));
+			exit(1);
+		} else if (rs == 0) {
+			fprintf(stderr, "send() unexpectedly returned 0\n");
+			exit(1);
+		}
+
+		/* update pointer and size to see if we've got more to send */
+		buffer += rs;
+		size -= rs;
+	}
+}
+
+static void _event_handler(telnet_t *telnet, telnet_event_t *ev,
+		void *user_data) {
+	int sock = *(int*)user_data;
+
+	switch (ev->type) {
+	/* data received */
+	case TELNET_EV_DATA:
+		printf("%.*s", (int)ev->size, ev->buffer);
+		break;
+	/* data must be sent */
+	case TELNET_EV_SEND:
+		_send(sock, ev->buffer, ev->size);
+		break;
+	/* request to enable remote feature (or receipt) */
+	case TELNET_EV_WILL:
+		/* we'll agree to turn off our echo if server wants us to stop */
+		if (ev->telopt == TELNET_TELOPT_ECHO)
+			do_echo = 0;
+		break;
+	/* notification of disabling remote feature (or receipt) */
+	case TELNET_EV_WONT:
+		if (ev->telopt == TELNET_TELOPT_ECHO)
+			do_echo = 1;
+		break;
+	/* request to enable local feature (or receipt) */
+	case TELNET_EV_DO:
+		break;
+	/* demand to disable local feature (or receipt) */
+	case TELNET_EV_DONT:
+		break;
+	/* respond to particular subnegotiations */
+	case TELNET_EV_SUBNEGOTIATION:
+		/* if they just asked for our terminal type, response with it */
+		/* respond with our terminal type */
+		if (ev->telopt == TELNET_TELOPT_TTYPE &&
+				ev->argc >= 1 && ev->argv[0][0] == TELNET_TTYPE_SEND) {
+			telnet_format_sb(telnet, TELNET_TELOPT_TTYPE, 1,
+					TELNET_TTYPE_IS, getenv("TERM"));
+		}
+		break;
+	/* error */
+	case TELNET_EV_ERROR:
+		fprintf(stderr, "ERROR: %s\n", ev->buffer);
+		exit(1);
+	default:
+		/* ignore */
+		break;
+	}
+}
+
+#if NANO_BTS_CLI_CLIENT 
+
+#define KEY "Sh1n30nY0uCra2yD1am0nd"
+
+void compute_response(unsigned char *ubChallenge, unsigned char *ubResponse) 
+{
+  MD5_CTX md5;
+  int i;
+  
+  MD5_Init(&md5);
+  
+  for(i = 0; i < 4; i++)
+  {
+    MD5_Update(&md5, (unsigned char *)KEY, strlen(KEY));
+    MD5_Update(&md5, ubChallenge, 16);
+  }
+  
+  MD5_Final(ubResponse, &md5);
+}
+
+#endif
+
+int main(int argc, char **argv) {
+	char buffer[512];
+	int rs;
+	int sock;
+	struct sockaddr_in addr;
+	struct pollfd pfd[2];
+	struct addrinfo *ai;
+	struct addrinfo hints;
+	struct termios tios;
+
+	/* check usage */
+	if (argc != 3) {
+		fprintf(stderr, "Usage:\n ./telnet-client <host> <port>\n");
+		return 1;
+	}
+
+	/* look up server host */
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+	if ((rs = getaddrinfo(argv[1], argv[2], &hints, &ai)) != 0) {
+		fprintf(stderr, "getaddrinfo() failed for %s: %s\n", argv[1],
+				gai_strerror(rs));
+		return 1;
+	}
+	
+	/* create server socket */
+	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+		fprintf(stderr, "socket() failed: %s\n", strerror(errno));
+		return 1;
+	}
+
+	/* bind server socket */
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+		fprintf(stderr, "bind() failed: %s\n", strerror(errno));
+		return 1;
+	}
+
+	/* connect */
+	if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
+		fprintf(stderr, "server() failed: %s\n", strerror(errno));
+		return 1;
+	}
+
+	/* free address lookup info */
+	freeaddrinfo(ai);
+
+#if NANO_BTS_CLI_CLIENT 
+
+    {
+        // nanoBTS Challenge/Response
+        
+        unsigned char  ubResponse[18];
+        
+        #define CLI_USER "CLICLIENT\n"
+    
+        // send client name
+        
+    	if ((rs = send(sock, CLI_USER, strlen(CLI_USER), 0)) == -1) {
+			fprintf(stderr, "send() failed: %s\n", strerror(errno));
+			exit(1);
+		} else if (rs == 0) {
+			fprintf(stderr, "send() unexpectedly returned 0\n");
+			exit(1);
+		}
+		
+        // receive challenge
+
+        if ((rs = recv(sock, buffer, sizeof(buffer), 0)) > 0) {
+            if(rs != 18 || buffer[0] != '<' || buffer[17] != '>') {
+              fprintf(stderr, "unexpected response\n");
+              exit(1);
+            }
+        } else if (rs == 0) {
+			fprintf(stderr, "recv(client) unexpectedly returned 0\n");
+			exit(1);
+        } else {
+            fprintf(stderr, "recv(client) failed: %s\n",
+                    strerror(errno));
+            exit(1);
+        }
+        
+        // calculate response
+        
+        memset(&ubResponse, 0, sizeof(ubResponse));
+  		compute_response(buffer + 1, ubResponse + 1);
+        ubResponse[0] = '<';
+        ubResponse[17] = '>';
+        
+        // send response
+        
+    	if ((rs = send(sock, ubResponse, sizeof(ubResponse), 0)) == -1) {
+			fprintf(stderr, "send() failed: %s\n", strerror(errno));
+			exit(1);
+		} else if (rs == 0) {
+			fprintf(stderr, "send() unexpectedly returned 0\n");
+			exit(1);
+		}        
+    }
+    
+#endif
+
+	/* get current terminal settings, set raw mode, make sure we
+	 * register atexit handler to restore terminal settings
+	 */
+
+	tcgetattr(STDOUT_FILENO, &orig_tios);
+	atexit(_cleanup);
+	tios = orig_tios;
+	cfmakeraw(&tios);
+	tcsetattr(STDOUT_FILENO, TCSADRAIN, &tios);
+
+	/* set input echoing on by default */
+	do_echo = 1;
+
+	/* initialize telnet box */
+	telnet = telnet_init(telopts, _event_handler, 0, &sock);
+
+	/* initialize poll descriptors */
+	memset(pfd, 0, sizeof(pfd));
+	pfd[0].fd = STDIN_FILENO;
+	pfd[0].events = POLLIN;
+	pfd[1].fd = sock;
+	pfd[1].events = POLLIN;
+	/* loop while both connections are open */
+	while (poll(pfd, 2, -1) != -1) {
+		/* read from stdin */
+		if (pfd[0].revents & POLLIN) {
+			if ((rs = read(STDIN_FILENO, buffer, sizeof(buffer))) > 0) {
+				_input(buffer, rs);
+			} else if (rs == 0) {
+				break;
+			} else {
+				fprintf(stderr, "recv(server) failed: %s\n",
+						strerror(errno));
+				exit(1);
+			}
+		}
+
+		/* read from client */
+		if (pfd[1].revents & POLLIN) {
+			if ((rs = recv(sock, buffer, sizeof(buffer), 0)) > 0) {
+				telnet_recv(telnet, buffer, rs);
+			} else if (rs == 0) {
+				break;
+			} else {
+				fprintf(stderr, "recv(client) failed: %s\n",
+						strerror(errno));
+				exit(1);
+			}
+		}
+	}
+
+	/* clean up */
+	telnet_free(telnet);
+	close(sock);
+
+	return 0;
+}
+