summaryrefslogtreecommitdiff
path: root/lib/telnet.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/telnet.c')
-rw-r--r--lib/telnet.c937
1 files changed, 937 insertions, 0 deletions
diff --git a/lib/telnet.c b/lib/telnet.c
new file mode 100644
index 000000000..8ca12450d
--- /dev/null
+++ b/lib/telnet.c
@@ -0,0 +1,937 @@
+/*****************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * The Original Code is Curl.
+ *
+ * The Initial Developer of the Original Code is Daniel Stenberg.
+ *
+ * Portions created by the Initial Developer are Copyright (C) 1998.
+ * All Rights Reserved.
+ *
+ * ------------------------------------------------------------
+ * Main author:
+ * - Daniel Stenberg <Daniel.Stenberg@haxx.nu>
+ *
+ * http://curl.haxx.nu
+ *
+ * $Source$
+ * $Revision$
+ * $Date$
+ * $Author$
+ * $State$
+ * $Locker$
+ *
+ * ------------------------------------------------------------
+ *
+ * This implementation of the TELNET protocol is written by
+ * Linus Nielsen <Linus.Nielsen@haxx.nu>,
+ * with some code snippets stolen from the BSD Telnet client.
+ *
+ * The negotiation is performed according to RFC 1143 (D. Bernstein,
+ * "The Q Method of Implementing TELNET Option Negotiation")
+ *
+ ****************************************************************************/
+
+/* -- WIN32 approved -- */
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+
+#include "setup.h"
+
+#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
+#include <winsock.h>
+#include <time.h>
+#include <io.h>
+#else
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#include <netinet/in.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <netdb.h>
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_NET_IF_H
+#include <net/if.h>
+#endif
+#include <sys/ioctl.h>
+#include <signal.h>
+
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+
+#endif
+
+#include "urldata.h"
+#include <curl/curl.h>
+#include "download.h"
+#include "sendf.h"
+#include "formdata.h"
+#include "progress.h"
+
+#define _MPRINTF_REPLACE /* use our functions only */
+#include <curl/mprintf.h>
+
+#define TELOPTS
+#define TELCMDS
+#define SLC_NAMES
+
+#include "arpa_telnet.h"
+
+#define SUBBUFSIZE 512
+
+#define SB_CLEAR() subpointer = subbuffer;
+#define SB_TERM() { subend = subpointer; SB_CLEAR(); }
+#define SB_ACCUM(c) if (subpointer < (subbuffer+sizeof subbuffer)) { \
+ *subpointer++ = (c); \
+ }
+
+#define SB_GET() ((*subpointer++)&0xff)
+#define SB_PEEK() ((*subpointer)&0xff)
+#define SB_EOF() (subpointer >= subend)
+#define SB_LEN() (subend - subpointer)
+
+void telwrite(struct UrlData *data,
+ unsigned char *buffer, /* Data to write */
+ int count); /* Number of bytes to write */
+
+void telrcv(struct UrlData *data,
+ unsigned char *inbuf, /* Data received from socket */
+ int count); /* Number of bytes received */
+
+static void printoption(struct UrlData *data,
+ const char *direction,
+ int cmd, int option);
+
+static void negotiate(struct UrlData *data);
+static void send_negotiation(struct UrlData *data, int cmd, int option);
+static void set_local_option(struct UrlData *data, int cmd, int option);
+static void set_remote_option(struct UrlData *data, int cmd, int option);
+
+static void printsub(struct UrlData *data,
+ int direction, unsigned char *pointer, int length);
+static void suboption(struct UrlData *data);
+
+/* suboptions */
+static char subbuffer[SUBBUFSIZE];
+static char *subpointer, *subend; /* buffer for sub-options */
+
+/*
+ * Telnet receiver states for fsm
+ */
+static enum
+{
+ TS_DATA = 0,
+ TS_IAC,
+ TS_WILL,
+ TS_WONT,
+ TS_DO,
+ TS_DONT,
+ TS_CR,
+ TS_SB, /* sub-option collection */
+ TS_SE /* looking for sub-option end */
+} telrcv_state;
+
+/* For negotiation compliant to RFC 1143 */
+#define NO 0
+#define YES 1
+#define WANTYES 2
+#define WANTNO 3
+
+#define EMPTY 0
+#define OPPOSITE 1
+
+static int us[256];
+static int usq[256];
+static int us_preferred[256];
+static int him[256];
+static int himq[256];
+static int him_preferred[256];
+
+void init_telnet(struct UrlData *data)
+{
+ telrcv_state = TS_DATA;
+
+ /* Init suboptions */
+ SB_CLEAR();
+
+ /* Set all options to NO */
+ memset(us, NO, 256);
+ memset(usq, NO, 256);
+ memset(us_preferred, NO, 256);
+ memset(him, NO, 256);
+ memset(himq, NO, 256);
+ memset(him_preferred, NO, 256);
+
+ /* Set the options we want */
+ us_preferred[TELOPT_BINARY] = YES;
+ us_preferred[TELOPT_SGA] = YES;
+ him_preferred[TELOPT_BINARY] = YES;
+ him_preferred[TELOPT_SGA] = YES;
+
+ /* Start negotiating */
+ negotiate(data);
+}
+
+static void negotiate(struct UrlData *data)
+{
+ int i;
+
+ for(i = 0;i < NTELOPTS;i++)
+ {
+ if(us_preferred[i] == YES)
+ set_local_option(data, i, YES);
+
+ if(him_preferred[i] == YES)
+ set_remote_option(data, i, YES);
+ }
+}
+
+static void printoption(struct UrlData *data,
+ const char *direction, int cmd, int option)
+{
+ char *fmt;
+ char *opt;
+
+ if (data->conf & CONF_VERBOSE)
+ {
+ if (cmd == IAC)
+ {
+ if (TELCMD_OK(option))
+ printf("%s IAC %s\n", direction, TELCMD(option));
+ else
+ printf("%s IAC %d\n", direction, option);
+ }
+ else
+ {
+ fmt = (cmd == WILL) ? "WILL" : (cmd == WONT) ? "WONT" :
+ (cmd == DO) ? "DO" : (cmd == DONT) ? "DONT" : 0;
+ if (fmt)
+ {
+ if (TELOPT_OK(option))
+ opt = TELOPT(option);
+ else if (option == TELOPT_EXOPL)
+ opt = "EXOPL";
+ else
+ opt = NULL;
+
+ if(opt)
+ printf("%s %s %s\n", direction, fmt, opt);
+ else
+ printf("%s %s %d\n", direction, fmt, option);
+ }
+ else
+ printf("%s %d %d\n", direction, cmd, option);
+ }
+ }
+}
+
+static void send_negotiation(struct UrlData *data, int cmd, int option)
+{
+ unsigned char buf[3];
+
+ buf[0] = IAC;
+ buf[1] = cmd;
+ buf[2] = option;
+
+ swrite(data->firstsocket, buf, 3);
+
+ printoption(data, "SENT", cmd, option);
+}
+
+void set_remote_option(struct UrlData *data, int option, int newstate)
+{
+ if(newstate == YES)
+ {
+ switch(him[option])
+ {
+ case NO:
+ him[option] = WANTYES;
+ send_negotiation(data, DO, option);
+ break;
+
+ case YES:
+ /* Already enabled */
+ break;
+
+ case WANTNO:
+ switch(himq[option])
+ {
+ case EMPTY:
+ /* Already negotiating for YES, queue the request */
+ himq[option] = OPPOSITE;
+ break;
+ case OPPOSITE:
+ /* Error: already queued an enable request */
+ break;
+ }
+ break;
+
+ case WANTYES:
+ switch(himq[option])
+ {
+ case EMPTY:
+ /* Error: already negotiating for enable */
+ break;
+ case OPPOSITE:
+ himq[option] = EMPTY;
+ break;
+ }
+ break;
+ }
+ }
+ else /* NO */
+ {
+ switch(him[option])
+ {
+ case NO:
+ /* Already disabled */
+ break;
+
+ case YES:
+ him[option] = WANTNO;
+ send_negotiation(data, DONT, option);
+ break;
+
+ case WANTNO:
+ switch(himq[option])
+ {
+ case EMPTY:
+ /* Already negotiating for NO */
+ break;
+ case OPPOSITE:
+ himq[option] = EMPTY;
+ break;
+ }
+ break;
+
+ case WANTYES:
+ switch(himq[option])
+ {
+ case EMPTY:
+ himq[option] = OPPOSITE;
+ break;
+ case OPPOSITE:
+ break;
+ }
+ break;
+ }
+ }
+}
+
+void rec_will(struct UrlData *data, int option)
+{
+ switch(him[option])
+ {
+ case NO:
+ if(him_preferred[option] == YES)
+ {
+ him[option] = YES;
+ send_negotiation(data, DO, option);
+ }
+ else
+ {
+ send_negotiation(data, DONT, option);
+ }
+ break;
+
+ case YES:
+ /* Already enabled */
+ break;
+
+ case WANTNO:
+ switch(himq[option])
+ {
+ case EMPTY:
+ /* Error: DONT answered by WILL */
+ him[option] = NO;
+ break;
+ case OPPOSITE:
+ /* Error: DONT answered by WILL */
+ him[option] = YES;
+ himq[option] = EMPTY;
+ break;
+ }
+ break;
+
+ case WANTYES:
+ switch(himq[option])
+ {
+ case EMPTY:
+ him[option] = YES;
+ break;
+ case OPPOSITE:
+ him[option] = WANTNO;
+ himq[option] = EMPTY;
+ send_negotiation(data, DONT, option);
+ break;
+ }
+ break;
+ }
+}
+
+void rec_wont(struct UrlData *data, int option)
+{
+ switch(him[option])
+ {
+ case NO:
+ /* Already disabled */
+ break;
+
+ case YES:
+ him[option] = NO;
+ send_negotiation(data, DONT, option);
+ break;
+
+ case WANTNO:
+ switch(himq[option])
+ {
+ case EMPTY:
+ him[option] = NO;
+ break;
+
+ case OPPOSITE:
+ him[option] = WANTYES;
+ himq[option] = EMPTY;
+ send_negotiation(data, DO, option);
+ break;
+ }
+ break;
+
+ case WANTYES:
+ switch(himq[option])
+ {
+ case EMPTY:
+ him[option] = NO;
+ break;
+ case OPPOSITE:
+ him[option] = NO;
+ himq[option] = EMPTY;
+ break;
+ }
+ break;
+ }
+}
+
+void set_local_option(struct UrlData *data, int option, int newstate)
+{
+ if(newstate == YES)
+ {
+ switch(us[option])
+ {
+ case NO:
+ us[option] = WANTYES;
+ send_negotiation(data, WILL, option);
+ break;
+
+ case YES:
+ /* Already enabled */
+ break;
+
+ case WANTNO:
+ switch(usq[option])
+ {
+ case EMPTY:
+ /* Already negotiating for YES, queue the request */
+ usq[option] = OPPOSITE;
+ break;
+ case OPPOSITE:
+ /* Error: already queued an enable request */
+ break;
+ }
+ break;
+
+ case WANTYES:
+ switch(usq[option])
+ {
+ case EMPTY:
+ /* Error: already negotiating for enable */
+ break;
+ case OPPOSITE:
+ usq[option] = EMPTY;
+ break;
+ }
+ break;
+ }
+ }
+ else /* NO */
+ {
+ switch(us[option])
+ {
+ case NO:
+ /* Already disabled */
+ break;
+
+ case YES:
+ us[option] = WANTNO;
+ send_negotiation(data, WONT, option);
+ break;
+
+ case WANTNO:
+ switch(usq[option])
+ {
+ case EMPTY:
+ /* Already negotiating for NO */
+ break;
+ case OPPOSITE:
+ usq[option] = EMPTY;
+ break;
+ }
+ break;
+
+ case WANTYES:
+ switch(usq[option])
+ {
+ case EMPTY:
+ usq[option] = OPPOSITE;
+ break;
+ case OPPOSITE:
+ break;
+ }
+ break;
+ }
+ }
+}
+
+void rec_do(struct UrlData *data, int option)
+{
+ switch(us[option])
+ {
+ case NO:
+ if(us_preferred[option] == YES)
+ {
+ us[option] = YES;
+ send_negotiation(data, WILL, option);
+ }
+ else
+ {
+ send_negotiation(data, WONT, option);
+ }
+ break;
+
+ case YES:
+ /* Already enabled */
+ break;
+
+ case WANTNO:
+ switch(usq[option])
+ {
+ case EMPTY:
+ /* Error: DONT answered by WILL */
+ us[option] = NO;
+ break;
+ case OPPOSITE:
+ /* Error: DONT answered by WILL */
+ us[option] = YES;
+ usq[option] = EMPTY;
+ break;
+ }
+ break;
+
+ case WANTYES:
+ switch(usq[option])
+ {
+ case EMPTY:
+ us[option] = YES;
+ break;
+ case OPPOSITE:
+ us[option] = WANTNO;
+ himq[option] = EMPTY;
+ send_negotiation(data, WONT, option);
+ break;
+ }
+ break;
+ }
+}
+
+void rec_dont(struct UrlData *data, int option)
+{
+ switch(us[option])
+ {
+ case NO:
+ /* Already disabled */
+ break;
+
+ case YES:
+ us[option] = NO;
+ send_negotiation(data, WONT, option);
+ break;
+
+ case WANTNO:
+ switch(usq[option])
+ {
+ case EMPTY:
+ us[option] = NO;
+ break;
+
+ case OPPOSITE:
+ us[option] = WANTYES;
+ usq[option] = EMPTY;
+ send_negotiation(data, WILL, option);
+ break;
+ }
+ break;
+
+ case WANTYES:
+ switch(usq[option])
+ {
+ case EMPTY:
+ us[option] = NO;
+ break;
+ case OPPOSITE:
+ us[option] = NO;
+ usq[option] = EMPTY;
+ break;
+ }
+ break;
+ }
+}
+
+
+static void printsub(struct UrlData *data,
+ int direction, /* '<' or '>' */
+ unsigned char *pointer, /* where suboption data is */
+ int length) /* length of suboption data */
+
+{
+ int i = 0;
+
+ if (data->conf & CONF_VERBOSE)
+ {
+ if (direction)
+ {
+ printf("%s IAC SB ", (direction == '<')? "RCVD":"SENT");
+ if (length >= 3)
+ {
+ int j;
+
+ i = pointer[length-2];
+ j = pointer[length-1];
+
+ if (i != IAC || j != SE)
+ {
+ printf("(terminated by ");
+ if (TELOPT_OK(i))
+ printf("%s ", TELOPT(i));
+ else if (TELCMD_OK(i))
+ printf("%s ", TELCMD(i));
+ else
+ printf("%d ", i);
+ if (TELOPT_OK(j))
+ printf("%s", TELOPT(j));
+ else if (TELCMD_OK(j))
+ printf("%s", TELCMD(j));
+ else
+ printf("%d", j);
+ printf(", not IAC SE!) ");
+ }
+ }
+ length -= 2;
+ }
+ if (length < 1)
+ {
+ printf("(Empty suboption?)");
+ return;
+ }
+
+ if (TELOPT_OK(pointer[0]))
+ printf("%s (unknown)", TELOPT(pointer[0]));
+ else
+ printf("%d (unknown)", pointer[i]);
+ for (i = 1; i < length; i++)
+ printf(" %d", pointer[i]);
+
+ if (direction)
+ {
+ printf("\n");
+ }
+ }
+}
+
+/*
+ * suboption()
+ *
+ * Look at the sub-option buffer, and try to be helpful to the other
+ * side.
+ * No suboptions are supported yet.
+ */
+
+static void suboption(struct UrlData *data)
+{
+ printsub(data, '<', (unsigned char *)subbuffer, SB_LEN()+2);
+ return;
+}
+
+void telrcv(struct UrlData *data,
+ unsigned char *inbuf, /* Data received from socket */
+ int count) /* Number of bytes received */
+{
+ unsigned char c;
+ int index = 0;
+
+ while(count--)
+ {
+ c = inbuf[index++];
+
+ switch (telrcv_state)
+ {
+ case TS_CR:
+ telrcv_state = TS_DATA;
+ if (c == '\0')
+ {
+ break; /* Ignore \0 after CR */
+ }
+
+ data->fwrite((char *)&c, 1, 1, data->out);
+ continue;
+
+ case TS_DATA:
+ if (c == IAC)
+ {
+ telrcv_state = TS_IAC;
+ break;
+ }
+ else if(c == '\r')
+ {
+ telrcv_state = TS_CR;
+ }
+
+ data->fwrite((char *)&c, 1, 1, data->out);
+ continue;
+
+ case TS_IAC:
+ process_iac:
+ switch (c)
+ {
+ case WILL:
+ telrcv_state = TS_WILL;
+ continue;
+ case WONT:
+ telrcv_state = TS_WONT;
+ continue;
+ case DO:
+ telrcv_state = TS_DO;
+ continue;
+ case DONT:
+ telrcv_state = TS_DONT;
+ continue;
+ case SB:
+ SB_CLEAR();
+ telrcv_state = TS_SB;
+ continue;
+ case IAC:
+ data->fwrite((char *)&c, 1, 1, data->out);
+ break;
+ case DM:
+ case NOP:
+ case GA:
+ default:
+ printoption(data, "RCVD", IAC, c);
+ break;
+ }
+ telrcv_state = TS_DATA;
+ continue;
+
+ case TS_WILL:
+ printoption(data, "RCVD", WILL, c);
+ rec_will(data, c);
+ telrcv_state = TS_DATA;
+ continue;
+
+ case TS_WONT:
+ printoption(data, "RCVD", WONT, c);
+ rec_wont(data, c);
+ telrcv_state = TS_DATA;
+ continue;
+
+ case TS_DO:
+ printoption(data, "RCVD", DO, c);
+ rec_do(data, c);
+ telrcv_state = TS_DATA;
+ continue;
+
+ case TS_DONT:
+ printoption(data, "RCVD", DONT, c);
+ rec_dont(data, c);
+ telrcv_state = TS_DATA;
+ continue;
+
+ case TS_SB:
+ if (c == IAC)
+ {
+ telrcv_state = TS_SE;
+ }
+ else
+ {
+ SB_ACCUM(c);
+ }
+ continue;
+
+ case TS_SE:
+ if (c != SE)
+ {
+ if (c != IAC)
+ {
+ /*
+ * This is an error. We only expect to get
+ * "IAC IAC" or "IAC SE". Several things may
+ * have happend. An IAC was not doubled, the
+ * IAC SE was left off, or another option got
+ * inserted into the suboption are all possibilities.
+ * If we assume that the IAC was not doubled,
+ * and really the IAC SE was left off, we could
+ * get into an infinate loop here. So, instead,
+ * we terminate the suboption, and process the
+ * partial suboption if we can.
+ */
+ SB_ACCUM((unsigned char)IAC);
+ SB_ACCUM(c);
+ subpointer -= 2;
+ SB_TERM();
+
+ printoption(data, "In SUBOPTION processing, RCVD", IAC, c);
+ suboption(data); /* handle sub-option */
+ telrcv_state = TS_IAC;
+ goto process_iac;
+ }
+ SB_ACCUM(c);
+ telrcv_state = TS_SB;
+ }
+ else
+ {
+ SB_ACCUM((unsigned char)IAC);
+ SB_ACCUM((unsigned char)SE);
+ subpointer -= 2;
+ SB_TERM();
+ suboption(data); /* handle sub-option */
+ telrcv_state = TS_DATA;
+ }
+ break;
+ }
+ }
+}
+
+void telwrite(struct UrlData *data,
+ unsigned char *buffer, /* Data to write */
+ int count) /* Number of bytes to write */
+{
+ unsigned char outbuf[2];
+ int out_count = 0;
+ int bytes_written;
+
+ while(count--)
+ {
+ outbuf[0] = *buffer++;
+ out_count = 1;
+ if(outbuf[0] == IAC)
+ outbuf[out_count++] = IAC;
+
+#ifndef USE_SSLEAY
+ bytes_written = swrite(data->firstsocket, outbuf, out_count);
+#else
+ if (data->use_ssl) {
+ bytes_written = SSL_write(data->ssl, (char *)outbuf, out_count);
+ }
+ else {
+ bytes_written = swrite(data->firstsocket, outbuf, out_count);
+ }
+#endif /* USE_SSLEAY */
+ }
+}
+
+UrgError telnet(struct UrlData *data)
+{
+ int sockfd = data->firstsocket;
+ fd_set readfd;
+ fd_set keepfd;
+
+ bool keepon = TRUE;
+ char *buf = data->buffer;
+ int nread;
+
+ init_telnet(data);
+
+ FD_ZERO (&readfd); /* clear it */
+ FD_SET (sockfd, &readfd);
+ FD_SET (1, &readfd);
+
+ keepfd = readfd;
+
+ while (keepon)
+ {
+ readfd = keepfd; /* set this every lap in the loop */
+
+ switch (select (sockfd + 1, &readfd, NULL, NULL, NULL))
+ {
+ case -1: /* error, stop reading */
+ keepon = FALSE;
+ continue;
+ case 0: /* timeout */
+ break;
+ default: /* read! */
+ if(FD_ISSET(1, &readfd))
+ {
+ nread = read(1, buf, 255);
+ telwrite(data, (unsigned char *)buf, nread);
+ }
+
+ if(FD_ISSET(sockfd, &readfd))
+ {
+#ifndef USE_SSLEAY
+ nread = sread (sockfd, buf, BUFSIZE - 1);
+#else
+ if (data->use_ssl) {
+ nread = SSL_read (data->ssl, buf, BUFSIZE - 1);
+ }
+ else {
+ nread = sread (sockfd, buf, BUFSIZE - 1);
+ }
+#endif /* USE_SSLEAY */
+ }
+
+ /* if we receive 0 or less here, the server closed the connection and
+ we bail out from this! */
+ if (nread <= 0) {
+ keepon = FALSE;
+ break;
+ }
+
+ telrcv(data, (unsigned char *)buf, nread);
+ }
+ }
+ return URG_OK;
+}
+
+