/* * Modifications Copyright 1993, 1994, 1995, 1999, * 2000, 2001, 2002, 2005 by Paul Mattes. * RPQNAMES modifications Copyright 2004 by Don Russell. * Original X11 Port Copyright 1990 by Jeff Sparkes. * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose and without fee is hereby granted, * provided that the above copyright notice appear in all copies and that * both that copyright notice and this permission notice appear in * supporting documentation. * * Copyright 1989 by Georgia Tech Research Corporation, Atlanta, GA 30332. * All Rights Reserved. GTRC hereby grants public use of this software. * Derivative works based on this software must incorporate this copyright * notice. * * x3270, c3270, s3270 and tcl3270 are distributed in the hope that they will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file LICENSE * for more details. */ /* * telnet.c * This module initializes and manages a telnet socket to * the given IBM host. */ #include "globals.h" #include #include #include #define TELCMDS 1 #define TELOPTS 1 #include "arpa_telnet.h" #include #include #include #include #include #if defined(HAVE_LIBSSL) /*[*/ #include #include #endif /*]*/ #include "tn3270e.h" #include "3270ds.h" #include "appres.h" #include "ansic.h" #include "ctlrc.h" #include "hostc.h" #include "kybdc.h" #include "macrosc.h" #include "popupsc.h" #include "statusc.h" #include "tablesc.h" #include "telnetc.h" #include "trace_dsc.h" #include "utilc.h" #include "xioc.h" #if !defined(TELOPT_NAWS) /*[*/ #define TELOPT_NAWS 31 #endif /*]*/ #if !defined(TELOPT_STARTTLS) /*[*/ #define TELOPT_STARTTLS 46 #endif /*]*/ #define TLS_FOLLOWS 1 #define BUFSZ 16384 #define TRACELINE 72 #define N_OPTS 256 /* Globals */ char *hostname = CN; time_t ns_time; int ns_brcvd; int ns_rrcvd; int ns_bsent; int ns_rsent; unsigned char *obuf; /* 3270 output buffer */ unsigned char *obptr = (unsigned char *) NULL; int linemode = 1; #if defined(LOCAL_PROCESS) /*[*/ Boolean local_process = False; #endif /*]*/ char *termtype; /* Externals */ extern struct timeval ds_ts; /* Statics */ static int sock = -1; /* active socket */ static unsigned char myopts[N_OPTS], hisopts[N_OPTS]; /* telnet option flags */ static unsigned char *ibuf = (unsigned char *) NULL; /* 3270 input buffer */ static unsigned char *ibptr; static int ibuf_size = 0; /* size of ibuf */ static unsigned char *obuf_base = (unsigned char *)NULL; static int obuf_size = 0; static unsigned char *netrbuf = (unsigned char *)NULL; /* network input buffer */ static unsigned char *sbbuf = (unsigned char *)NULL; /* telnet sub-option buffer */ static unsigned char *sbptr; static unsigned char telnet_state; static int syncing; static unsigned long output_id = 0L; static char ttype_tmpval[13]; #if defined(X3270_TN3270E) /*[*/ static unsigned long e_funcs; /* negotiated TN3270E functions */ #define E_OPT(n) (1 << (n)) static unsigned short e_xmit_seq; /* transmit sequence number */ static int response_required; #endif /*]*/ #if defined(X3270_ANSI) /*[*/ static int ansi_data = 0; static unsigned char *lbuf = (unsigned char *)NULL; /* line-mode input buffer */ static unsigned char *lbptr; static int lnext = 0; static int backslashed = 0; static int t_valid = 0; static char vintr; static char vquit; static char verase; static char vkill; static char veof; static char vwerase; static char vrprnt; static char vlnext; #endif /*]*/ static int tn3270e_negotiated = 0; static enum { E_NONE, E_3270, E_NVT, E_SSCP } tn3270e_submode = E_NONE; static int tn3270e_bound = 0; static char plu_name[BIND_PLU_NAME_MAX+1]; static char **lus = (char **)NULL; static char **curr_lu = (char **)NULL; static char *try_lu = CN; static int telnet_fsm(unsigned char c); static void net_rawout(unsigned const char *buf, int len); static void check_in3270(void); static void store3270in(unsigned char c); static void check_linemode(Boolean init); static int non_blocking(Boolean on); static void net_connected(void); #if defined(X3270_TN3270E) /*[*/ static int tn3270e_negotiate(void); #endif /*]*/ static int process_eor(void); #if defined(X3270_TN3270E) /*[*/ #if defined(X3270_TRACE) /*[*/ static const char *tn3270e_function_names(const unsigned char *, int); #endif /*]*/ static void tn3270e_subneg_send(unsigned char, unsigned long); static unsigned long tn3270e_fdecode(const unsigned char *, int); static void tn3270e_ack(void); static void tn3270e_nak(enum pds); #endif /*]*/ #if defined(X3270_ANSI) /*[*/ static void do_data(char c); static void do_intr(char c); static void do_quit(char c); static void do_cerase(char c); static void do_werase(char c); static void do_kill(char c); static void do_rprnt(char c); static void do_eof(char c); static void do_eol(char c); static void do_lnext(char c); static char parse_ctlchar(char *s); static void cooked_init(void); #endif /*]*/ #if defined(X3270_TRACE) /*[*/ static const char *cmd(int c); static const char *opt(unsigned char c); static const char *nnn(int c); #else /*][*/ #if defined(__GNUC__) /*[*/ #else /*][*/ #endif /*]*/ #define cmd(x) 0 #define opt(x) 0 #define nnn(x) 0 #endif /*]*/ /* telnet states */ #define TNS_DATA 0 /* receiving data */ #define TNS_IAC 1 /* got an IAC */ #define TNS_WILL 2 /* got an IAC WILL */ #define TNS_WONT 3 /* got an IAC WONT */ #define TNS_DO 4 /* got an IAC DO */ #define TNS_DONT 5 /* got an IAC DONT */ #define TNS_SB 6 /* got an IAC SB */ #define TNS_SB_IAC 7 /* got an IAC after an IAC SB */ /* telnet predefined messages */ static unsigned char do_opt[] = { IAC, DO, '_' }; static unsigned char dont_opt[] = { IAC, DONT, '_' }; static unsigned char will_opt[] = { IAC, WILL, '_' }; static unsigned char wont_opt[] = { IAC, WONT, '_' }; #if defined(X3270_TN3270E) /*[*/ static unsigned char functions_req[] = { IAC, SB, TELOPT_TN3270E, TN3270E_OP_FUNCTIONS }; #endif /*]*/ #if defined(X3270_TRACE) /*[*/ static const char *telquals[2] = { "IS", "SEND" }; #endif /*]*/ #if defined(X3270_TN3270E) /*[*/ #if defined(X3270_TRACE) /*[*/ static const char *reason_code[8] = { "CONN-PARTNER", "DEVICE-IN-USE", "INV-ASSOCIATE", "INV-NAME", "INV-DEVICE-TYPE", "TYPE-NAME-ERROR", "UNKNOWN-ERROR", "UNSUPPORTED-REQ" }; #define rsn(n) (((n) <= TN3270E_REASON_UNSUPPORTED_REQ) ? \ reason_code[(n)] : "??") #endif /*]*/ static const char *function_name[5] = { "BIND-IMAGE", "DATA-STREAM-CTL", "RESPONSES", "SCS-CTL-CODES", "SYSREQ" }; #define fnn(n) (((n) <= TN3270E_FUNC_SYSREQ) ? \ function_name[(n)] : "??") #if defined(X3270_TRACE) /*[*/ static const char *data_type[9] = { "3270-DATA", "SCS-DATA", "RESPONSE", "BIND-IMAGE", "UNBIND", "NVT-DATA", "REQUEST", "SSCP-LU-DATA", "PRINT-EOJ" }; #define e_dt(n) (((n) <= TN3270E_DT_PRINT_EOJ) ? \ data_type[(n)] : "??") static const char *req_flag[1] = { " ERR-COND-CLEARED" }; #define e_rq(fn, n) (((fn) == TN3270E_DT_REQUEST) ? \ (((n) <= TN3270E_RQF_ERR_COND_CLEARED) ? \ req_flag[(n)] : " ??") : "") static const char *hrsp_flag[3] = { "NO-RESPONSE", "ERROR-RESPONSE", "ALWAYS-RESPONSE" }; #define e_hrsp(n) (((n) <= TN3270E_RSF_ALWAYS_RESPONSE) ? \ hrsp_flag[(n)] : "??") static const char *trsp_flag[2] = { "POSITIVE-RESPONSE", "NEGATIVE-RESPONSE" }; #define e_trsp(n) (((n) <= TN3270E_RSF_NEGATIVE_RESPONSE) ? \ trsp_flag[(n)] : "??") #define e_rsp(fn, n) (((fn) == TN3270E_DT_RESPONSE) ? e_trsp(n) : e_hrsp(n)) #endif /*]*/ #endif /*]*/ #if defined(C3270) && defined(C3270_80_132) /*[*/ #define XMIT_ROWS ((appres.altscreen != CN)? 24: maxROWS) #define XMIT_COLS ((appres.altscreen != CN)? 80: maxCOLS) #else /*][*/ #define XMIT_ROWS maxROWS #define XMIT_COLS maxCOLS #endif /*]*/ #if defined(HAVE_LIBSSL) /*[*/ Boolean secure_connection = False; static SSL_CTX *ssl_ctx; static SSL *ssl_con; static Boolean need_tls_follows = False; static void ssl_init(void); #if OPENSSL_VERSION_NUMBER >= 0x00907000L /*[*/ #define INFO_CONST const #else /*][*/ #define INFO_CONST #endif /*]*/ static void client_info_callback(INFO_CONST SSL *s, int where, int ret); static void continue_tls(unsigned char *sbbuf, int len); #endif /*]*/ static void output_possible(void); /* * net_connect * Establish a telnet socket to the given host passed as an argument. * Called only once and is responsible for setting up the telnet * variables. Returns the file descriptor of the connected socket. */ int net_connect(const char *host, char *portname, Boolean ls, Boolean *resolving, Boolean *pending) { struct servent *sp; struct hostent *hp; #if !defined(AF_INET6) /*[*/ unsigned long lport; unsigned short port; char *ptr; #endif /*]*/ char passthru_haddr[8]; int passthru_len = 0; unsigned short passthru_port = 0; union { struct sockaddr sa; struct sockaddr_in sin; #if defined(AF_INET6) /*[*/ struct sockaddr_in6 sin6; #endif /*]*/ } haddr; socklen_t ha_len = sizeof(haddr); int on = 1; #if defined(OMTU) /*[*/ int mtu = OMTU; #endif /*]*/ # define close_fail { (void) close(sock); sock = -1; return -1; } if (netrbuf == (unsigned char *)NULL) netrbuf = (unsigned char *)Malloc(BUFSZ); #if defined(X3270_ANSI) /*[*/ if (!t_valid) { vintr = parse_ctlchar(appres.intr); vquit = parse_ctlchar(appres.quit); verase = parse_ctlchar(appres.erase); vkill = parse_ctlchar(appres.kill); veof = parse_ctlchar(appres.eof); vwerase = parse_ctlchar(appres.werase); vrprnt = parse_ctlchar(appres.rprnt); vlnext = parse_ctlchar(appres.lnext); t_valid = 1; } #endif /*]*/ *resolving = False; *pending = False; Replace(hostname, NewString(host)); /* get the passthru host and port number */ if (passthru_host) { const char *hn; hn = getenv("INTERNET_HOST"); if (hn == CN) hn = "internet-gateway"; hp = gethostbyname(hn); if (hp == (struct hostent *) 0) { popup_an_error("Unknown passthru host: %s", hn); return -1; } (void) memmove(passthru_haddr, hp->h_addr, hp->h_length); passthru_len = hp->h_length; sp = getservbyname("telnet-passthru","tcp"); if (sp != (struct servent *)NULL) passthru_port = sp->s_port; else passthru_port = htons(3514); } #if !defined(AF_INET6) /*[*/ /* get the port number */ lport = strtoul(portname, &ptr, 0); if (ptr == portname || *ptr != '\0' || lport == 0L || lport & ~0xffff) { if (!(sp = getservbyname(portname, "tcp"))) { popup_an_error("Unknown port number or service: %s", portname); return -1; } port = sp->s_port; } else port = htons((unsigned short)lport); current_port = ntohs(port); #endif /*]*/ /* fill in the socket address of the given host */ (void) memset((char *) &haddr, 0, sizeof(haddr)); if (passthru_host) { haddr.sin.sin_family = AF_INET; (void) memmove(&haddr.sin.sin_addr, passthru_haddr, passthru_len); haddr.sin.sin_port = passthru_port; ha_len = sizeof(struct sockaddr_in); } else { #if defined(LOCAL_PROCESS) /*[*/ if (ls) { local_process = True; } else { #endif /*]*/ #if defined(AF_INET6) /*[*/ struct addrinfo hints, *res; int rc; /* Use getaddrinfo(). */ #if defined(LOCAL_PROCESS) /*[*/ local_process = False; #endif /*]*/ (void) memset(&hints, '\0', sizeof(struct addrinfo)); hints.ai_flags = 0; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; rc = getaddrinfo(host, portname, &hints, &res); if (rc != 0) { popup_an_error("%s/%s: %s", hostname, portname, gai_strerror(rc)); return -1; } switch (res->ai_family) { case AF_INET: current_port = ntohs(((struct sockaddr_in *)res->ai_addr)->sin_port); break; case AF_INET6: current_port = ntohs(((struct sockaddr_in6 *)res->ai_addr)->sin6_port); break; default: popup_an_error("%s: unknown family %d", hostname, res->ai_family); freeaddrinfo(res); return -1; } (void) memcpy(&haddr.sa, res->ai_addr, res->ai_addrlen); ha_len = res->ai_addrlen; freeaddrinfo(res); #else /*][*/ /* Use gethostbyname(). */ hp = gethostbyname(host); if (hp == (struct hostent *) 0) { haddr.sin.sin_family = AF_INET; haddr.sin.sin_addr.s_addr = inet_addr(host); if (haddr.sin.sin_addr.s_addr == (unsigned long)-1) { popup_an_error("Unknown host:\n%s", hostname); return -1; } } else { haddr.sin.sin_family = hp->h_addrtype; (void) memmove(&haddr.sin.sin_addr, hp->h_addr, hp->h_length); } haddr.sin.sin_port = port; ha_len = sizeof(struct sockaddr_in); #endif /*]*/ #if defined(LOCAL_PROCESS) /*[*/ } #endif /*]*/ } #if defined(LOCAL_PROCESS) /*[*/ if (local_process) { int amaster; struct winsize w; w.ws_row = XMIT_ROWS; w.ws_col = XMIT_COLS; w.ws_xpixel = 0; w.ws_ypixel = 0; switch (forkpty(&amaster, NULL, NULL, &w)) { case -1: /* failed */ popup_an_errno(errno, "forkpty"); close_fail; case 0: /* child */ putenv("TERM=xterm"); if (strchr(host, ' ') != CN) { (void) execlp("/bin/sh", "sh", "-c", host, NULL); } else { char *arg1; arg1 = strrchr(host, '/'); (void) execlp(host, (arg1 == CN) ? host : arg1 + 1, NULL); } perror(host); _exit(1); break; default: /* parent */ sock = amaster; (void) fcntl(sock, F_SETFD, 1); net_connected(); host_in3270(CONNECTED_ANSI); break; } } else { #endif /*]*/ /* create the socket */ if ((sock = socket(haddr.sa.sa_family, SOCK_STREAM, 0)) == -1) { popup_an_errno(errno, "socket"); return -1; } /* set options for inline out-of-band data and keepalives */ if (setsockopt(sock, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0) { popup_an_errno(errno, "setsockopt(SO_OOBINLINE)"); close_fail; } if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof(on)) < 0) { popup_an_errno(errno, "setsockopt(SO_KEEPALIVE)"); close_fail; } #if defined(OMTU) /*[*/ if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&mtu, sizeof(mtu)) < 0) { popup_an_errno(errno, "setsockopt(SO_SNDBUF)"); close_fail; } #endif /*]*/ /* set the socket to be non-delaying */ if (non_blocking(True) < 0) close_fail; /* don't share the socket with our children */ (void) fcntl(sock, F_SETFD, 1); /* init ssl */ #if defined(HAVE_LIBSSL) /*[*/ if (ssl_host) ssl_init(); #endif /*]*/ /* connect */ if (connect(sock, &haddr.sa, ha_len) == -1) { if (errno == EWOULDBLOCK #if defined(EINPROGRESS) /*[*/ || errno == EINPROGRESS #endif /*]*/ ) { trace_dsn("Connection pending.\n"); *pending = True; output_id = AddOutput(sock, output_possible); } else { popup_an_errno(errno, "Connect to %s, port %d", hostname, current_port); close_fail; } } else { if (non_blocking(False) < 0) close_fail; net_connected(); } #if defined(LOCAL_PROCESS) /*[*/ } #endif /*]*/ /* set up temporary termtype */ if (appres.termname == CN && std_ds_host) { (void) sprintf(ttype_tmpval, "IBM-327%c-%d", appres.m3279 ? '9' : '8', model_num); termtype = ttype_tmpval; } /* all done */ return sock; } #undef close_fail /* Set up the LU list. */ static void setup_lus(void) { char *lu; char *comma; int n_lus = 1; int i; connected_lu = CN; connected_type = CN; if (!luname[0]) { Replace(lus, NULL); curr_lu = (char **)NULL; try_lu = CN; return; } /* * Count the commas in the LU name. That plus one is the * number of LUs to try. */ lu = luname; while ((comma = strchr(lu, ',')) != CN) { n_lus++; lu++; } /* * Allocate enough memory to construct an argv[] array for * the LUs. */ Replace(lus, (char **)Malloc((n_lus+1) * sizeof(char *) + strlen(luname) + 1)); /* Copy each LU into the array. */ lu = (char *)(lus + n_lus + 1); (void) strcpy(lu, luname); i = 0; do { lus[i++] = lu; comma = strchr(lu, ','); if (comma != CN) { *comma = '\0'; lu = comma + 1; } } while (comma != CN); lus[i] = CN; curr_lu = lus; try_lu = *curr_lu; } static void net_connected(void) { trace_dsn("Connected to %s, port %u%s.\n", hostname, current_port, ssl_host? " via SSL": ""); #if defined(HAVE_LIBSSL) /*[*/ /* Set up SSL. */ if (ssl_host && !secure_connection) { SSL_set_fd(ssl_con, sock); if (SSL_connect(ssl_con) != 1) { /* * No need to trace the error, it was already * displayed. */ host_disconnect(True); return; } secure_connection = True; trace_dsn("TLS/SSL tunneled connection complete. " "Connection is now secure.\n"); /* Tell everyone else again. */ host_connected(); } #endif /*]*/ /* set up telnet options */ (void) memset((char *) myopts, 0, sizeof(myopts)); (void) memset((char *) hisopts, 0, sizeof(hisopts)); #if defined(X3270_TN3270E) /*[*/ e_funcs = E_OPT(TN3270E_FUNC_BIND_IMAGE) | E_OPT(TN3270E_FUNC_RESPONSES) | E_OPT(TN3270E_FUNC_SYSREQ); e_xmit_seq = 0; response_required = TN3270E_RSF_NO_RESPONSE; #endif /*]*/ #if defined(HAVE_LIBSSL) /*[*/ need_tls_follows = False; #endif /*]*/ telnet_state = TNS_DATA; ibptr = ibuf; /* clear statistics and flags */ (void) time(&ns_time); ns_brcvd = 0; ns_rrcvd = 0; ns_bsent = 0; ns_rsent = 0; syncing = 0; tn3270e_negotiated = 0; tn3270e_submode = E_NONE; tn3270e_bound = 0; setup_lus(); check_linemode(True); /* write out the passthru hostname and port nubmer */ if (passthru_host) { char *buf; buf = Malloc(strlen(hostname) + 32); (void) sprintf(buf, "%s %d\r\n", hostname, current_port); (void) write(sock, buf, strlen(buf)); Free(buf); } } /* * connection_complete * The connection appears to be complete (output is possible or input * appeared ready but read() returned EWOULDBLOCK). Complete the * connection-completion processing. */ static void connection_complete(void) { if (non_blocking(False) < 0) { host_disconnect(True); return; } host_connected(); net_connected(); } /* * output_possible * Output is possible on the socket. Used only when a connection is * pending, to determine that the connection is complete. */ static void output_possible(void) { if (HALF_CONNECTED) { connection_complete(); } if (output_id) { RemoveInput(output_id); output_id = 0L; } } /* * net_disconnect * Shut down the socket. */ void net_disconnect(void) { #if defined(HAVE_LIBSSL) /*[*/ if (ssl_con != NULL) { SSL_shutdown(ssl_con); SSL_free(ssl_con); ssl_con = NULL; } secure_connection = False; #endif /*]*/ if (CONNECTED) (void) shutdown(sock, 2); (void) close(sock); sock = -1; trace_dsn("SENT disconnect\n"); /* Restore terminal type to its default. */ if (appres.termname == CN) termtype = full_model_name; /* We're not connected to an LU any more. */ status_lu(CN); /* We have no more interest inputput buffer space. */ if (output_id != 0L) { RemoveInput(output_id); output_id = 0L; } } /* * net_input * Called by the toolkit whenever there is input available on the * socket. Reads the data, processes the special telnet commands * and calls process_ds to process the 3270 data stream. */ void net_input(void) { register unsigned char *cp; int nr; if (sock < 0) return; #if defined(X3270_ANSI) /*[*/ ansi_data = 0; #endif /*]*/ #if defined(HAVE_LIBSSL) /*[*/ if (ssl_con != NULL) nr = SSL_read(ssl_con, (char *) netrbuf, BUFSZ); else #else /*][*/ #endif /*]*/ nr = read(sock, (char *) netrbuf, BUFSZ); if (nr < 0) { #if defined(HAVE_LIBSSL) /*[*/ if (ssl_con != NULL) { unsigned long e; char err_buf[120]; e = ERR_get_error(); if (e != 0) (void) ERR_error_string(e, err_buf); else strcpy(err_buf, "unknown error"); trace_dsn("RCVD SSL_read error %ld (%s)\n", e, err_buf); popup_an_error("SSL_read:\n%s", err_buf); host_disconnect(True); return; } #endif /*]*/ if (errno == EWOULDBLOCK) return; if (HALF_CONNECTED && errno == EAGAIN) { connection_complete(); return; } #if defined(LOCAL_PROCESS) /*[*/ if (errno == EIO && local_process) { trace_dsn("RCVD local process disconnect\n"); host_disconnect(False); return; } #endif /*]*/ trace_dsn("RCVD socket error %d\n", errno); if (HALF_CONNECTED) popup_an_errno(errno, "Connect to %s, port %d", hostname, current_port); else if (errno != ECONNRESET) popup_an_errno(errno, "Socket read"); host_disconnect(True); return; } else if (nr == 0) { /* Host disconnected. */ trace_dsn("RCVD disconnect\n"); host_disconnect(False); return; } /* Process the data. */ if (HALF_CONNECTED) { if (non_blocking(False) < 0) { host_disconnect(True); return; } host_connected(); net_connected(); } #if defined(X3270_TRACE) /*[*/ trace_netdata('<', netrbuf, nr); #endif /*]*/ ns_brcvd += nr; for (cp = netrbuf; cp < (netrbuf + nr); cp++) { #if defined(LOCAL_PROCESS) /*[*/ if (local_process) { /* More to do here, probably. */ if (IN_NEITHER) { /* now can assume ANSI mode */ host_in3270(CONNECTED_ANSI); hisopts[TELOPT_ECHO] = 1; check_linemode(False); kybdlock_clr(KL_AWAITING_FIRST, "telnet_fsm"); status_reset(); ps_process(); } ansi_process((unsigned int) *cp); } else { #endif /*]*/ if (telnet_fsm(*cp)) { (void) ctlr_dbcs_postprocess(); host_disconnect(True); return; } #if defined(LOCAL_PROCESS) /*[*/ } #endif /*]*/ } #if defined(X3270_ANSI) /*[*/ if (IN_ANSI) { (void) ctlr_dbcs_postprocess(); } if (ansi_data) { trace_dsn("\n"); ansi_data = 0; } #endif /*]*/ #if defined(X3270_TRACE) /*[*/ /* See if it's time to roll over the trace file. */ trace_rollover_check(); #endif /*]*/ } /* * set16 * Put a 16-bit value in a buffer. * Returns the number of bytes required. */ static int set16(char *buf, int n) { char *b0 = buf; n %= 256 * 256; if ((n / 256) == IAC) *(unsigned char *)buf++ = IAC; *buf++ = (n / 256); n %= 256; if (n == IAC) *(unsigned char *)buf++ = IAC; *buf++ = n; return buf - b0; } /* * send_naws * Send a Telnet window size sub-option negotation. */ static void send_naws(void) { char naws_msg[14]; int naws_len = 0; (void) sprintf(naws_msg, "%c%c%c", IAC, SB, TELOPT_NAWS); naws_len += 3; naws_len += set16(naws_msg + naws_len, XMIT_COLS); naws_len += set16(naws_msg + naws_len, XMIT_ROWS); (void) sprintf(naws_msg + naws_len, "%c%c", IAC, SE); naws_len += 2; net_rawout((unsigned char *)naws_msg, naws_len); trace_dsn("SENT %s NAWS %d %d %s\n", cmd(SB), XMIT_COLS, XMIT_ROWS, cmd(SE)); } /* Advance 'try_lu' to the next desired LU name. */ static void next_lu(void) { if (curr_lu != (char **)NULL && (try_lu = *++curr_lu) == CN) curr_lu = (char **)NULL; } /* * telnet_fsm * Telnet finite-state machine. * Returns 0 for okay, -1 for errors. */ static int telnet_fsm(unsigned char c) { #if defined(X3270_ANSI) /*[*/ char *see_chr; int sl; #endif /*]*/ switch (telnet_state) { case TNS_DATA: /* normal data processing */ if (c == IAC) { /* got a telnet command */ telnet_state = TNS_IAC; #if defined(X3270_ANSI) /*[*/ if (ansi_data) { trace_dsn("\n"); ansi_data = 0; } #endif /*]*/ break; } if (IN_NEITHER) { /* now can assume ANSI mode */ #if defined(X3270_ANSI)/*[*/ if (linemode) cooked_init(); #endif /*]*/ host_in3270(CONNECTED_ANSI); kybdlock_clr(KL_AWAITING_FIRST, "telnet_fsm"); status_reset(); ps_process(); } if (IN_ANSI && !IN_E) { #if defined(X3270_ANSI) /*[*/ if (!ansi_data) { trace_dsn("<.. "); ansi_data = 4; } see_chr = ctl_see((int) c); ansi_data += (sl = strlen(see_chr)); if (ansi_data >= TRACELINE) { trace_dsn(" ...\n... "); ansi_data = 4 + sl; } trace_dsn(see_chr); if (!syncing) { if (linemode && appres.onlcr && c == '\n') ansi_process((unsigned int) '\r'); ansi_process((unsigned int) c); sms_store(c); } #endif /*]*/ } else { store3270in(c); } break; case TNS_IAC: /* process a telnet command */ if (c != EOR && c != IAC) { trace_dsn("RCVD %s ", cmd(c)); } switch (c) { case IAC: /* escaped IAC, insert it */ if (IN_ANSI && !IN_E) { #if defined(X3270_ANSI) /*[*/ if (!ansi_data) { trace_dsn("<.. "); ansi_data = 4; } see_chr = ctl_see((int) c); ansi_data += (sl = strlen(see_chr)); if (ansi_data >= TRACELINE) { trace_dsn(" ...\n ..."); ansi_data = 4 + sl; } trace_dsn(see_chr); ansi_process((unsigned int) c); sms_store(c); #endif /*]*/ } else store3270in(c); telnet_state = TNS_DATA; break; case EOR: /* eor, process accumulated input */ if (IN_3270 || (IN_E && tn3270e_negotiated)) { ns_rrcvd++; if (process_eor()) return -1; } else Warning("EOR received when not in 3270 mode, " "ignored."); trace_dsn("RCVD EOR\n"); ibptr = ibuf; telnet_state = TNS_DATA; break; case WILL: telnet_state = TNS_WILL; break; case WONT: telnet_state = TNS_WONT; break; case DO: telnet_state = TNS_DO; break; case DONT: telnet_state = TNS_DONT; break; case SB: telnet_state = TNS_SB; if (sbbuf == (unsigned char *)NULL) sbbuf = (unsigned char *)Malloc(1024); sbptr = sbbuf; break; case DM: trace_dsn("\n"); if (syncing) { syncing = 0; x_except_on(sock); } telnet_state = TNS_DATA; break; case GA: case NOP: trace_dsn("\n"); telnet_state = TNS_DATA; break; default: trace_dsn("???\n"); telnet_state = TNS_DATA; break; } break; case TNS_WILL: /* telnet WILL DO OPTION command */ trace_dsn("%s\n", opt(c)); switch (c) { case TELOPT_SGA: case TELOPT_BINARY: case TELOPT_EOR: case TELOPT_TTYPE: case TELOPT_ECHO: #if defined(X3270_TN3270E) /*[*/ case TELOPT_TN3270E: #endif /*]*/ if (c != TELOPT_TN3270E || !non_tn3270e_host) { if (!hisopts[c]) { hisopts[c] = 1; do_opt[2] = c; net_rawout(do_opt, sizeof(do_opt)); trace_dsn("SENT %s %s\n", cmd(DO), opt(c)); /* * For UTS, volunteer to do EOR when * they do. */ if (c == TELOPT_EOR && !myopts[c]) { myopts[c] = 1; will_opt[2] = c; net_rawout(will_opt, sizeof(will_opt)); trace_dsn("SENT %s %s\n", cmd(WILL), opt(c)); } check_in3270(); check_linemode(False); } break; } default: dont_opt[2] = c; net_rawout(dont_opt, sizeof(dont_opt)); trace_dsn("SENT %s %s\n", cmd(DONT), opt(c)); break; } telnet_state = TNS_DATA; break; case TNS_WONT: /* telnet WONT DO OPTION command */ trace_dsn("%s\n", opt(c)); if (hisopts[c]) { hisopts[c] = 0; dont_opt[2] = c; net_rawout(dont_opt, sizeof(dont_opt)); trace_dsn("SENT %s %s\n", cmd(DONT), opt(c)); check_in3270(); check_linemode(False); } telnet_state = TNS_DATA; break; case TNS_DO: /* telnet PLEASE DO OPTION command */ trace_dsn("%s\n", opt(c)); switch (c) { case TELOPT_BINARY: case TELOPT_EOR: case TELOPT_TTYPE: case TELOPT_SGA: case TELOPT_NAWS: case TELOPT_TM: #if defined(X3270_TN3270E) /*[*/ case TELOPT_TN3270E: #endif /*]*/ #if defined(HAVE_LIBSSL) /*[*/ case TELOPT_STARTTLS: #endif /*]*/ if (c == TELOPT_TN3270E && non_tn3270e_host) goto wont; if (c == TELOPT_TM && !appres.bsd_tm) goto wont; if (!myopts[c]) { if (c != TELOPT_TM) myopts[c] = 1; will_opt[2] = c; net_rawout(will_opt, sizeof(will_opt)); trace_dsn("SENT %s %s\n", cmd(WILL), opt(c)); check_in3270(); check_linemode(False); } if (c == TELOPT_NAWS) send_naws(); #if defined(HAVE_LIBSSL) /*[*/ if (c == TELOPT_STARTTLS) { static unsigned char follows_msg[] = { IAC, SB, TELOPT_STARTTLS, TLS_FOLLOWS, IAC, SE }; /* * Send IAC SB STARTTLS FOLLOWS IAC SE * to announce that what follows is TLS. */ net_rawout(follows_msg, sizeof(follows_msg)); trace_dsn("SENT %s %s FOLLOWS %s\n", cmd(SB), opt(TELOPT_STARTTLS), cmd(SE)); need_tls_follows = True; } #endif /*]*/ break; default: wont: wont_opt[2] = c; net_rawout(wont_opt, sizeof(wont_opt)); trace_dsn("SENT %s %s\n", cmd(WONT), opt(c)); break; } telnet_state = TNS_DATA; break; case TNS_DONT: /* telnet PLEASE DON'T DO OPTION command */ trace_dsn("%s\n", opt(c)); if (myopts[c]) { myopts[c] = 0; wont_opt[2] = c; net_rawout(wont_opt, sizeof(wont_opt)); trace_dsn("SENT %s %s\n", cmd(WONT), opt(c)); check_in3270(); check_linemode(False); } telnet_state = TNS_DATA; break; case TNS_SB: /* telnet sub-option string command */ if (c == IAC) telnet_state = TNS_SB_IAC; else *sbptr++ = c; break; case TNS_SB_IAC: /* telnet sub-option string command */ *sbptr++ = c; if (c == SE) { telnet_state = TNS_DATA; if (sbbuf[0] == TELOPT_TTYPE && sbbuf[1] == TELQUAL_SEND) { int tt_len, tb_len; char *tt_out; trace_dsn("%s %s\n", opt(sbbuf[0]), telquals[sbbuf[1]]); if (lus != (char **)NULL && try_lu == CN) { /* None of the LUs worked. */ popup_an_error("Cannot connect to " "specified LU"); return -1; } tt_len = strlen(termtype); if (try_lu != CN && *try_lu) { tt_len += strlen(try_lu) + 1; connected_lu = try_lu; } else connected_lu = CN; status_lu(connected_lu); tb_len = 4 + tt_len + 2; tt_out = Malloc(tb_len + 1); (void) sprintf(tt_out, "%c%c%c%c%s%s%s%c%c", IAC, SB, TELOPT_TTYPE, TELQUAL_IS, termtype, (try_lu != CN && *try_lu) ? "@" : "", (try_lu != CN && *try_lu) ? try_lu : "", IAC, SE); net_rawout((unsigned char *)tt_out, tb_len); trace_dsn("SENT %s %s %s %.*s %s\n", cmd(SB), opt(TELOPT_TTYPE), telquals[TELQUAL_IS], tt_len, tt_out + 4, cmd(SE)); Free(tt_out); /* Advance to the next LU name. */ next_lu(); } #if defined(X3270_TN3270E) /*[*/ else if (myopts[TELOPT_TN3270E] && sbbuf[0] == TELOPT_TN3270E) { if (tn3270e_negotiate()) return -1; } #endif /*]*/ #if defined(HAVE_LIBSSL) /*[*/ else if (need_tls_follows && myopts[TELOPT_STARTTLS] && sbbuf[0] == TELOPT_STARTTLS) { continue_tls(sbbuf, sbptr - sbbuf); } #endif /*]*/ } else { telnet_state = TNS_SB; } break; } return 0; } #if defined(X3270_TN3270E) /*[*/ /* Send a TN3270E terminal type request. */ static void tn3270e_request(void) { int tt_len, tb_len; char *tt_out; char *t; tt_len = strlen(termtype); if (try_lu != CN && *try_lu) tt_len += strlen(try_lu) + 1; tb_len = 5 + tt_len + 2; tt_out = Malloc(tb_len + 1); t = tt_out; t += sprintf(tt_out, "%c%c%c%c%c%s", IAC, SB, TELOPT_TN3270E, TN3270E_OP_DEVICE_TYPE, TN3270E_OP_REQUEST, termtype); /* Convert 3279 to 3278, per the RFC. */ if (tt_out[12] == '9') tt_out[12] = '8'; if (try_lu != CN && *try_lu) t += sprintf(t, "%c%s", TN3270E_OP_CONNECT, try_lu); (void) sprintf(t, "%c%c", IAC, SE); net_rawout((unsigned char *)tt_out, tb_len); trace_dsn("SENT %s %s DEVICE-TYPE REQUEST %.*s%s%s " "%s\n", cmd(SB), opt(TELOPT_TN3270E), strlen(termtype), tt_out + 5, (try_lu != CN && *try_lu) ? " CONNECT " : "", (try_lu != CN && *try_lu) ? try_lu : "", cmd(SE)); Free(tt_out); } /* * Back off of TN3270E. */ static void backoff_tn3270e(const char *why) { trace_dsn("Aborting TN3270E: %s\n", why); /* Tell the host 'no'. */ wont_opt[2] = TELOPT_TN3270E; net_rawout(wont_opt, sizeof(wont_opt)); trace_dsn("SENT %s %s\n", cmd(WONT), opt(TELOPT_TN3270E)); /* Restore the LU list; we may need to run it again in TN3270 mode. */ setup_lus(); /* Reset our internal state. */ myopts[TELOPT_TN3270E] = 0; check_in3270(); } /* * Negotiation of TN3270E options. * Returns 0 if okay, -1 if we have to give up altogether. */ static int tn3270e_negotiate(void) { #define LU_MAX 32 static char reported_lu[LU_MAX+1]; static char reported_type[LU_MAX+1]; int sblen; unsigned long e_rcvd; /* Find out how long the subnegotiation buffer is. */ for (sblen = 0; ; sblen++) { if (sbbuf[sblen] == SE) break; } trace_dsn("TN3270E "); switch (sbbuf[1]) { case TN3270E_OP_SEND: if (sbbuf[2] == TN3270E_OP_DEVICE_TYPE) { /* Host wants us to send our device type. */ trace_dsn("SEND DEVICE-TYPE SE\n"); tn3270e_request(); } else { trace_dsn("SEND ??%u SE\n", sbbuf[2]); } break; case TN3270E_OP_DEVICE_TYPE: /* Device type negotiation. */ trace_dsn("DEVICE-TYPE "); switch (sbbuf[2]) { case TN3270E_OP_IS: { int tnlen, snlen; /* Device type success. */ /* Isolate the terminal type and session. */ tnlen = 0; while (sbbuf[3+tnlen] != SE && sbbuf[3+tnlen] != TN3270E_OP_CONNECT) tnlen++; snlen = 0; if (sbbuf[3+tnlen] == TN3270E_OP_CONNECT) { while(sbbuf[3+tnlen+1+snlen] != SE) snlen++; } trace_dsn("IS %.*s CONNECT %.*s SE\n", tnlen, &sbbuf[3], snlen, &sbbuf[3+tnlen+1]); /* Remember the LU. */ if (tnlen) { if (tnlen > LU_MAX) tnlen = LU_MAX; (void)strncpy(reported_type, (char *)&sbbuf[3], tnlen); reported_type[tnlen] = '\0'; connected_type = reported_type; } if (snlen) { if (snlen > LU_MAX) snlen = LU_MAX; (void)strncpy(reported_lu, (char *)&sbbuf[3+tnlen+1], snlen); reported_lu[snlen] = '\0'; connected_lu = reported_lu; status_lu(connected_lu); } /* Tell them what we can do. */ tn3270e_subneg_send(TN3270E_OP_REQUEST, e_funcs); break; } case TN3270E_OP_REJECT: /* Device type failure. */ trace_dsn("REJECT REASON %s SE\n", rsn(sbbuf[4])); if (sbbuf[4] == TN3270E_REASON_INV_DEVICE_TYPE || sbbuf[4] == TN3270E_REASON_UNSUPPORTED_REQ) { backoff_tn3270e("Host rejected device type or " "request type"); break; } next_lu(); if (try_lu != CN) { /* Try the next LU. */ tn3270e_request(); } else if (lus != (char **)NULL) { /* No more LUs to try. Give up. */ backoff_tn3270e("Host rejected resource(s)"); } else { backoff_tn3270e("Device type rejected"); } break; default: trace_dsn("??%u SE\n", sbbuf[2]); break; } break; case TN3270E_OP_FUNCTIONS: /* Functions negotiation. */ trace_dsn("FUNCTIONS "); switch (sbbuf[2]) { case TN3270E_OP_REQUEST: /* Host is telling us what functions they want. */ trace_dsn("REQUEST %s SE\n", tn3270e_function_names(sbbuf+3, sblen-3)); e_rcvd = tn3270e_fdecode(sbbuf+3, sblen-3); if ((e_rcvd == e_funcs) || (e_funcs & ~e_rcvd)) { /* They want what we want, or less. Done. */ e_funcs = e_rcvd; tn3270e_subneg_send(TN3270E_OP_IS, e_funcs); tn3270e_negotiated = 1; trace_dsn("TN3270E option negotiation " "complete.\n"); check_in3270(); } else { /* * They want us to do something we can't. * Request the common subset. */ e_funcs &= e_rcvd; tn3270e_subneg_send(TN3270E_OP_REQUEST, e_funcs); } break; case TN3270E_OP_IS: /* They accept our last request, or a subset thereof. */ trace_dsn("IS %s SE\n", tn3270e_function_names(sbbuf+3, sblen-3)); e_rcvd = tn3270e_fdecode(sbbuf+3, sblen-3); if (e_rcvd != e_funcs) { if (e_funcs & ~e_rcvd) { /* * They've removed something. This is * technically illegal, but we can * live with it. */ e_funcs = e_rcvd; } else { /* * They've added something. Abandon * TN3270E, they're brain dead. */ backoff_tn3270e("Host illegally added " "function(s)"); break; } } tn3270e_negotiated = 1; trace_dsn("TN3270E option negotiation complete.\n"); check_in3270(); break; default: trace_dsn("??%u SE\n", sbbuf[2]); break; } break; default: trace_dsn("??%u SE\n", sbbuf[1]); } /* Good enough for now. */ return 0; } #if defined(X3270_TRACE) /*[*/ /* Expand a string of TN3270E function codes into text. */ static const char * tn3270e_function_names(const unsigned char *buf, int len) { int i; static char text_buf[1024]; char *s = text_buf; if (!len) return("(null)"); for (i = 0; i < len; i++) { s += sprintf(s, "%s%s", (s == text_buf) ? "" : " ", fnn(buf[i])); } return text_buf; } #endif /*]*/ /* Expand the current TN3270E function codes into text. */ const char * tn3270e_current_opts(void) { int i; static char text_buf[1024]; char *s = text_buf; if (!e_funcs || !IN_E) return CN; for (i = 0; i < 32; i++) { if (e_funcs & E_OPT(i)) s += sprintf(s, "%s%s", (s == text_buf) ? "" : " ", fnn(i)); } return text_buf; } /* Transmit a TN3270E FUNCTIONS REQUEST or FUNCTIONS IS message. */ static void tn3270e_subneg_send(unsigned char op, unsigned long funcs) { unsigned char proto_buf[7 + 32]; int proto_len; int i; /* Construct the buffers. */ (void) memcpy(proto_buf, functions_req, 4); proto_buf[4] = op; proto_len = 5; for (i = 0; i < 32; i++) { if (funcs & E_OPT(i)) proto_buf[proto_len++] = i; } /* Complete and send out the protocol message. */ proto_buf[proto_len++] = IAC; proto_buf[proto_len++] = SE; net_rawout(proto_buf, proto_len); /* Complete and send out the trace text. */ trace_dsn("SENT %s %s FUNCTIONS %s %s %s\n", cmd(SB), opt(TELOPT_TN3270E), (op == TN3270E_OP_REQUEST)? "REQUEST": "IS", tn3270e_function_names(proto_buf + 5, proto_len - 7), cmd(SE)); } /* Translate a string of TN3270E functions into a bit-map. */ static unsigned long tn3270e_fdecode(const unsigned char *buf, int len) { unsigned long r = 0L; int i; /* Note that this code silently ignores options >= 32. */ for (i = 0; i < len; i++) { if (buf[i] < 32) r |= E_OPT(buf[i]); } return r; } #endif /*]*/ #if defined(X3270_TN3270E) /*[*/ static void process_bind(unsigned char *buf, int buflen) { int namelen, i; (void) memset(plu_name, '\0', sizeof(plu_name)); /* Make sure it's a BIND. */ if (buflen < 1 || buf[0] != BIND_RU) { return; } buf++; buflen--; /* Extract the PLU name. */ if (buflen < BIND_OFF_PLU_NAME + BIND_PLU_NAME_MAX) return; namelen = buf[BIND_OFF_PLU_NAME_LEN]; if (namelen > BIND_PLU_NAME_MAX) namelen = BIND_PLU_NAME_MAX; for (i = 0; i < namelen; i++) { plu_name[i] = ebc2asc0[buf[BIND_OFF_PLU_NAME + i]]; } } #endif /*]*/ static int process_eor(void) { if (syncing || !(ibptr - ibuf)) return(0); #if defined(X3270_TN3270E) /*[*/ if (IN_E) { tn3270e_header *h = (tn3270e_header *)ibuf; unsigned char *s; enum pds rv; trace_dsn("RCVD TN3270E(%s%s %s %u)\n", e_dt(h->data_type), e_rq(h->data_type, h->request_flag), e_rsp(h->data_type, h->response_flag), h->seq_number[0] << 8 | h->seq_number[1]); switch (h->data_type) { case TN3270E_DT_3270_DATA: if ((e_funcs & E_OPT(TN3270E_FUNC_BIND_IMAGE)) && !tn3270e_bound) return 0; tn3270e_submode = E_3270; check_in3270(); response_required = h->response_flag; rv = process_ds(ibuf + EH_SIZE, (ibptr - ibuf) - EH_SIZE); if (rv < 0 && response_required != TN3270E_RSF_NO_RESPONSE) tn3270e_nak(rv); else if (rv == PDS_OKAY_NO_OUTPUT && response_required == TN3270E_RSF_ALWAYS_RESPONSE) tn3270e_ack(); response_required = TN3270E_RSF_NO_RESPONSE; return 0; case TN3270E_DT_BIND_IMAGE: if (!(e_funcs & E_OPT(TN3270E_FUNC_BIND_IMAGE))) return 0; process_bind(ibuf + EH_SIZE, (ibptr - ibuf) - EH_SIZE); trace_dsn("< BIND PLU-name '%s'\n", plu_name); tn3270e_bound = 1; check_in3270(); return 0; case TN3270E_DT_UNBIND: if (!(e_funcs & E_OPT(TN3270E_FUNC_BIND_IMAGE))) return 0; tn3270e_bound = 0; if (tn3270e_submode == E_3270) tn3270e_submode = E_NONE; check_in3270(); return 0; case TN3270E_DT_NVT_DATA: /* In tn3270e NVT mode */ tn3270e_submode = E_NVT; check_in3270(); for (s = ibuf; s < ibptr; s++) { ansi_process(*s++); } return 0; case TN3270E_DT_SSCP_LU_DATA: if (!(e_funcs & E_OPT(TN3270E_FUNC_BIND_IMAGE))) return 0; tn3270e_submode = E_SSCP; check_in3270(); ctlr_write_sscp_lu(ibuf + EH_SIZE, (ibptr - ibuf) - EH_SIZE); return 0; default: /* Should do something more extraordinary here. */ return 0; } } else #endif /*]*/ { (void) process_ds(ibuf, ibptr - ibuf); } return 0; } /* * net_exception * Called when there is an exceptional condition on the socket. */ void net_exception(void) { #if defined(LOCAL_PROCESS) /*[*/ if (local_process) { trace_dsn("RCVD exception\n"); } else #endif /*[*/ { trace_dsn("RCVD urgent data indication\n"); if (!syncing) { syncing = 1; x_except_off(); } } } /* * Flavors of Network Output: * * 3270 mode * net_output send a 3270 record * * ANSI mode; call each other in turn * net_sendc net_cookout for 1 byte * net_sends net_cookout for a null-terminated string * net_cookout send user data with cooked-mode processing, ANSI mode * net_cookedout send user data, ANSI mode, already cooked * net_rawout send telnet protocol data, ANSI mode * */ /* * net_rawout * Send out raw telnet data. We assume that there will always be enough * space to buffer what we want to transmit, so we don't handle EAGAIN or * EWOULDBLOCK. */ static void net_rawout(unsigned const char *buf, int len) { int nw; #if defined(X3270_TRACE) /*[*/ trace_netdata('>', buf, len); #endif /*]*/ while (len) { #if defined(OMTU) /*[*/ int n2w = len; int pause = 0; if (n2w > OMTU) { n2w = OMTU; pause = 1; } #else # define n2w len #endif #if defined(HAVE_LIBSSL) /*[*/ if (ssl_con != NULL) nw = SSL_write(ssl_con, (const char *) buf, n2w); else #endif /*]*/ nw = write(sock, (const char *) buf, n2w); if (nw < 0) { #if defined(HAVE_LIBSSL) /*[*/ if (ssl_con != NULL) { unsigned long e; char err_buf[120]; e = ERR_get_error(); (void) ERR_error_string(e, err_buf); trace_dsn("RCVD SSL_write error %ld (%s)\n", e, err_buf); popup_an_error("SSL_write:\n%s", err_buf); host_disconnect(False); return; } #endif /*]*/ trace_dsn("RCVD socket error %d\n", errno); if (errno == EPIPE || errno == ECONNRESET) { host_disconnect(False); return; } else if (errno == EINTR) { goto bot; } else { popup_an_errno(errno, "Socket write"); host_disconnect(True); return; } } ns_bsent += nw; len -= nw; buf += nw; bot: #if defined(OMTU) /*[*/ if (pause) sleep(1); #endif /*]*/ ; } } #if defined(X3270_ANSI) /*[*/ /* * net_hexansi_out * Send uncontrolled user data to the host in ANSI mode, performing IAC * and CR quoting as necessary. */ void net_hexansi_out(unsigned char *buf, int len) { unsigned char *tbuf; unsigned char *xbuf; if (!len) return; #if defined(X3270_TRACE) /*[*/ /* Trace the data. */ if (toggled(DS_TRACE)) { int i; trace_dsn(">"); for (i = 0; i < len; i++) trace_dsn(" %s", ctl_see((int) *(buf+i))); trace_dsn("\n"); } #endif /*]*/ /* Expand it. */ tbuf = xbuf = (unsigned char *)Malloc(2*len); while (len) { unsigned char c = *buf++; *tbuf++ = c; len--; if (c == IAC) *tbuf++ = IAC; else if (c == '\r' && (!len || *buf != '\n')) *tbuf++ = '\0'; } /* Send it to the host. */ net_rawout(xbuf, tbuf - xbuf); Free(xbuf); } /* * net_cookedout * Send user data out in ANSI mode, without cooked-mode processing. */ static void net_cookedout(const char *buf, int len) { #if defined(X3270_TRACE) /*[*/ if (toggled(DS_TRACE)) { int i; trace_dsn(">"); for (i = 0; i < len; i++) trace_dsn(" %s", ctl_see((int) *(buf+i))); trace_dsn("\n"); } #endif /*]*/ net_rawout((unsigned const char *) buf, len); } /* * net_cookout * Send output in ANSI mode, including cooked-mode processing if * appropriate. */ static void net_cookout(const char *buf, int len) { if (!IN_ANSI || (kybdlock & KL_AWAITING_FIRST)) return; if (linemode) { register int i; char c; for (i = 0; i < len; i++) { c = buf[i]; /* Input conversions. */ if (!lnext && c == '\r' && appres.icrnl) c = '\n'; else if (!lnext && c == '\n' && appres.inlcr) c = '\r'; /* Backslashes. */ if (c == '\\' && !backslashed) backslashed = 1; else backslashed = 0; /* Control chars. */ if (c == '\n') do_eol(c); else if (c == vintr) do_intr(c); else if (c == vquit) do_quit(c); else if (c == verase) do_cerase(c); else if (c == vkill) do_kill(c); else if (c == vwerase) do_werase(c); else if (c == vrprnt) do_rprnt(c); else if (c == veof) do_eof(c); else if (c == vlnext) do_lnext(c); else if (c == 0x08 || c == 0x7f) /* Yes, a hack. */ do_cerase(c); else do_data(c); } return; } else net_cookedout(buf, len); } /* * Cooked mode input processing. */ static void cooked_init(void) { if (lbuf == (unsigned char *)NULL) lbuf = (unsigned char *)Malloc(BUFSZ); lbptr = lbuf; lnext = 0; backslashed = 0; } static void ansi_process_s(const char *data) { while (*data) ansi_process((unsigned int) *data++); } static void forward_data(void) { net_cookedout((char *) lbuf, lbptr - lbuf); cooked_init(); } static void do_data(char c) { if (lbptr+1 < lbuf + BUFSZ) { *lbptr++ = c; if (c == '\r') *lbptr++ = '\0'; if (c == '\t') ansi_process((unsigned int) c); else ansi_process_s(ctl_see((int) c)); } else ansi_process_s("\007"); lnext = 0; backslashed = 0; } static void do_intr(char c) { if (lnext) { do_data(c); return; } ansi_process_s(ctl_see((int) c)); cooked_init(); net_interrupt(); } static void do_quit(char c) { if (lnext) { do_data(c); return; } ansi_process_s(ctl_see((int) c)); cooked_init(); net_break(); } static void do_cerase(char c) { int len; if (backslashed) { lbptr--; ansi_process_s("\b"); do_data(c); return; } if (lnext) { do_data(c); return; } if (lbptr > lbuf) { len = strlen(ctl_see((int) *--lbptr)); while (len--) ansi_process_s("\b \b"); } } static void do_werase(char c) { int any = 0; int len; if (lnext) { do_data(c); return; } while (lbptr > lbuf) { char ch; ch = *--lbptr; if (ch == ' ' || ch == '\t') { if (any) { ++lbptr; break; } } else any = 1; len = strlen(ctl_see((int) ch)); while (len--) ansi_process_s("\b \b"); } } static void do_kill(char c) { int i, len; if (backslashed) { lbptr--; ansi_process_s("\b"); do_data(c); return; } if (lnext) { do_data(c); return; } while (lbptr > lbuf) { len = strlen(ctl_see((int) *--lbptr)); for (i = 0; i < len; i++) ansi_process_s("\b \b"); } } static void do_rprnt(char c) { unsigned char *p; if (lnext) { do_data(c); return; } ansi_process_s(ctl_see((int) c)); ansi_process_s("\r\n"); for (p = lbuf; p < lbptr; p++) ansi_process_s(ctl_see((int) *p)); } static void do_eof(char c) { if (backslashed) { lbptr--; ansi_process_s("\b"); do_data(c); return; } if (lnext) { do_data(c); return; } do_data(c); forward_data(); } static void do_eol(char c) { if (lnext) { do_data(c); return; } if (lbptr+2 >= lbuf + BUFSZ) { ansi_process_s("\007"); return; } *lbptr++ = '\r'; *lbptr++ = '\n'; ansi_process_s("\r\n"); forward_data(); } static void do_lnext(char c) { if (lnext) { do_data(c); return; } lnext = 1; ansi_process_s("^\b"); } #endif /*]*/ /* * check_in3270 * Check for switches between NVT, SSCP-LU and 3270 modes. */ static void check_in3270(void) { enum cstate new_cstate = NOT_CONNECTED; #if defined(X3270_TRACE) /*[*/ static const char *state_name[] = { "unconnected", "resolving", "pending", "connected initial", "TN3270 NVT", "TN3270 3270", "TN3270E", "TN3270E NVT", "TN3270E SSCP-LU", "TN3270E 3270" }; #endif /*]*/ #if defined(X3270_TN3270E) /*[*/ if (myopts[TELOPT_TN3270E]) { if (!tn3270e_negotiated) new_cstate = CONNECTED_INITIAL_E; else switch (tn3270e_submode) { case E_NONE: new_cstate = CONNECTED_INITIAL_E; break; case E_NVT: new_cstate = CONNECTED_NVT; break; case E_3270: new_cstate = CONNECTED_TN3270E; break; case E_SSCP: new_cstate = CONNECTED_SSCP; break; } } else #endif /*]*/ if (myopts[TELOPT_BINARY] && myopts[TELOPT_EOR] && myopts[TELOPT_TTYPE] && hisopts[TELOPT_BINARY] && hisopts[TELOPT_EOR]) { new_cstate = CONNECTED_3270; } else if (cstate == CONNECTED_INITIAL) { /* Nothing has happened, yet. */ return; } else { new_cstate = CONNECTED_ANSI; } if (new_cstate != cstate) { #if defined(X3270_TN3270E) /*[*/ int was_in_e = IN_E; #endif /*]*/ #if defined(X3270_TN3270E) /*[*/ /* * If we've now switched between non-TN3270E mode and * TN3270E mode, reset the LU list so we can try again * in the new mode. */ if (lus != (char **)NULL && was_in_e != IN_E) { curr_lu = lus; try_lu = *curr_lu; } #endif /*]*/ /* Allocate the initial 3270 input buffer. */ if (new_cstate >= CONNECTED_INITIAL && !ibuf_size) { ibuf = (unsigned char *)Malloc(BUFSIZ); ibuf_size = BUFSIZ; ibptr = ibuf; } #if defined(X3270_ANSI) /*[*/ /* Reinitialize line mode. */ if ((new_cstate == CONNECTED_ANSI && linemode) || new_cstate == CONNECTED_NVT) cooked_init(); #endif /*]*/ #if defined(X3270_TN3270E) /*[*/ /* If we fell out of TN3270E, remove the state. */ if (!myopts[TELOPT_TN3270E]) { tn3270e_negotiated = 0; tn3270e_submode = E_NONE; tn3270e_bound = 0; } #endif /*]*/ trace_dsn("Now operating in %s mode.\n", state_name[new_cstate]); host_in3270(new_cstate); } } /* * store3270in * Store a character in the 3270 input buffer, checking for buffer * overflow and reallocating ibuf if necessary. */ static void store3270in(unsigned char c) { if (ibptr - ibuf >= ibuf_size) { ibuf_size += BUFSIZ; ibuf = (unsigned char *)Realloc((char *)ibuf, ibuf_size); ibptr = ibuf + ibuf_size - BUFSIZ; } *ibptr++ = c; } /* * space3270out * Ensure that more characters will fit in the 3270 output buffer. * Allocates the buffer in BUFSIZ chunks. * Allocates hidden space at the front of the buffer for TN3270E. */ void space3270out(int n) { unsigned nc = 0; /* amount of data currently in obuf */ unsigned more = 0; if (obuf_size) nc = obptr - obuf; while ((nc + n + EH_SIZE) > (obuf_size + more)) { more += BUFSIZ; } if (more) { obuf_size += more; obuf_base = (unsigned char *)Realloc((char *)obuf_base, obuf_size); obuf = obuf_base + EH_SIZE; obptr = obuf + nc; } } /* * check_linemode * Set the global variable 'linemode', which says whether we are in * character-by-character mode or line mode. */ static void check_linemode(Boolean init) { int wasline = linemode; /* * The next line is a deliberate kluge to effectively ignore the SGA * option. If the host will echo for us, we assume * character-at-a-time; otherwise we assume fully cooked by us. * * This allows certain IBM hosts which volunteer SGA but refuse * ECHO to operate more-or-less normally, at the expense of * implementing the (hopefully useless) "character-at-a-time, local * echo" mode. * * We still implement "switch to line mode" and "switch to character * mode" properly by asking for both SGA and ECHO to be off or on, but * we basically ignore the reply for SGA. */ linemode = !hisopts[TELOPT_ECHO] /* && !hisopts[TELOPT_SGA] */; if (init || linemode != wasline) { st_changed(ST_LINE_MODE, linemode); if (!init) { trace_dsn("Operating in %s mode.\n", linemode ? "line" : "character-at-a-time"); } #if defined(X3270_ANSI) /*[*/ if (IN_ANSI && linemode) cooked_init(); #endif /*]*/ } } #if defined(X3270_TRACE) /*[*/ /* * nnn * Expands a number to a character string, for displaying unknown telnet * commands and options. */ static const char * nnn(int c) { static char buf[64]; (void) sprintf(buf, "%d", c); return buf; } /* * cmd * Expands a TELNET command into a character string. */ static const char * cmd(int c) { if (TELCMD_OK(c)) return TELCMD(c); else return nnn(c); } /* * opt * Expands a TELNET option into a character string. */ static const char * opt(unsigned char c) { if (TELOPT_OK(c)) return TELOPT(c); else if (c == TELOPT_TN3270E) return "TN3270E"; #if defined(HAVE_LIBSSL) /*[*/ else if (c == TELOPT_STARTTLS) return "START-TLS"; #endif /*]*/ else return nnn((int)c); } #define LINEDUMP_MAX 32 void trace_netdata(char direction, unsigned const char *buf, int len) { int offset; struct timeval ts; double tdiff; if (!toggled(DS_TRACE)) return; (void) gettimeofday(&ts, (struct timezone *)NULL); if (IN_3270) { tdiff = ((1.0e6 * (double)(ts.tv_sec - ds_ts.tv_sec)) + (double)(ts.tv_usec - ds_ts.tv_usec)) / 1.0e6; trace_dsn("%c +%gs\n", direction, tdiff); } ds_ts = ts; for (offset = 0; offset < len; offset++) { if (!(offset % LINEDUMP_MAX)) trace_dsn("%s%c 0x%-3x ", (offset ? "\n" : ""), direction, offset); trace_dsn("%02x", buf[offset]); } trace_dsn("\n"); } #endif /*]*/ /* * net_output * Send 3270 output over the network: * - Prepend TN3270E header * - Expand IAC to IAC IAC * - Append IAC EOR */ void net_output(void) { static unsigned char *xobuf = NULL; static int xobuf_len = 0; int need_resize = 0; unsigned char *nxoptr, *xoptr; #if defined(X3270_TN3270E) /*[*/ #define BSTART ((IN_TN3270E || IN_SSCP) ? obuf_base : obuf) #else /*][*/ #define BSTART obuf #endif /*]*/ #if defined(X3270_TN3270E) /*[*/ /* Set the TN3720E header. */ if (IN_TN3270E || IN_SSCP) { tn3270e_header *h = (tn3270e_header *)obuf_base; /* Check for sending a TN3270E response. */ if (response_required == TN3270E_RSF_ALWAYS_RESPONSE) { tn3270e_ack(); response_required = TN3270E_RSF_NO_RESPONSE; } /* Set the outbound TN3270E header. */ h->data_type = IN_TN3270E ? TN3270E_DT_3270_DATA : TN3270E_DT_SSCP_LU_DATA; h->request_flag = 0; h->response_flag = 0; h->seq_number[0] = (e_xmit_seq >> 8) & 0xff; h->seq_number[1] = e_xmit_seq & 0xff; trace_dsn("SENT TN3270E(%s NO-RESPONSE %u)\n", IN_TN3270E ? "3270-DATA" : "SSCP-LU-DATA", e_xmit_seq); if (e_funcs & E_OPT(TN3270E_FUNC_RESPONSES)) e_xmit_seq = (e_xmit_seq + 1) & 0x7fff; } #endif /*]*/ /* Reallocate the expanded output buffer. */ while (xobuf_len < (obptr - BSTART + 1) * 2) { xobuf_len += BUFSZ; need_resize++; } if (need_resize) { Replace(xobuf, (unsigned char *)Malloc(xobuf_len)); } /* Copy and expand IACs. */ xoptr = xobuf; nxoptr = BSTART; while (nxoptr < obptr) { if ((*xoptr++ = *nxoptr++) == IAC) { *xoptr++ = IAC; } } /* Append the IAC EOR and transmit. */ *xoptr++ = IAC; *xoptr++ = EOR; net_rawout(xobuf, xoptr - xobuf); trace_dsn("SENT EOR\n"); ns_rsent++; #undef BSTART } #if defined(X3270_TN3270E) /*[*/ /* Send a TN3270E positive response to the server. */ static void tn3270e_ack(void) { unsigned char rsp_buf[10]; tn3270e_header *h_in = (tn3270e_header *)ibuf; int rsp_len = 0; rsp_len = 0; rsp_buf[rsp_len++] = TN3270E_DT_RESPONSE; /* data_type */ rsp_buf[rsp_len++] = 0; /* request_flag */ rsp_buf[rsp_len++] = TN3270E_RSF_POSITIVE_RESPONSE; /* response_flag */ rsp_buf[rsp_len++] = h_in->seq_number[0]; /* seq_number[0] */ if (h_in->seq_number[0] == IAC) rsp_buf[rsp_len++] = IAC; rsp_buf[rsp_len++] = h_in->seq_number[1]; /* seq_number[1] */ if (h_in->seq_number[1] == IAC) rsp_buf[rsp_len++] = IAC; rsp_buf[rsp_len++] = TN3270E_POS_DEVICE_END; rsp_buf[rsp_len++] = IAC; rsp_buf[rsp_len++] = EOR; trace_dsn("SENT TN3270E(RESPONSE POSITIVE-RESPONSE " "%u) DEVICE-END\n", h_in->seq_number[0] << 8 | h_in->seq_number[1]); net_rawout(rsp_buf, rsp_len); } /* Send a TN3270E negative response to the server. */ static void tn3270e_nak(enum pds rv) { unsigned char rsp_buf[10]; tn3270e_header *h_in = (tn3270e_header *)ibuf; int rsp_len = 0; char *neg = NULL; rsp_buf[rsp_len++] = TN3270E_DT_RESPONSE; /* data_type */ rsp_buf[rsp_len++] = 0; /* request_flag */ rsp_buf[rsp_len++] = TN3270E_RSF_NEGATIVE_RESPONSE; /* response_flag */ rsp_buf[rsp_len++] = h_in->seq_number[0]; /* seq_number[0] */ if (h_in->seq_number[0] == IAC) rsp_buf[rsp_len++] = IAC; rsp_buf[rsp_len++] = h_in->seq_number[1]; /* seq_number[1] */ if (h_in->seq_number[1] == IAC) rsp_buf[rsp_len++] = IAC; switch (rv) { default: case PDS_BAD_CMD: rsp_buf[rsp_len++] = TN3270E_NEG_COMMAND_REJECT; neg = "COMMAND-REJECT"; break; case PDS_BAD_ADDR: rsp_buf[rsp_len++] = TN3270E_NEG_OPERATION_CHECK; neg = "OPERATION-CHECK"; break; } rsp_buf[rsp_len++] = IAC; rsp_buf[rsp_len++] = EOR; trace_dsn("SENT TN3270E(RESPONSE NEGATIVE-RESPONSE %u) %s\n", h_in->seq_number[0] << 8 | h_in->seq_number[1], neg); net_rawout(rsp_buf, rsp_len); } #if defined(X3270_TRACE) /*[*/ /* Add a dummy TN3270E header to the output buffer. */ Boolean net_add_dummy_tn3270e(void) { tn3270e_header *h; if (!IN_E || tn3270e_submode == E_NONE) return False; space3270out(EH_SIZE); h = (tn3270e_header *)obptr; switch (tn3270e_submode) { case E_NONE: break; case E_NVT: h->data_type = TN3270E_DT_NVT_DATA; break; case E_SSCP: h->data_type = TN3270E_DT_SSCP_LU_DATA; break; case E_3270: h->data_type = TN3270E_DT_3270_DATA; break; } h->request_flag = 0; h->response_flag = TN3270E_RSF_NO_RESPONSE; h->seq_number[0] = 0; h->seq_number[1] = 0; obptr += EH_SIZE; return True; } #endif /*]*/ #endif /*]*/ #if defined(X3270_TRACE) /*[*/ /* * Add IAC EOR to a buffer. */ void net_add_eor(unsigned char *buf, int len) { buf[len++] = IAC; buf[len++] = EOR; } #endif /*]*/ #if defined(X3270_ANSI) /*[*/ /* * net_sendc * Send a character of user data over the network in ANSI mode. */ void net_sendc(char c) { if (c == '\r' && !linemode #if defined(LOCAL_PROCESS) /*[*/ && !local_process #endif /*]*/ ) { /* CR must be quoted */ net_cookout("\r\0", 2); } else { net_cookout(&c, 1); } } /* * net_sends * Send a null-terminated string of user data in ANSI mode. */ void net_sends(const char *s) { net_cookout(s, strlen(s)); } /* * net_send_erase * Sends the KILL character in ANSI mode. */ void net_send_erase(void) { net_cookout(&verase, 1); } /* * net_send_kill * Sends the KILL character in ANSI mode. */ void net_send_kill(void) { net_cookout(&vkill, 1); } /* * net_send_werase * Sends the WERASE character in ANSI mode. */ void net_send_werase(void) { net_cookout(&vwerase, 1); } #endif /*]*/ #if defined(X3270_MENUS) /*[*/ /* * External entry points to negotiate line or character mode. */ void net_linemode(void) { if (!CONNECTED) return; if (hisopts[TELOPT_ECHO]) { dont_opt[2] = TELOPT_ECHO; net_rawout(dont_opt, sizeof(dont_opt)); trace_dsn("SENT %s %s\n", cmd(DONT), opt(TELOPT_ECHO)); } if (hisopts[TELOPT_SGA]) { dont_opt[2] = TELOPT_SGA; net_rawout(dont_opt, sizeof(dont_opt)); trace_dsn("SENT %s %s\n", cmd(DONT), opt(TELOPT_SGA)); } } void net_charmode(void) { if (!CONNECTED) return; if (!hisopts[TELOPT_ECHO]) { do_opt[2] = TELOPT_ECHO; net_rawout(do_opt, sizeof(do_opt)); trace_dsn("SENT %s %s\n", cmd(DO), opt(TELOPT_ECHO)); } if (!hisopts[TELOPT_SGA]) { do_opt[2] = TELOPT_SGA; net_rawout(do_opt, sizeof(do_opt)); trace_dsn("SENT %s %s\n", cmd(DO), opt(TELOPT_SGA)); } } #endif /*]*/ /* * net_break * Send telnet break, which is used to implement 3270 ATTN. * */ void net_break(void) { static unsigned char buf[] = { IAC, BREAK }; /* I don't know if we should first send TELNET synch ? */ net_rawout(buf, sizeof(buf)); trace_dsn("SENT BREAK\n"); } /* * net_interrupt * Send telnet IP. * */ void net_interrupt(void) { static unsigned char buf[] = { IAC, IP }; /* I don't know if we should first send TELNET synch ? */ net_rawout(buf, sizeof(buf)); trace_dsn("SENT IP\n"); } /* * net_abort * Send telnet AO. * */ #if defined(X3270_TN3270E) /*[*/ void net_abort(void) { static unsigned char buf[] = { IAC, AO }; if (e_funcs & E_OPT(TN3270E_FUNC_SYSREQ)) { /* * I'm not sure yet what to do here. Should the host respond * to the AO by sending us SSCP-LU data (and putting us into * SSCP-LU mode), or should we put ourselves in it? * Time, and testers, will tell. */ switch (tn3270e_submode) { case E_NONE: case E_NVT: break; case E_SSCP: net_rawout(buf, sizeof(buf)); trace_dsn("SENT AO\n"); if (tn3270e_bound || !(e_funcs & E_OPT(TN3270E_FUNC_BIND_IMAGE))) { tn3270e_submode = E_3270; check_in3270(); } break; case E_3270: net_rawout(buf, sizeof(buf)); trace_dsn("SENT AO\n"); tn3270e_submode = E_SSCP; check_in3270(); break; } } } #endif /*]*/ #if defined(X3270_ANSI) /*[*/ /* * parse_ctlchar * Parse an stty control-character specification. * A cheap, non-complaining implementation. */ static char parse_ctlchar(char *s) { if (!s || !*s) return 0; if ((int) strlen(s) > 1) { if (*s != '^') return 0; else if (*(s+1) == '?') return 0177; else return *(s+1) - '@'; } else return *s; } #endif /*]*/ #if (defined(X3270_MENUS) || defined(C3270)) && defined(X3270_ANSI) /*[*/ /* * net_linemode_chars * Report line-mode characters. */ struct ctl_char * net_linemode_chars(void) { static struct ctl_char c[9]; c[0].name = "intr"; (void) strcpy(c[0].value, ctl_see(vintr)); c[1].name = "quit"; (void) strcpy(c[1].value, ctl_see(vquit)); c[2].name = "erase"; (void) strcpy(c[2].value, ctl_see(verase)); c[3].name = "kill"; (void) strcpy(c[3].value, ctl_see(vkill)); c[4].name = "eof"; (void) strcpy(c[4].value, ctl_see(veof)); c[5].name = "werase"; (void) strcpy(c[5].value, ctl_see(vwerase)); c[6].name = "rprnt"; (void) strcpy(c[6].value, ctl_see(vrprnt)); c[7].name = "lnext"; (void) strcpy(c[7].value, ctl_see(vlnext)); c[8].name = 0; return c; } #endif /*]*/ #if defined(X3270_TRACE) /*[*/ /* * Construct a string to reproduce the current TELNET options. * Returns a Boolean indicating whether it is necessary. */ Boolean net_snap_options(void) { Boolean any = False; int i; static unsigned char ttype_str[] = { IAC, DO, TELOPT_TTYPE, IAC, SB, TELOPT_TTYPE, TELQUAL_SEND, IAC, SE }; if (!CONNECTED) return False; obptr = obuf; /* Do TTYPE first. */ if (myopts[TELOPT_TTYPE]) { unsigned j; space3270out(sizeof(ttype_str)); for (j = 0; j < sizeof(ttype_str); j++) *obptr++ = ttype_str[j]; } /* Do the other options. */ for (i = 0; i < N_OPTS; i++) { space3270out(6); if (i == TELOPT_TTYPE) continue; if (hisopts[i]) { *obptr++ = IAC; *obptr++ = WILL; *obptr++ = (unsigned char)i; any = True; } if (myopts[i]) { *obptr++ = IAC; *obptr++ = DO; *obptr++ = (unsigned char)i; any = True; } } #if defined(X3270_TN3270E) /*[*/ /* If we're in TN3270E mode, snap the subnegotations as well. */ if (myopts[TELOPT_TN3270E]) { any = True; space3270out(5 + ((connected_type != CN) ? strlen(connected_type) : 0) + ((connected_lu != CN) ? + strlen(connected_lu) : 0) + 2); *obptr++ = IAC; *obptr++ = SB; *obptr++ = TELOPT_TN3270E; *obptr++ = TN3270E_OP_DEVICE_TYPE; *obptr++ = TN3270E_OP_IS; if (connected_type != CN) { (void) memcpy(obptr, connected_type, strlen(connected_type)); obptr += strlen(connected_type); } if (connected_lu != CN) { *obptr++ = TN3270E_OP_CONNECT; (void) memcpy(obptr, connected_lu, strlen(connected_lu)); obptr += strlen(connected_lu); } *obptr++ = IAC; *obptr++ = SE; space3270out(38); (void) memcpy(obptr, functions_req, 4); obptr += 4; *obptr++ = TN3270E_OP_IS; for (i = 0; i < 32; i++) { if (e_funcs & E_OPT(i)) *obptr++ = i; } *obptr++ = IAC; *obptr++ = SE; if (tn3270e_bound) { tn3270e_header *h; space3270out(EH_SIZE + 3); h = (tn3270e_header *)obptr; h->data_type = TN3270E_DT_BIND_IMAGE; h->request_flag = 0; h->response_flag = 0; h->seq_number[0] = 0; h->seq_number[1] = 0; obptr += EH_SIZE; *obptr++ = 1; /* dummy */ *obptr++ = IAC; *obptr++ = EOR; } } #endif /*]*/ return any; } #endif /*]*/ /* * Set blocking/non-blocking mode on the socket. On error, pops up an error * message, but does not close the socket. */ static int non_blocking(Boolean on) { #if !defined(BLOCKING_CONNECT_ONLY) /*[*/ # if defined(FIONBIO) /*[*/ int i = on ? 1 : 0; if (ioctl(sock, FIONBIO, &i) < 0) { popup_an_errno(errno, "ioctl(FIONBIO)"); return -1; } # else /*][*/ int f; if ((f = fcntl(sock, F_GETFL, 0)) == -1) { popup_an_errno(errno, "fcntl(F_GETFL)"); return -1; } if (on) f |= O_NDELAY; else f &= ~O_NDELAY; if (fcntl(sock, F_SETFL, f) < 0) { popup_an_errno(errno, "fcntl(F_SETFL)"); return -1; } # endif /*]*/ #endif /*]*/ return 0; } #if defined(HAVE_LIBSSL) /*[*/ /* Initialize the OpenSSL library. */ static void ssl_init(void) { static Boolean ssl_initted = False; if (!ssl_initted) { SSL_load_error_strings(); SSL_library_init(); ssl_initted = True; ssl_ctx = SSL_CTX_new(SSLv23_method()); if (ssl_ctx == NULL) { popup_an_error("SSL_CTX_new failed"); ssl_host = False; return; } SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL); } ssl_con = SSL_new(ssl_ctx); if (ssl_con == NULL) { popup_an_error("SSL_new failed"); ssl_host = False; } SSL_set_verify(ssl_con, 0/*xxx*/, NULL); SSL_CTX_set_info_callback(ssl_ctx, client_info_callback); /* XXX: May need to get key file and password. */ if (appres.cert_file) { if (!(SSL_CTX_use_certificate_chain_file(ssl_ctx, appres.cert_file))) { unsigned long e; char err_buf[120]; e = ERR_get_error(); (void) ERR_error_string(e, err_buf); popup_an_error("SSL_CTX_use_certificate_chain_file(" "\"%s\") failed:\n%s", appres.cert_file, err_buf); } } SSL_CTX_set_default_verify_paths(ssl_ctx); } /* Callback for tracing protocol negotiation. */ static void client_info_callback(INFO_CONST SSL *s, int where, int ret) { if (where == SSL_CB_CONNECT_LOOP) { trace_dsn("SSL_connect: %s %s\n", SSL_state_string(s), SSL_state_string_long(s)); } else if (where == SSL_CB_CONNECT_EXIT) { if (ret == 0) { trace_dsn("SSL_connect: failed in %s\n", SSL_state_string_long(s)); } else if (ret < 0) { unsigned long e; char err_buf[121]; err_buf[0] = '\n'; e = ERR_get_error(); if (e != 0) (void) ERR_error_string(e, err_buf + 1); else if (errno != 0) strcpy(err_buf + 1, strerror(errno)); else err_buf[0] = '\0'; popup_an_error("SSL_connect: error in %s%s", SSL_state_string_long(s), err_buf); } } } /* Process a STARTTLS subnegotiation. */ static void continue_tls(unsigned char *sbbuf, int len) { /* Whatever happens, we're not expecting another SB STARTTLS. */ need_tls_follows = False; /* Make sure the option is FOLLOWS. */ if (len < 2 || sbbuf[1] != TLS_FOLLOWS) { /* Trace the junk. */ trace_dsn("%s ? %s\n", opt(TELOPT_STARTTLS), cmd(SE)); popup_an_error("TLS negotiation failure"); net_disconnect(); return; } /* Trace what we got. */ trace_dsn("%s FOLLOWS %s\n", opt(TELOPT_STARTTLS), cmd(SE)); /* Initialize the SSL library. */ ssl_init(); if (ssl_con == NULL) { /* Failed. */ net_disconnect(); return; } /* Set up the TLS/SSL connection. */ SSL_set_fd(ssl_con, sock); if (SSL_connect(ssl_con) != 1) { /* Error already displayed. */ net_disconnect(); return; } secure_connection = True; /* Success. */ trace_dsn("TLS/SSL negotiated connection complete. " "Connection is now secure.\n"); /* Tell the world that we are (still) connected, now in secure mode. */ host_connected(); } #endif /*]*/ #if defined(X3270_SCRIPT) || defined(TCL3270) /*[*/ /* Return the current BIND application name, if any. */ const char * net_query_bind_plu_name(void) { #if defined(X3270_TN3270E) /*[*/ /* * Return the PLU name, if we're in TN3270E 3270 mode and have * negotiated the BIND-IMAGE option. */ if ((cstate == CONNECTED_TN3270E) && (e_funcs & E_OPT(TN3270E_FUNC_BIND_IMAGE))) return plu_name; else return ""; #else /*][*/ /* No TN3270E, no BIND negotiation. */ return ""; #endif /*]*/ } /* Return the current connection state. */ const char * net_query_connection_state(void) { if (CONNECTED) { #if defined(X3270_TN3270E) /*[*/ if (IN_E) { switch (tn3270e_submode) { default: case E_NONE: if (tn3270e_bound) return "tn3270e bound"; else return "tn3270e unbound"; case E_3270: return "tn3270e lu-lu"; case E_NVT: return "tn3270e nvt"; case E_SSCP: return "tn3270 sscp-lu"; } } else #endif /*]*/ { if (IN_3270) return "tn3270 3270"; else return "tn3270 nvt"; } } else if (HALF_CONNECTED) return "connecting"; else return ""; } /* Return the LU name. */ const char * net_query_lu_name(void) { if (CONNECTED && connected_lu != CN) return connected_lu; else return ""; } /* Return the hostname and port. */ const char * net_query_host(void) { static char *s = CN; if (CONNECTED) { Free(s); #if defined(LOCAL_PROCESS) /*[*/ if (local_process) { s = xs_buffer("process %s", hostname); } else #endif /*]*/ { s = xs_buffer("host %s %u %s", hostname, current_port, #if defined(HAVE_LIBSSL) /*[*/ secure_connection? "encrypted": #endif /*]*/ "unencrypted" ); } return s; } else return ""; } #endif /*]*/ /* Return the local address for the socket. */ int net_getsockname(void *buf, int *len) { if (sock < 0) return -1; return getsockname(sock, buf, (socklen_t *)(void *)len); }