/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://curl.haxx.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * * $Id: telnet.c,v 1.67 2004/10/06 07:50:18 bagder Exp $ ***************************************************************************/ #include "setup.h" #ifndef CURL_DISABLE_TELNET /* -- WIN32 approved -- */ #include #include #include #include #include #include #include #include #if defined( WIN32 ) #include #include #else #ifdef HAVE_SYS_SOCKET_H #include #endif #include #include #ifdef HAVE_UNISTD_H #include #endif #include #ifdef HAVE_ARPA_INET_H #include #endif #ifdef HAVE_NET_IF_H #include #endif #include #include #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_SYS_SELECT_H #include #endif #endif #include "urldata.h" #include #include "transfer.h" #include "sendf.h" #include "telnet.h" #define _MPRINTF_REPLACE /* use our functions only */ #include #define TELOPTS #define TELCMDS #include "arpa_telnet.h" #include "memory.h" /* The last #include file should be: */ #include "memdebug.h" #define SUBBUFSIZE 512 #define CURL_SB_CLEAR( x ) x->subpointer = x->subbuffer; #define CURL_SB_TERM( x ) { x->subend = x->subpointer; CURL_SB_CLEAR( x ); } #define CURL_SB_ACCUM( x,c ) \ if ( x->subpointer < ( x->subbuffer + sizeof x->subbuffer ) ) { \ *x->subpointer++ = ( c ); \ } #define CURL_SB_GET( x ) ( ( *x->subpointer++ ) & 0xff ) #define CURL_SB_PEEK( x ) ( ( *x->subpointer ) & 0xff ) #define CURL_SB_EOF( x ) ( x->subpointer >= x->subend ) #define CURL_SB_LEN( x ) ( x->subend - x->subpointer ) #ifdef WIN32 typedef FARPROC WSOCK2_FUNC; static CURLcode check_wsock2( struct SessionHandle *data ); #endif static void telrcv( struct connectdata *, unsigned char *inbuf, /* Data received from socket */ ssize_t count ); /* Number of bytes received */ static void printoption( struct SessionHandle *data, const char *direction, int cmd, int option ); static void negotiate( struct connectdata * ); static void send_negotiation( struct connectdata *, int cmd, int option ); static void set_local_option( struct connectdata *, int cmd, int option ); static void set_remote_option( struct connectdata *, int cmd, int option ); static void printsub( struct SessionHandle *data, int direction, unsigned char *pointer, size_t length ); static void suboption( struct connectdata * ); /* For negotiation compliant to RFC 1143 */ #define CURL_NO 0 #define CURL_YES 1 #define CURL_WANTYES 2 #define CURL_WANTNO 3 #define CURL_EMPTY 0 #define CURL_OPPOSITE 1 /* * Telnet receiver states for fsm */ typedef enum { CURL_TS_DATA = 0, CURL_TS_IAC, CURL_TS_WILL, CURL_TS_WONT, CURL_TS_DO, CURL_TS_DONT, CURL_TS_CR, CURL_TS_SB, /* sub-option collection */ CURL_TS_SE /* looking for sub-option end */ } TelnetReceive; struct TELNET { int please_negotiate; int already_negotiated; int us[256]; int usq[256]; int us_preferred[256]; int him[256]; int himq[256]; int him_preferred[256]; char subopt_ttype[32]; /* Set with suboption TTYPE */ char subopt_xdisploc[128]; /* Set with suboption XDISPLOC */ struct curl_slist *telnet_vars; /* Environment variables */ /* suboptions */ char subbuffer[SUBBUFSIZE]; char *subpointer, *subend; /* buffer for sub-options */ TelnetReceive telrcv_state; }; #ifdef WIN32 static CURLcode check_wsock2( struct SessionHandle *data ) { int err; WORD wVersionRequested; WSADATA wsaData; curlassert( data ); /* telnet requires at least WinSock 2.0 so ask for it. */ wVersionRequested = MAKEWORD( 2, 0 ); err = WSAStartup( wVersionRequested, &wsaData ); /* We must've called this once already, so this call */ /* should always succeed. But, just in case... */ if ( err != 0 ) { failf( data,"WSAStartup failed (%d)",err ); return CURLE_FAILED_INIT; } /* We have to have a WSACleanup call for every successful */ /* WSAStartup call. */ WSACleanup(); /* Check that our version is supported */ if ( LOBYTE( wsaData.wVersion ) != LOBYTE( wVersionRequested ) || HIBYTE( wsaData.wVersion ) != HIBYTE( wVersionRequested ) ) { /* Our version isn't supported */ failf( data,"insufficient winsock version to support " "telnet" ); return CURLE_FAILED_INIT; } /* Our version is supported */ return CURLE_OK; } #endif static CURLcode init_telnet( struct connectdata *conn ) { struct TELNET *tn; tn = (struct TELNET *)calloc( 1, sizeof( struct TELNET ) ); if ( !tn ) { return CURLE_OUT_OF_MEMORY; } conn->proto.telnet = (void *)tn; /* make us known */ tn->telrcv_state = CURL_TS_DATA; /* Init suboptions */ CURL_SB_CLEAR( tn ); /* Set the options we want by default */ tn->us_preferred[CURL_TELOPT_BINARY] = CURL_YES; tn->us_preferred[CURL_TELOPT_SGA] = CURL_YES; tn->him_preferred[CURL_TELOPT_BINARY] = CURL_YES; tn->him_preferred[CURL_TELOPT_SGA] = CURL_YES; return CURLE_OK; } static void negotiate( struct connectdata *conn ) { int i; struct TELNET *tn = (struct TELNET *)conn->proto.telnet; for ( i = 0; i < CURL_NTELOPTS; i++ ) { if ( tn->us_preferred[i] == CURL_YES ) { set_local_option( conn, i, CURL_YES ); } if ( tn->him_preferred[i] == CURL_YES ) { set_remote_option( conn, i, CURL_YES ); } } } static void printoption( struct SessionHandle *data, const char *direction, int cmd, int option ) { const char *fmt; const char *opt; if ( data->set.verbose ) { if ( cmd == CURL_IAC ) { if ( CURL_TELCMD_OK( option ) ) { Curl_infof( data, "%s IAC %s\n", direction, CURL_TELCMD( option ) ); } else { Curl_infof( data, "%s IAC %d\n", direction, option ); } } else { fmt = ( cmd == CURL_WILL ) ? "WILL" : ( cmd == CURL_WONT ) ? "WONT" : ( cmd == CURL_DO ) ? "DO" : ( cmd == CURL_DONT ) ? "DONT" : 0; if ( fmt ) { if ( CURL_TELOPT_OK( option ) ) { opt = CURL_TELOPT( option ); } else if ( option == CURL_TELOPT_EXOPL ) { opt = "EXOPL"; } else { opt = NULL; } if ( opt ) { Curl_infof( data, "%s %s %s\n", direction, fmt, opt ); } else { Curl_infof( data, "%s %s %d\n", direction, fmt, option ); } } else { Curl_infof( data, "%s %d %d\n", direction, cmd, option ); } } } } static void send_negotiation( struct connectdata *conn, int cmd, int option ) { unsigned char buf[3]; buf[0] = CURL_IAC; buf[1] = cmd; buf[2] = option; (void)swrite( conn->sock[FIRSTSOCKET], buf, 3 ); printoption( conn->data, "SENT", cmd, option ); } static void set_remote_option( struct connectdata *conn, int option, int newstate ) { struct TELNET *tn = (struct TELNET *)conn->proto.telnet; if ( newstate == CURL_YES ) { switch ( tn->him[option] ) { case CURL_NO: tn->him[option] = CURL_WANTYES; send_negotiation( conn, CURL_DO, option ); break; case CURL_YES: /* Already enabled */ break; case CURL_WANTNO: switch ( tn->himq[option] ) { case CURL_EMPTY: /* Already negotiating for CURL_YES, queue the request */ tn->himq[option] = CURL_OPPOSITE; break; case CURL_OPPOSITE: /* Error: already queued an enable request */ break; } break; case CURL_WANTYES: switch ( tn->himq[option] ) { case CURL_EMPTY: /* Error: already negotiating for enable */ break; case CURL_OPPOSITE: tn->himq[option] = CURL_EMPTY; break; } break; } } else /* NO */ { switch ( tn->him[option] ) { case CURL_NO: /* Already disabled */ break; case CURL_YES: tn->him[option] = CURL_WANTNO; send_negotiation( conn, CURL_DONT, option ); break; case CURL_WANTNO: switch ( tn->himq[option] ) { case CURL_EMPTY: /* Already negotiating for NO */ break; case CURL_OPPOSITE: tn->himq[option] = CURL_EMPTY; break; } break; case CURL_WANTYES: switch ( tn->himq[option] ) { case CURL_EMPTY: tn->himq[option] = CURL_OPPOSITE; break; case CURL_OPPOSITE: break; } break; } } } static void rec_will( struct connectdata *conn, int option ) { struct TELNET *tn = (struct TELNET *)conn->proto.telnet; switch ( tn->him[option] ) { case CURL_NO: if ( tn->him_preferred[option] == CURL_YES ) { tn->him[option] = CURL_YES; send_negotiation( conn, CURL_DO, option ); } else { send_negotiation( conn, CURL_DONT, option ); } break; case CURL_YES: /* Already enabled */ break; case CURL_WANTNO: switch ( tn->himq[option] ) { case CURL_EMPTY: /* Error: DONT answered by WILL */ tn->him[option] = CURL_NO; break; case CURL_OPPOSITE: /* Error: DONT answered by WILL */ tn->him[option] = CURL_YES; tn->himq[option] = CURL_EMPTY; break; } break; case CURL_WANTYES: switch ( tn->himq[option] ) { case CURL_EMPTY: tn->him[option] = CURL_YES; break; case CURL_OPPOSITE: tn->him[option] = CURL_WANTNO; tn->himq[option] = CURL_EMPTY; send_negotiation( conn, CURL_DONT, option ); break; } break; } } static void rec_wont( struct connectdata *conn, int option ) { struct TELNET *tn = (struct TELNET *)conn->proto.telnet; switch ( tn->him[option] ) { case CURL_NO: /* Already disabled */ break; case CURL_YES: tn->him[option] = CURL_NO; send_negotiation( conn, CURL_DONT, option ); break; case CURL_WANTNO: switch ( tn->himq[option] ) { case CURL_EMPTY: tn->him[option] = CURL_NO; break; case CURL_OPPOSITE: tn->him[option] = CURL_WANTYES; tn->himq[option] = CURL_EMPTY; send_negotiation( conn, CURL_DO, option ); break; } break; case CURL_WANTYES: switch ( tn->himq[option] ) { case CURL_EMPTY: tn->him[option] = CURL_NO; break; case CURL_OPPOSITE: tn->him[option] = CURL_NO; tn->himq[option] = CURL_EMPTY; break; } break; } } static void set_local_option( struct connectdata *conn, int option, int newstate ) { struct TELNET *tn = (struct TELNET *)conn->proto.telnet; if ( newstate == CURL_YES ) { switch ( tn->us[option] ) { case CURL_NO: tn->us[option] = CURL_WANTYES; send_negotiation( conn, CURL_WILL, option ); break; case CURL_YES: /* Already enabled */ break; case CURL_WANTNO: switch ( tn->usq[option] ) { case CURL_EMPTY: /* Already negotiating for CURL_YES, queue the request */ tn->usq[option] = CURL_OPPOSITE; break; case CURL_OPPOSITE: /* Error: already queued an enable request */ break; } break; case CURL_WANTYES: switch ( tn->usq[option] ) { case CURL_EMPTY: /* Error: already negotiating for enable */ break; case CURL_OPPOSITE: tn->usq[option] = CURL_EMPTY; break; } break; } } else /* NO */ { switch ( tn->us[option] ) { case CURL_NO: /* Already disabled */ break; case CURL_YES: tn->us[option] = CURL_WANTNO; send_negotiation( conn, CURL_WONT, option ); break; case CURL_WANTNO: switch ( tn->usq[option] ) { case CURL_EMPTY: /* Already negotiating for NO */ break; case CURL_OPPOSITE: tn->usq[option] = CURL_EMPTY; break; } break; case CURL_WANTYES: switch ( tn->usq[option] ) { case CURL_EMPTY: tn->usq[option] = CURL_OPPOSITE; break; case CURL_OPPOSITE: break; } break; } } } static void rec_do( struct connectdata *conn, int option ) { struct TELNET *tn = (struct TELNET *)conn->proto.telnet; switch ( tn->us[option] ) { case CURL_NO: if ( tn->us_preferred[option] == CURL_YES ) { tn->us[option] = CURL_YES; send_negotiation( conn, CURL_WILL, option ); } else { send_negotiation( conn, CURL_WONT, option ); } break; case CURL_YES: /* Already enabled */ break; case CURL_WANTNO: switch ( tn->usq[option] ) { case CURL_EMPTY: /* Error: DONT answered by WILL */ tn->us[option] = CURL_NO; break; case CURL_OPPOSITE: /* Error: DONT answered by WILL */ tn->us[option] = CURL_YES; tn->usq[option] = CURL_EMPTY; break; } break; case CURL_WANTYES: switch ( tn->usq[option] ) { case CURL_EMPTY: tn->us[option] = CURL_YES; break; case CURL_OPPOSITE: tn->us[option] = CURL_WANTNO; tn->himq[option] = CURL_EMPTY; send_negotiation( conn, CURL_WONT, option ); break; } break; } } static void rec_dont( struct connectdata *conn, int option ) { struct TELNET *tn = (struct TELNET *)conn->proto.telnet; switch ( tn->us[option] ) { case CURL_NO: /* Already disabled */ break; case CURL_YES: tn->us[option] = CURL_NO; send_negotiation( conn, CURL_WONT, option ); break; case CURL_WANTNO: switch ( tn->usq[option] ) { case CURL_EMPTY: tn->us[option] = CURL_NO; break; case CURL_OPPOSITE: tn->us[option] = CURL_WANTYES; tn->usq[option] = CURL_EMPTY; send_negotiation( conn, CURL_WILL, option ); break; } break; case CURL_WANTYES: switch ( tn->usq[option] ) { case CURL_EMPTY: tn->us[option] = CURL_NO; break; case CURL_OPPOSITE: tn->us[option] = CURL_NO; tn->usq[option] = CURL_EMPTY; break; } break; } } static void printsub( struct SessionHandle *data, int direction, /* '<' or '>' */ unsigned char *pointer, /* where suboption data is */ size_t length ) { /* length of suboption data */ unsigned int i = 0; if ( data->set.verbose ) { if ( direction ) { Curl_infof( data, "%s IAC SB ", ( direction == '<' ) ? "RCVD" : "SENT" ); if ( length >= 3 ) { int j; i = pointer[length - 2]; j = pointer[length - 1]; if ( i != CURL_IAC || j != CURL_SE ) { Curl_infof( data, "(terminated by " ); if ( CURL_TELOPT_OK( i ) ) { Curl_infof( data, "%s ", CURL_TELOPT( i ) ); } else if ( CURL_TELCMD_OK( i ) ) { Curl_infof( data, "%s ", CURL_TELCMD( i ) ); } else { Curl_infof( data, "%d ", i ); } if ( CURL_TELOPT_OK( j ) ) { Curl_infof( data, "%s", CURL_TELOPT( j ) ); } else if ( CURL_TELCMD_OK( j ) ) { Curl_infof( data, "%s", CURL_TELCMD( j ) ); } else { Curl_infof( data, "%d", j ); } Curl_infof( data, ", not IAC SE!) " ); } } length -= 2; } if ( length < 1 ) { Curl_infof( data, "(Empty suboption?)" ); return; } if ( CURL_TELOPT_OK( pointer[0] ) ) { switch ( pointer[0] ) { case CURL_TELOPT_TTYPE: case CURL_TELOPT_XDISPLOC: case CURL_TELOPT_NEW_ENVIRON: Curl_infof( data, "%s", CURL_TELOPT( pointer[0] ) ); break; default: Curl_infof( data, "%s (unsupported)", CURL_TELOPT( pointer[0] ) ); break; } } else { Curl_infof( data, "%d (unknown)", pointer[i] ); } switch ( pointer[1] ) { case CURL_TELQUAL_IS: Curl_infof( data, " IS" ); break; case CURL_TELQUAL_SEND: Curl_infof( data, " SEND" ); break; case CURL_TELQUAL_INFO: Curl_infof( data, " INFO/REPLY" ); break; case CURL_TELQUAL_NAME: Curl_infof( data, " NAME" ); break; } switch ( pointer[0] ) { case CURL_TELOPT_TTYPE: case CURL_TELOPT_XDISPLOC: pointer[length] = 0; Curl_infof( data, " \"%s\"", &pointer[2] ); break; case CURL_TELOPT_NEW_ENVIRON: if ( pointer[1] == CURL_TELQUAL_IS ) { Curl_infof( data, " " ); for ( i = 3; i < length; i++ ) { switch ( pointer[i] ) { case CURL_NEW_ENV_VAR: Curl_infof( data, ", " ); break; case CURL_NEW_ENV_VALUE: Curl_infof( data, " = " ); break; default: Curl_infof( data, "%c", pointer[i] ); break; } } } break; default: for ( i = 2; i < length; i++ ) Curl_infof( data, " %.2x", pointer[i] ); break; } if ( direction ) { Curl_infof( data, "\n" ); } } } static CURLcode check_telnet_options( struct connectdata *conn ) { struct curl_slist *head; char option_keyword[128]; char option_arg[256]; char *buf; struct SessionHandle *data = conn->data; struct TELNET *tn = (struct TELNET *)conn->proto.telnet; /* Add the user name as an environment variable if it was given on the command line */ if ( conn->bits.user_passwd ) { snprintf( option_arg, sizeof( option_arg ), "USER,%s", conn->user ); tn->telnet_vars = curl_slist_append( tn->telnet_vars, option_arg ); tn->us_preferred[CURL_TELOPT_NEW_ENVIRON] = CURL_YES; } for ( head = data->set.telnet_options; head; head = head->next ) { if ( sscanf( head->data, "%127[^= ]%*[ =]%255s", option_keyword, option_arg ) == 2 ) { /* Terminal type */ if ( curl_strequal( option_keyword, "TTYPE" ) ) { strncpy( tn->subopt_ttype, option_arg, 31 ); tn->subopt_ttype[31] = 0; /* String termination */ tn->us_preferred[CURL_TELOPT_TTYPE] = CURL_YES; continue; } /* Display variable */ if ( curl_strequal( option_keyword, "XDISPLOC" ) ) { strncpy( tn->subopt_xdisploc, option_arg, 127 ); tn->subopt_xdisploc[127] = 0; /* String termination */ tn->us_preferred[CURL_TELOPT_XDISPLOC] = CURL_YES; continue; } /* Environment variable */ if ( curl_strequal( option_keyword, "NEW_ENV" ) ) { buf = strdup( option_arg ); if ( !buf ) { return CURLE_OUT_OF_MEMORY; } tn->telnet_vars = curl_slist_append( tn->telnet_vars, buf ); tn->us_preferred[CURL_TELOPT_NEW_ENVIRON] = CURL_YES; continue; } failf( data, "Unknown telnet option %s", head->data ); return CURLE_UNKNOWN_TELNET_OPTION; } else { failf( data, "Syntax error in telnet option: %s", head->data ); return CURLE_TELNET_OPTION_SYNTAX; } } return CURLE_OK; } /* * suboption() * * Look at the sub-option buffer, and try to be helpful to the other * side. */ static void suboption( struct connectdata *conn ) { struct curl_slist *v; unsigned char temp[2048]; size_t len; size_t tmplen; char varname[128]; char varval[128]; struct SessionHandle *data = conn->data; struct TELNET *tn = (struct TELNET *)conn->proto.telnet; printsub( data, '<', (unsigned char *)tn->subbuffer, CURL_SB_LEN( tn ) + 2 ); switch ( CURL_SB_GET( tn ) ) { case CURL_TELOPT_TTYPE: len = strlen( tn->subopt_ttype ) + 4 + 2; snprintf( (char *)temp, sizeof( temp ), "%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_TTYPE, CURL_TELQUAL_IS, tn->subopt_ttype, CURL_IAC, CURL_SE ); (void)swrite( conn->sock[FIRSTSOCKET], temp, len ); printsub( data, '>', &temp[2], len - 2 ); break; case CURL_TELOPT_XDISPLOC: len = strlen( tn->subopt_xdisploc ) + 4 + 2; snprintf( (char *)temp, sizeof( temp ), "%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_XDISPLOC, CURL_TELQUAL_IS, tn->subopt_xdisploc, CURL_IAC, CURL_SE ); (void)swrite( conn->sock[FIRSTSOCKET], temp, len ); printsub( data, '>', &temp[2], len - 2 ); break; case CURL_TELOPT_NEW_ENVIRON: snprintf( (char *)temp, sizeof( temp ), "%c%c%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_NEW_ENVIRON, CURL_TELQUAL_IS ); len = 4; for ( v = tn->telnet_vars; v; v = v->next ) { tmplen = ( strlen( v->data ) + 1 ); /* Add the variable only if it fits */ if ( len + tmplen < (int)sizeof( temp ) - 6 ) { sscanf( v->data, "%127[^,],%127s", varname, varval ); snprintf( (char *)&temp[len], sizeof( temp ) - len, "%c%s%c%s", CURL_NEW_ENV_VAR, varname, CURL_NEW_ENV_VALUE, varval ); len += tmplen; } } snprintf( (char *)&temp[len], sizeof( temp ) - len, "%c%c", CURL_IAC, CURL_SE ); len += 2; (void)swrite( conn->sock[FIRSTSOCKET], temp, len ); printsub( data, '>', &temp[2], len - 2 ); break; } return; } static void telrcv( struct connectdata *conn, unsigned char *inbuf, /* Data received from socket */ ssize_t count ) { /* Number of bytes received */ unsigned char c; int in = 0; struct SessionHandle *data = conn->data; struct TELNET *tn = (struct TELNET *)conn->proto.telnet; while ( count-- ) { c = inbuf[in++]; switch ( tn->telrcv_state ) { case CURL_TS_CR: tn->telrcv_state = CURL_TS_DATA; if ( c == '\0' ) { break; /* Ignore \0 after CR */ } Curl_client_write( data, CLIENTWRITE_BODY, (char *)&c, 1 ); continue; case CURL_TS_DATA: if ( c == CURL_IAC ) { tn->telrcv_state = CURL_TS_IAC; break; } else if ( c == '\r' ) { tn->telrcv_state = CURL_TS_CR; } Curl_client_write( data, CLIENTWRITE_BODY, (char *)&c, 1 ); continue; case CURL_TS_IAC: process_iac: switch ( c ) { case CURL_WILL: tn->telrcv_state = CURL_TS_WILL; continue; case CURL_WONT: tn->telrcv_state = CURL_TS_WONT; continue; case CURL_DO: tn->telrcv_state = CURL_TS_DO; continue; case CURL_DONT: tn->telrcv_state = CURL_TS_DONT; continue; case CURL_SB: CURL_SB_CLEAR( tn ); tn->telrcv_state = CURL_TS_SB; continue; case CURL_IAC: Curl_client_write( data, CLIENTWRITE_BODY, (char *)&c, 1 ); break; case CURL_DM: case CURL_NOP: case CURL_GA: default: printoption( data, "RCVD", CURL_IAC, c ); break; } tn->telrcv_state = CURL_TS_DATA; continue; case CURL_TS_WILL: printoption( data, "RCVD", CURL_WILL, c ); tn->please_negotiate = 1; rec_will( conn, c ); tn->telrcv_state = CURL_TS_DATA; continue; case CURL_TS_WONT: printoption( data, "RCVD", CURL_WONT, c ); tn->please_negotiate = 1; rec_wont( conn, c ); tn->telrcv_state = CURL_TS_DATA; continue; case CURL_TS_DO: printoption( data, "RCVD", CURL_DO, c ); tn->please_negotiate = 1; rec_do( conn, c ); tn->telrcv_state = CURL_TS_DATA; continue; case CURL_TS_DONT: printoption( data, "RCVD", CURL_DONT, c ); tn->please_negotiate = 1; rec_dont( conn, c ); tn->telrcv_state = CURL_TS_DATA; continue; case CURL_TS_SB: if ( c == CURL_IAC ) { tn->telrcv_state = CURL_TS_SE; } else { CURL_SB_ACCUM( tn,c ); } continue; case CURL_TS_SE: if ( c != CURL_SE ) { if ( c != CURL_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. */ CURL_SB_ACCUM( tn, (unsigned char)CURL_IAC ); CURL_SB_ACCUM( tn, c ); tn->subpointer -= 2; CURL_SB_TERM( tn ); printoption( data, "In SUBOPTION processing, RCVD", CURL_IAC, c ); suboption( conn ); /* handle sub-option */ tn->telrcv_state = CURL_TS_IAC; goto process_iac; } CURL_SB_ACCUM( tn,c ); tn->telrcv_state = CURL_TS_SB; } else { CURL_SB_ACCUM( tn, (unsigned char)CURL_IAC ); CURL_SB_ACCUM( tn, (unsigned char)CURL_SE ); tn->subpointer -= 2; CURL_SB_TERM( tn ); suboption( conn ); /* handle sub-option */ tn->telrcv_state = CURL_TS_DATA; } break; } } } CURLcode Curl_telnet_done( struct connectdata *conn, CURLcode status ) { struct TELNET *tn = (struct TELNET *)conn->proto.telnet; (void)status; /* unused */ curl_slist_free_all( tn->telnet_vars ); free( conn->proto.telnet ); conn->proto.telnet = NULL; return CURLE_OK; } CURLcode Curl_telnet( struct connectdata *conn ) { CURLcode code; struct SessionHandle *data = conn->data; curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; #ifdef WIN32 HMODULE wsock2; WSOCK2_FUNC close_event_func; WSOCK2_FUNC create_event_func; WSOCK2_FUNC event_select_func; WSOCK2_FUNC enum_netevents_func; WSAEVENT event_handle; WSANETWORKEVENTS events; HANDLE stdin_handle; HANDLE objs[2]; DWORD obj_count; DWORD wait_timeout; DWORD waitret; DWORD readfile_read; #else fd_set readfd; fd_set keepfd; #endif ssize_t nread; bool keepon = TRUE; char *buf = data->state.buffer; struct TELNET *tn; code = init_telnet( conn ); if ( code ) { return code; } tn = (struct TELNET *)conn->proto.telnet; code = check_telnet_options( conn ); if ( code ) { return code; } #ifdef WIN32 /* ** This functionality only works with WinSock >= 2.0. So, ** make sure have it. */ code = check_wsock2( data ); if ( code ) { return code; } /* OK, so we have WinSock 2.0. We need to dynamically */ /* load ws2_32.dll and get the function pointers we need. */ wsock2 = LoadLibrary( "WS2_32.DLL" ); if ( wsock2 == NULL ) { failf( data,"failed to load WS2_32.DLL (%d)",GetLastError() ); return CURLE_FAILED_INIT; } /* Grab a pointer to WSACreateEvent */ create_event_func = GetProcAddress( wsock2,"WSACreateEvent" ); if ( create_event_func == NULL ) { failf( data,"failed to find WSACreateEvent function (%d)", GetLastError() ); FreeLibrary( wsock2 ); return CURLE_FAILED_INIT; } /* And WSACloseEvent */ close_event_func = GetProcAddress( wsock2,"WSACloseEvent" ); if ( create_event_func == NULL ) { failf( data,"failed to find WSACloseEvent function (%d)", GetLastError() ); FreeLibrary( wsock2 ); return CURLE_FAILED_INIT; } /* And WSAEventSelect */ event_select_func = GetProcAddress( wsock2,"WSAEventSelect" ); if ( event_select_func == NULL ) { failf( data,"failed to find WSAEventSelect function (%d)", GetLastError() ); FreeLibrary( wsock2 ); return CURLE_FAILED_INIT; } /* And WSAEnumNetworkEvents */ enum_netevents_func = GetProcAddress( wsock2,"WSAEnumNetworkEvents" ); if ( enum_netevents_func == NULL ) { failf( data,"failed to find WSAEnumNetworkEvents function (%d)", GetLastError() ); FreeLibrary( wsock2 ); return CURLE_FAILED_INIT; } /* We want to wait for both stdin and the socket. Since ** the select() function in winsock only works on sockets ** we have to use the WaitForMultipleObjects() call. */ /* First, create a sockets event object */ event_handle = (WSAEVENT)create_event_func(); if ( event_handle == WSA_INVALID_EVENT ) { failf( data,"WSACreateEvent failed (%d)",WSAGetLastError() ); FreeLibrary( wsock2 ); return CURLE_FAILED_INIT; } /* The get the Windows file handle for stdin */ stdin_handle = GetStdHandle( STD_INPUT_HANDLE ); /* Create the list of objects to wait for */ objs[0] = event_handle; objs[1] = stdin_handle; /* Tell winsock what events we want to listen to */ if ( event_select_func( sockfd, event_handle, FD_READ | FD_CLOSE ) == SOCKET_ERROR ) { close_event_func( event_handle ); FreeLibrary( wsock2 ); return 0; } /* If stdin_handle is a pipe, use PeekNamedPipe() method to check it, else use the old WaitForMultipleObjects() way */ if ( GetFileType( stdin_handle ) == FILE_TYPE_PIPE ) { /* Don't wait for stdin_handle, just wait for event_handle */ obj_count = 1; /* Check stdin_handle per 100 milliseconds */ wait_timeout = 100; } else { obj_count = 2; wait_timeout = INFINITE; } /* Keep on listening and act on events */ while ( keepon ) { waitret = WaitForMultipleObjects( obj_count, objs, FALSE, wait_timeout ); switch ( waitret ) { case WAIT_TIMEOUT: { unsigned char outbuf[2]; int out_count = 0; ssize_t bytes_written; char *buffer = buf; for (;; ) { if ( !PeekNamedPipe( stdin_handle, NULL, 0, NULL, &readfile_read, NULL ) ) { keepon = FALSE; break; } nread = readfile_read; if ( !nread ) { break; } if ( !ReadFile( stdin_handle, buf, sizeof( data->state.buffer ), &readfile_read, NULL ) ) { keepon = FALSE; break; } nread = readfile_read; while ( nread-- ) { outbuf[0] = *buffer++; out_count = 1; if ( outbuf[0] == CURL_IAC ) { outbuf[out_count++] = CURL_IAC; } Curl_write( conn, conn->sock[FIRSTSOCKET], outbuf, out_count, &bytes_written ); } } } break; case WAIT_OBJECT_0 + 1: { unsigned char outbuf[2]; int out_count = 0; ssize_t bytes_written; char *buffer = buf; if ( !ReadFile( stdin_handle, buf, sizeof( data->state.buffer ), &readfile_read, NULL ) ) { keepon = FALSE; break; } nread = readfile_read; while ( nread-- ) { outbuf[0] = *buffer++; out_count = 1; if ( outbuf[0] == CURL_IAC ) { outbuf[out_count++] = CURL_IAC; } Curl_write( conn, conn->sock[FIRSTSOCKET], outbuf, out_count, &bytes_written ); } } break; case WAIT_OBJECT_0: if ( enum_netevents_func( sockfd, event_handle, &events ) != SOCKET_ERROR ) { if ( events.lNetworkEvents & FD_READ ) { /* This reallu OUGHT to check its return code. */ (void)Curl_read( conn, sockfd, buf, BUFSIZE - 1, &nread ); telrcv( conn, (unsigned char *)buf, nread ); fflush( stdout ); /* Negotiate if the peer has started negotiating, otherwise don't. We don't want to speak telnet with non-telnet servers, like POP or SMTP. */ if ( tn->please_negotiate && !tn->already_negotiated ) { negotiate( conn ); tn->already_negotiated = 1; } } if ( events.lNetworkEvents & FD_CLOSE ) { keepon = FALSE; } } break; } } /* We called WSACreateEvent, so call WSACloseEvent */ if ( close_event_func( event_handle ) == FALSE ) { infof( data,"WSACloseEvent failed (%d)",WSAGetLastError() ); } /* "Forget" pointers into the library we're about to free */ create_event_func = NULL; close_event_func = NULL; event_select_func = NULL; enum_netevents_func = NULL; /* We called LoadLibrary, so call FreeLibrary */ if ( !FreeLibrary( wsock2 ) ) { infof( data,"FreeLibrary(wsock2) failed (%d)",GetLastError() ); } #else FD_ZERO( &readfd ); /* clear it */ FD_SET( sockfd, &readfd ); FD_SET( 0, &readfd ); keepfd = readfd; while ( keepon ) { struct timeval interval; readfd = keepfd; /* set this every lap in the loop */ interval.tv_sec = 1; interval.tv_usec = 0; switch ( select( sockfd + 1, &readfd, NULL, NULL, &interval ) ) { case - 1: /* error, stop reading */ keepon = FALSE; continue; case 0: /* timeout */ break; default: /* read! */ if ( FD_ISSET( 0, &readfd ) ) { /* read from stdin */ unsigned char outbuf[2]; int out_count = 0; ssize_t bytes_written; char *buffer = buf; nread = read( 0, buf, 255 ); while ( nread-- ) { outbuf[0] = *buffer++; out_count = 1; if ( outbuf[0] == CURL_IAC ) { outbuf[out_count++] = CURL_IAC; } Curl_write( conn, conn->sock[FIRSTSOCKET], outbuf, out_count, &bytes_written ); } } if ( FD_ISSET( sockfd, &readfd ) ) { /* This OUGHT to check the return code... */ (void)Curl_read( conn, sockfd, buf, BUFSIZE - 1, &nread ); /* 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( conn, (unsigned char *)buf, nread ); /* Negotiate if the peer has started negotiating, otherwise don't. We don't want to speak telnet with non-telnet servers, like POP or SMTP. */ if ( tn->please_negotiate && !tn->already_negotiated ) { negotiate( conn ); tn->already_negotiated = 1; } } } if ( data->set.timeout ) { struct timeval now; /* current time */ now = Curl_tvnow(); if ( Curl_tvdiff( now, conn->created ) / 1000 >= data->set.timeout ) { failf( data, "Time-out" ); code = CURLE_OPERATION_TIMEOUTED; keepon = FALSE; } } } #endif /* mark this as "no further transfer wanted" */ Curl_Transfer( conn, -1, -1, FALSE, NULL, -1, NULL ); return code; } #endif