| 1 | /* |
| 2 | * Simple Telnet server code, adapted from PuTTY's own Telnet |
| 3 | * client code for use as a Cygwin local pty proxy. |
| 4 | */ |
| 5 | |
| 6 | #include <stdio.h> |
| 7 | #include <stdlib.h> |
| 8 | #include <string.h> |
| 9 | |
| 10 | #include "sel.h" |
| 11 | #include "telnet.h" |
| 12 | #include "malloc.h" |
| 13 | #include "pty.h" |
| 14 | |
| 15 | #ifndef FALSE |
| 16 | #define FALSE 0 |
| 17 | #endif |
| 18 | #ifndef TRUE |
| 19 | #define TRUE 1 |
| 20 | #endif |
| 21 | |
| 22 | #define IAC 255 /* interpret as command: */ |
| 23 | #define DONT 254 /* you are not to use option */ |
| 24 | #define DO 253 /* please, you use option */ |
| 25 | #define WONT 252 /* I won't use option */ |
| 26 | #define WILL 251 /* I will use option */ |
| 27 | #define SB 250 /* interpret as subnegotiation */ |
| 28 | #define SE 240 /* end sub negotiation */ |
| 29 | |
| 30 | #define GA 249 /* you may reverse the line */ |
| 31 | #define EL 248 /* erase the current line */ |
| 32 | #define EC 247 /* erase the current character */ |
| 33 | #define AYT 246 /* are you there */ |
| 34 | #define AO 245 /* abort output--but let prog finish */ |
| 35 | #define IP 244 /* interrupt process--permanently */ |
| 36 | #define BREAK 243 /* break */ |
| 37 | #define DM 242 /* data mark--for connect. cleaning */ |
| 38 | #define NOP 241 /* nop */ |
| 39 | #define EOR 239 /* end of record (transparent mode) */ |
| 40 | #define ABORT 238 /* Abort process */ |
| 41 | #define SUSP 237 /* Suspend process */ |
| 42 | #define xEOF 236 /* End of file: EOF is already used... */ |
| 43 | |
| 44 | #define TELOPTS(X) \ |
| 45 | X(BINARY, 0) /* 8-bit data path */ \ |
| 46 | X(ECHO, 1) /* echo */ \ |
| 47 | X(RCP, 2) /* prepare to reconnect */ \ |
| 48 | X(SGA, 3) /* suppress go ahead */ \ |
| 49 | X(NAMS, 4) /* approximate message size */ \ |
| 50 | X(STATUS, 5) /* give status */ \ |
| 51 | X(TM, 6) /* timing mark */ \ |
| 52 | X(RCTE, 7) /* remote controlled transmission and echo */ \ |
| 53 | X(NAOL, 8) /* negotiate about output line width */ \ |
| 54 | X(NAOP, 9) /* negotiate about output page size */ \ |
| 55 | X(NAOCRD, 10) /* negotiate about CR disposition */ \ |
| 56 | X(NAOHTS, 11) /* negotiate about horizontal tabstops */ \ |
| 57 | X(NAOHTD, 12) /* negotiate about horizontal tab disposition */ \ |
| 58 | X(NAOFFD, 13) /* negotiate about formfeed disposition */ \ |
| 59 | X(NAOVTS, 14) /* negotiate about vertical tab stops */ \ |
| 60 | X(NAOVTD, 15) /* negotiate about vertical tab disposition */ \ |
| 61 | X(NAOLFD, 16) /* negotiate about output LF disposition */ \ |
| 62 | X(XASCII, 17) /* extended ascic character set */ \ |
| 63 | X(LOGOUT, 18) /* force logout */ \ |
| 64 | X(BM, 19) /* byte macro */ \ |
| 65 | X(DET, 20) /* data entry terminal */ \ |
| 66 | X(SUPDUP, 21) /* supdup protocol */ \ |
| 67 | X(SUPDUPOUTPUT, 22) /* supdup output */ \ |
| 68 | X(SNDLOC, 23) /* send location */ \ |
| 69 | X(TTYPE, 24) /* terminal type */ \ |
| 70 | X(EOR, 25) /* end or record */ \ |
| 71 | X(TUID, 26) /* TACACS user identification */ \ |
| 72 | X(OUTMRK, 27) /* output marking */ \ |
| 73 | X(TTYLOC, 28) /* terminal location number */ \ |
| 74 | X(3270REGIME, 29) /* 3270 regime */ \ |
| 75 | X(X3PAD, 30) /* X.3 PAD */ \ |
| 76 | X(NAWS, 31) /* window size */ \ |
| 77 | X(TSPEED, 32) /* terminal speed */ \ |
| 78 | X(LFLOW, 33) /* remote flow control */ \ |
| 79 | X(LINEMODE, 34) /* Linemode option */ \ |
| 80 | X(XDISPLOC, 35) /* X Display Location */ \ |
| 81 | X(OLD_ENVIRON, 36) /* Old - Environment variables */ \ |
| 82 | X(AUTHENTICATION, 37) /* Authenticate */ \ |
| 83 | X(ENCRYPT, 38) /* Encryption option */ \ |
| 84 | X(NEW_ENVIRON, 39) /* New - Environment variables */ \ |
| 85 | X(TN3270E, 40) /* TN3270 enhancements */ \ |
| 86 | X(XAUTH, 41) \ |
| 87 | X(CHARSET, 42) /* Character set */ \ |
| 88 | X(RSP, 43) /* Remote serial port */ \ |
| 89 | X(COM_PORT_OPTION, 44) /* Com port control */ \ |
| 90 | X(SLE, 45) /* Suppress local echo */ \ |
| 91 | X(STARTTLS, 46) /* Start TLS */ \ |
| 92 | X(KERMIT, 47) /* Automatic Kermit file transfer */ \ |
| 93 | X(SEND_URL, 48) \ |
| 94 | X(FORWARD_X, 49) \ |
| 95 | X(PRAGMA_LOGON, 138) \ |
| 96 | X(SSPI_LOGON, 139) \ |
| 97 | X(PRAGMA_HEARTBEAT, 140) \ |
| 98 | X(EXOPL, 255) /* extended-options-list */ |
| 99 | |
| 100 | #define telnet_enum(x,y) TELOPT_##x = y, |
| 101 | enum { TELOPTS(telnet_enum) dummy=0 }; |
| 102 | #undef telnet_enum |
| 103 | |
| 104 | #define TELQUAL_IS 0 /* option is... */ |
| 105 | #define TELQUAL_SEND 1 /* send option */ |
| 106 | #define TELQUAL_INFO 2 /* ENVIRON: informational version of IS */ |
| 107 | #define BSD_VAR 1 |
| 108 | #define BSD_VALUE 0 |
| 109 | #define RFC_VAR 0 |
| 110 | #define RFC_VALUE 1 |
| 111 | |
| 112 | #define CR 13 |
| 113 | #define LF 10 |
| 114 | #define NUL 0 |
| 115 | |
| 116 | #define iswritable(x) ( (x) != IAC && (x) != CR ) |
| 117 | |
| 118 | static char *telopt(int opt) |
| 119 | { |
| 120 | #define telnet_str(x,y) case TELOPT_##x: return #x; |
| 121 | switch (opt) { |
| 122 | TELOPTS(telnet_str) |
| 123 | default: |
| 124 | return "<unknown>"; |
| 125 | } |
| 126 | #undef telnet_str |
| 127 | } |
| 128 | |
| 129 | static void telnet_size(void *handle, int width, int height); |
| 130 | |
| 131 | struct Opt { |
| 132 | int send; /* what we initially send */ |
| 133 | int nsend; /* -ve send if requested to stop it */ |
| 134 | int ack, nak; /* +ve and -ve acknowledgements */ |
| 135 | int option; /* the option code */ |
| 136 | int index; /* index into telnet->opt_states[] */ |
| 137 | enum { |
| 138 | REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE |
| 139 | } initial_state; |
| 140 | }; |
| 141 | |
| 142 | enum { |
| 143 | OPTINDEX_NAWS, |
| 144 | OPTINDEX_TSPEED, |
| 145 | OPTINDEX_TTYPE, |
| 146 | OPTINDEX_OENV, |
| 147 | OPTINDEX_NENV, |
| 148 | OPTINDEX_ECHO, |
| 149 | OPTINDEX_WE_SGA, |
| 150 | OPTINDEX_THEY_SGA, |
| 151 | OPTINDEX_WE_BIN, |
| 152 | OPTINDEX_THEY_BIN, |
| 153 | NUM_OPTS |
| 154 | }; |
| 155 | |
| 156 | static const struct Opt o_naws = |
| 157 | { DO, DONT, WILL, WONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED }; |
| 158 | static const struct Opt o_ttype = |
| 159 | { DO, DONT, WILL, WONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED }; |
| 160 | static const struct Opt o_oenv = |
| 161 | { DO, DONT, WILL, WONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE }; |
| 162 | static const struct Opt o_nenv = |
| 163 | { DO, DONT, WILL, WONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED }; |
| 164 | static const struct Opt o_echo = |
| 165 | { WILL, WONT, DO, DONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED }; |
| 166 | static const struct Opt o_they_sga = |
| 167 | { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED }; |
| 168 | static const struct Opt o_we_sga = |
| 169 | { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED }; |
| 170 | |
| 171 | static const struct Opt *const opts[] = { |
| 172 | &o_echo, &o_we_sga, &o_they_sga, &o_naws, &o_ttype, &o_oenv, &o_nenv, NULL |
| 173 | }; |
| 174 | |
| 175 | struct telnet_tag { |
| 176 | int opt_states[NUM_OPTS]; |
| 177 | |
| 178 | int sb_opt, sb_len; |
| 179 | unsigned char *sb_buf; |
| 180 | int sb_size; |
| 181 | |
| 182 | enum { |
| 183 | TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, |
| 184 | SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR |
| 185 | } state; |
| 186 | |
| 187 | sel_wfd *net, *pty; |
| 188 | |
| 189 | /* |
| 190 | * Options we must finish processing before launching the shell |
| 191 | */ |
| 192 | int old_environ_done, new_environ_done, ttype_done; |
| 193 | |
| 194 | /* |
| 195 | * Ready to start shell? |
| 196 | */ |
| 197 | int shell_ok; |
| 198 | int envvarsize; |
| 199 | struct shell_data shdata; |
| 200 | }; |
| 201 | |
| 202 | #define TELNET_MAX_BACKLOG 4096 |
| 203 | |
| 204 | #define SB_DELTA 1024 |
| 205 | |
| 206 | static void send_opt(Telnet telnet, int cmd, int option) |
| 207 | { |
| 208 | unsigned char b[3]; |
| 209 | |
| 210 | b[0] = IAC; |
| 211 | b[1] = cmd; |
| 212 | b[2] = option; |
| 213 | sel_write(telnet->net, (char *)b, 3); |
| 214 | } |
| 215 | |
| 216 | static void deactivate_option(Telnet telnet, const struct Opt *o) |
| 217 | { |
| 218 | if (telnet->opt_states[o->index] == REQUESTED || |
| 219 | telnet->opt_states[o->index] == ACTIVE) |
| 220 | send_opt(telnet, o->nsend, o->option); |
| 221 | telnet->opt_states[o->index] = REALLY_INACTIVE; |
| 222 | } |
| 223 | |
| 224 | /* |
| 225 | * Generate side effects of enabling or disabling an option. |
| 226 | */ |
| 227 | static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled) |
| 228 | { |
| 229 | } |
| 230 | |
| 231 | static void activate_option(Telnet telnet, const struct Opt *o) |
| 232 | { |
| 233 | if (o->option == TELOPT_NEW_ENVIRON || |
| 234 | o->option == TELOPT_OLD_ENVIRON || |
| 235 | o->option == TELOPT_TTYPE) { |
| 236 | char buf[6]; |
| 237 | buf[0] = IAC; |
| 238 | buf[1] = SB; |
| 239 | buf[2] = o->option; |
| 240 | buf[3] = TELQUAL_SEND; |
| 241 | buf[4] = IAC; |
| 242 | buf[5] = SE; |
| 243 | sel_write(telnet->net, buf, 6); |
| 244 | } |
| 245 | option_side_effects(telnet, o, 1); |
| 246 | } |
| 247 | |
| 248 | static void done_option(Telnet telnet, int option) |
| 249 | { |
| 250 | if (option == TELOPT_OLD_ENVIRON) |
| 251 | telnet->old_environ_done = 1; |
| 252 | else if (option == TELOPT_NEW_ENVIRON) |
| 253 | telnet->new_environ_done = 1; |
| 254 | else if (option == TELOPT_TTYPE) |
| 255 | telnet->ttype_done = 1; |
| 256 | |
| 257 | if (telnet->old_environ_done && telnet->new_environ_done && |
| 258 | telnet->ttype_done) { |
| 259 | telnet->shell_ok = 1; |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | static void refused_option(Telnet telnet, const struct Opt *o) |
| 264 | { |
| 265 | done_option(telnet, o->option); |
| 266 | if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON && |
| 267 | telnet->opt_states[o_oenv.index] == INACTIVE) { |
| 268 | send_opt(telnet, WILL, TELOPT_OLD_ENVIRON); |
| 269 | telnet->opt_states[o_oenv.index] = REQUESTED; |
| 270 | telnet->old_environ_done = 0; |
| 271 | } |
| 272 | option_side_effects(telnet, o, 0); |
| 273 | } |
| 274 | |
| 275 | static void proc_rec_opt(Telnet telnet, int cmd, int option) |
| 276 | { |
| 277 | const struct Opt *const *o; |
| 278 | |
| 279 | for (o = opts; *o; o++) { |
| 280 | if ((*o)->option == option && (*o)->ack == cmd) { |
| 281 | switch (telnet->opt_states[(*o)->index]) { |
| 282 | case REQUESTED: |
| 283 | telnet->opt_states[(*o)->index] = ACTIVE; |
| 284 | activate_option(telnet, *o); |
| 285 | break; |
| 286 | case ACTIVE: |
| 287 | break; |
| 288 | case INACTIVE: |
| 289 | telnet->opt_states[(*o)->index] = ACTIVE; |
| 290 | send_opt(telnet, (*o)->send, option); |
| 291 | activate_option(telnet, *o); |
| 292 | break; |
| 293 | case REALLY_INACTIVE: |
| 294 | send_opt(telnet, (*o)->nsend, option); |
| 295 | break; |
| 296 | } |
| 297 | return; |
| 298 | } else if ((*o)->option == option && (*o)->nak == cmd) { |
| 299 | switch (telnet->opt_states[(*o)->index]) { |
| 300 | case REQUESTED: |
| 301 | telnet->opt_states[(*o)->index] = INACTIVE; |
| 302 | refused_option(telnet, *o); |
| 303 | break; |
| 304 | case ACTIVE: |
| 305 | telnet->opt_states[(*o)->index] = INACTIVE; |
| 306 | send_opt(telnet, (*o)->nsend, option); |
| 307 | option_side_effects(telnet, *o, 0); |
| 308 | break; |
| 309 | case INACTIVE: |
| 310 | case REALLY_INACTIVE: |
| 311 | break; |
| 312 | } |
| 313 | return; |
| 314 | } |
| 315 | } |
| 316 | /* |
| 317 | * If we reach here, the option was one we weren't prepared to |
| 318 | * cope with. If the request was positive (WILL or DO), we send |
| 319 | * a negative ack to indicate refusal. If the request was |
| 320 | * negative (WONT / DONT), we must do nothing. |
| 321 | */ |
| 322 | if (cmd == WILL || cmd == DO) |
| 323 | send_opt(telnet, (cmd == WILL ? DONT : WONT), option); |
| 324 | } |
| 325 | |
| 326 | static void process_subneg(Telnet telnet) |
| 327 | { |
| 328 | unsigned char b[2048], *p, *q; |
| 329 | int var, value, n; |
| 330 | char *e; |
| 331 | |
| 332 | switch (telnet->sb_opt) { |
| 333 | case TELOPT_OLD_ENVIRON: |
| 334 | case TELOPT_NEW_ENVIRON: |
| 335 | if (telnet->sb_buf[0] == TELQUAL_IS) { |
| 336 | if (telnet->sb_opt == TELOPT_NEW_ENVIRON) { |
| 337 | var = RFC_VAR; |
| 338 | value = RFC_VALUE; |
| 339 | } else { |
| 340 | if (telnet->sb_len > 1 && !(telnet->sb_buf[0] &~ 1)) { |
| 341 | var = telnet->sb_buf[0]; |
| 342 | value = BSD_VAR ^ BSD_VALUE ^ var; |
| 343 | } else { |
| 344 | var = BSD_VAR; |
| 345 | value = BSD_VALUE; |
| 346 | } |
| 347 | } |
| 348 | } |
| 349 | n = 1; |
| 350 | while (n < telnet->sb_len && telnet->sb_buf[n] == var) { |
| 351 | int varpos, varlen, valpos, vallen; |
| 352 | char *result; |
| 353 | |
| 354 | varpos = ++n; |
| 355 | while (n < telnet->sb_len && telnet->sb_buf[n] != value) |
| 356 | n++; |
| 357 | if (n == telnet->sb_len) |
| 358 | break; |
| 359 | varlen = n - varpos; |
| 360 | valpos = ++n; |
| 361 | while (n < telnet->sb_len && telnet->sb_buf[n] != var) |
| 362 | n++; |
| 363 | vallen = n - valpos; |
| 364 | |
| 365 | result = snewn(varlen + vallen + 2, char); |
| 366 | sprintf(result, "%.*s=%.*s", |
| 367 | varlen, telnet->sb_buf+varpos, |
| 368 | vallen, telnet->sb_buf+valpos); |
| 369 | if (telnet->shdata.nenvvars >= telnet->envvarsize) { |
| 370 | telnet->envvarsize = telnet->shdata.nenvvars * 3 / 2 + 16; |
| 371 | telnet->shdata.envvars = sresize(telnet->shdata.envvars, |
| 372 | telnet->envvarsize, char *); |
| 373 | } |
| 374 | telnet->shdata.envvars[telnet->shdata.nenvvars++] = result; |
| 375 | } |
| 376 | done_option(telnet, telnet->sb_opt); |
| 377 | break; |
| 378 | case TELOPT_TTYPE: |
| 379 | if (telnet->sb_len >= 1 && telnet->sb_buf[0] == TELQUAL_IS) { |
| 380 | telnet->shdata.termtype = snewn(5 + telnet->sb_len, char); |
| 381 | strcpy(telnet->shdata.termtype, "TERM="); |
| 382 | for (n = 0; n < telnet->sb_len-1; n++) { |
| 383 | char c = telnet->sb_buf[n+1]; |
| 384 | if (c >= 'A' && c <= 'Z') |
| 385 | c = c + 'a' - 'A'; |
| 386 | telnet->shdata.termtype[n+5] = c; |
| 387 | } |
| 388 | telnet->shdata.termtype[telnet->sb_len+5-1] = '\0'; |
| 389 | } |
| 390 | done_option(telnet, telnet->sb_opt); |
| 391 | break; |
| 392 | case TELOPT_NAWS: |
| 393 | if (telnet->sb_len == 4) { |
| 394 | int w, h; |
| 395 | w = (unsigned char)telnet->sb_buf[0]; |
| 396 | w = (w << 8) | (unsigned char)telnet->sb_buf[1]; |
| 397 | h = (unsigned char)telnet->sb_buf[2]; |
| 398 | h = (h << 8) | (unsigned char)telnet->sb_buf[3]; |
| 399 | pty_resize(w, h); |
| 400 | } |
| 401 | break; |
| 402 | } |
| 403 | } |
| 404 | |
| 405 | void telnet_from_net(Telnet telnet, char *buf, int len) |
| 406 | { |
| 407 | while (len--) { |
| 408 | int c = (unsigned char) *buf++; |
| 409 | |
| 410 | switch (telnet->state) { |
| 411 | case TOP_LEVEL: |
| 412 | case SEENCR: |
| 413 | /* |
| 414 | * PuTTY sends Telnet's new line sequence (CR LF on |
| 415 | * the wire) in response to the return key. We must |
| 416 | * therefore treat that as equivalent to CR NUL, and |
| 417 | * send CR to the pty. |
| 418 | */ |
| 419 | if ((c == NUL || c == '\n') && telnet->state == SEENCR) |
| 420 | telnet->state = TOP_LEVEL; |
| 421 | else if (c == IAC) |
| 422 | telnet->state = SEENIAC; |
| 423 | else { |
| 424 | char cc = c; |
| 425 | sel_write(telnet->pty, &cc, 1); |
| 426 | |
| 427 | if (c == CR) |
| 428 | telnet->state = SEENCR; |
| 429 | else |
| 430 | telnet->state = TOP_LEVEL; |
| 431 | } |
| 432 | break; |
| 433 | case SEENIAC: |
| 434 | if (c == DO) |
| 435 | telnet->state = SEENDO; |
| 436 | else if (c == DONT) |
| 437 | telnet->state = SEENDONT; |
| 438 | else if (c == WILL) |
| 439 | telnet->state = SEENWILL; |
| 440 | else if (c == WONT) |
| 441 | telnet->state = SEENWONT; |
| 442 | else if (c == SB) |
| 443 | telnet->state = SEENSB; |
| 444 | else if (c == DM) |
| 445 | telnet->state = TOP_LEVEL; |
| 446 | else { |
| 447 | /* ignore everything else; print it if it's IAC */ |
| 448 | if (c == IAC) { |
| 449 | char cc = c; |
| 450 | sel_write(telnet->pty, &cc, 1); |
| 451 | } |
| 452 | telnet->state = TOP_LEVEL; |
| 453 | } |
| 454 | break; |
| 455 | case SEENWILL: |
| 456 | proc_rec_opt(telnet, WILL, c); |
| 457 | telnet->state = TOP_LEVEL; |
| 458 | break; |
| 459 | case SEENWONT: |
| 460 | proc_rec_opt(telnet, WONT, c); |
| 461 | telnet->state = TOP_LEVEL; |
| 462 | break; |
| 463 | case SEENDO: |
| 464 | proc_rec_opt(telnet, DO, c); |
| 465 | telnet->state = TOP_LEVEL; |
| 466 | break; |
| 467 | case SEENDONT: |
| 468 | proc_rec_opt(telnet, DONT, c); |
| 469 | telnet->state = TOP_LEVEL; |
| 470 | break; |
| 471 | case SEENSB: |
| 472 | telnet->sb_opt = c; |
| 473 | telnet->sb_len = 0; |
| 474 | telnet->state = SUBNEGOT; |
| 475 | break; |
| 476 | case SUBNEGOT: |
| 477 | if (c == IAC) |
| 478 | telnet->state = SUBNEG_IAC; |
| 479 | else { |
| 480 | subneg_addchar: |
| 481 | if (telnet->sb_len >= telnet->sb_size) { |
| 482 | telnet->sb_size += SB_DELTA; |
| 483 | telnet->sb_buf = sresize(telnet->sb_buf, telnet->sb_size, |
| 484 | unsigned char); |
| 485 | } |
| 486 | telnet->sb_buf[telnet->sb_len++] = c; |
| 487 | telnet->state = SUBNEGOT; /* in case we came here by goto */ |
| 488 | } |
| 489 | break; |
| 490 | case SUBNEG_IAC: |
| 491 | if (c != SE) |
| 492 | goto subneg_addchar; /* yes, it's a hack, I know, but... */ |
| 493 | else { |
| 494 | process_subneg(telnet); |
| 495 | telnet->state = TOP_LEVEL; |
| 496 | } |
| 497 | break; |
| 498 | } |
| 499 | } |
| 500 | } |
| 501 | |
| 502 | Telnet telnet_new(sel_wfd *net, sel_wfd *pty) |
| 503 | { |
| 504 | Telnet telnet; |
| 505 | |
| 506 | telnet = snew(struct telnet_tag); |
| 507 | telnet->sb_buf = NULL; |
| 508 | telnet->sb_size = 0; |
| 509 | telnet->state = TOP_LEVEL; |
| 510 | telnet->net = net; |
| 511 | telnet->pty = pty; |
| 512 | telnet->shdata.envvars = NULL; |
| 513 | telnet->shdata.nenvvars = telnet->envvarsize = 0; |
| 514 | telnet->shdata.termtype = NULL; |
| 515 | |
| 516 | /* |
| 517 | * Initialise option states. |
| 518 | */ |
| 519 | { |
| 520 | const struct Opt *const *o; |
| 521 | |
| 522 | for (o = opts; *o; o++) { |
| 523 | telnet->opt_states[(*o)->index] = (*o)->initial_state; |
| 524 | if (telnet->opt_states[(*o)->index] == REQUESTED) |
| 525 | send_opt(telnet, (*o)->send, (*o)->option); |
| 526 | } |
| 527 | } |
| 528 | |
| 529 | telnet->old_environ_done = 1; /* initially don't want to bother */ |
| 530 | telnet->new_environ_done = 0; |
| 531 | telnet->ttype_done = 0; |
| 532 | telnet->shell_ok = 0; |
| 533 | |
| 534 | return telnet; |
| 535 | } |
| 536 | |
| 537 | void telnet_free(Telnet telnet) |
| 538 | { |
| 539 | sfree(telnet->sb_buf); |
| 540 | sfree(telnet); |
| 541 | } |
| 542 | |
| 543 | void telnet_from_pty(Telnet telnet, char *buf, int len) |
| 544 | { |
| 545 | unsigned char *p, *end; |
| 546 | static const unsigned char iac[2] = { IAC, IAC }; |
| 547 | static const unsigned char cr[2] = { CR, NUL }; |
| 548 | #if 0 |
| 549 | static const unsigned char nl[2] = { CR, LF }; |
| 550 | #endif |
| 551 | |
| 552 | p = (unsigned char *)buf; |
| 553 | end = (unsigned char *)(buf + len); |
| 554 | while (p < end) { |
| 555 | unsigned char *q = p; |
| 556 | |
| 557 | while (p < end && iswritable(*p)) |
| 558 | p++; |
| 559 | sel_write(telnet->net, (char *)q, p - q); |
| 560 | |
| 561 | while (p < end && !iswritable(*p)) { |
| 562 | sel_write(telnet->net, (char *)(*p == IAC ? iac : cr), 2); |
| 563 | p++; |
| 564 | } |
| 565 | } |
| 566 | } |
| 567 | |
| 568 | int telnet_shell_ok(Telnet telnet, struct shell_data *shdata) |
| 569 | { |
| 570 | if (telnet->shell_ok) |
| 571 | *shdata = telnet->shdata; /* structure copy */ |
| 572 | return telnet->shell_ok; |
| 573 | } |