|
|
1.1 ! root 1: char *cknetv = "Network support, 5A(015) 23 Nov 92"; ! 2: ! 3: /* C K C N E T -- Network support */ ! 4: /* ! 5: Authors: ! 6: ! 7: Frank da Cruz ([email protected], [email protected]), ! 8: Columbia University Center for Computing Activities. ! 9: netopen() routine for TCP/IP originally by Ken Yap, Rochester University ! 10: ([email protected]) (no longer at that address). ! 11: Missing pieces for Excelan sockets library from William Bader, Moravian ! 12: College <[email protected]>. ! 13: TELNET protocol by Frank da Cruz. ! 14: TGV MultiNet code by Frank da Cruz. ! 15: MultiNet code adapted to WIN/TCP by Ray Hunter of TWG. ! 16: MultiNet code adapted to DEC TCP/IP by Lee Tibbert of DEC and Frank da Cruz. ! 17: SunLink X.25 support by Marcello Frutig, Catholic University, ! 18: Rio de Janeiro, Brazil ([email protected]) with fixes from ! 19: Stefaan Eeckels, Eurokom, Luxembourg. ! 20: Other contributions as indicated below. ! 21: ! 22: Copyright (C) 1985, 1992, Trustees of Columbia University in the City of New ! 23: York. Permission is granted to any individual or institution to use this ! 24: software as long as it is not sold for profit. This copyright notice must be ! 25: retained. This software may not be included in commercial products without ! 26: written permission of Columbia University. ! 27: */ ! 28: ! 29: #include "ckcdeb.h" ! 30: #include "ckcker.h" ! 31: #ifdef I386IX /* Has to come before ckcnet.h in */ ! 32: #include <errno.h> /* this version, but after in others */ ! 33: #endif /* I386IX */ ! 34: #include "ckcnet.h" ! 35: ! 36: #ifdef NETCONN ! 37: /* Don't need these if there is no network support. */ ! 38: ! 39: #ifdef WINTCP ! 40: ! 41: #include <errno.h> ! 42: #include <setjmp.h> ! 43: #include <signal.h> ! 44: /* ! 45: * The WIN/TCP code path is the same as that for Multinet. Only the routine ! 46: * names have changed ... ! 47: */ ! 48: #define socket_errno errno ! 49: #define socket_read netread ! 50: #define socket_ioctl ioctl ! 51: #define socket_write netwrite ! 52: #define socket_perror win$perror ! 53: #define socket_close netclose ! 54: ! 55: #else /* Not WINTCP */ ! 56: #ifndef I386IX ! 57: #include <errno.h> ! 58: #endif /* I386IX */ ! 59: #include <signal.h> ! 60: #ifndef ZILOG ! 61: #include <setjmp.h> ! 62: #else ! 63: #include <setret.h> ! 64: #endif /* ZILOG */ ! 65: #endif /* WINTCP */ ! 66: ! 67: #ifdef datageneral /* Data General AOS/VS */ ! 68: #include <:usr:include:vs_tcp_errno.h> ! 69: #include <:usr:include:sys:vs_tcp_types.h> ! 70: #include <:usr:include:sys:socket.h> ! 71: #include <:usr:include:netinet:in.h> ! 72: #include <:usr:include:netdb.h> ! 73: #endif /* datageneral */ ! 74: ! 75: extern SIGTYP (*saval)(); /* For saving alarm handler */ ! 76: ! 77: _PROTOTYP( VOID bgchk, (void) ); ! 78: ! 79: extern int /* External variables */ ! 80: duplex, debses, seslog, ttyfd, quiet, msgflg; ! 81: ! 82: #ifdef SVR4 ! 83: /* ! 84: These suggested by Rob Healey, [email protected], to avoid ! 85: bugs in Berkeley compatibility library on Sys V R4 systems, but untested ! 86: by me (fdc). Remove this bit if it gives you trouble. ! 87: (Later corrected by Marc Boucher <[email protected]> because ! 88: bzero/bcopy are not argument-compatible with memset/memcpy|memmove.) ! 89: */ ! 90: #define bzero(s,n) memset(s,0,n) ! 91: #define bcopy(h,a,l) memmove(a,h,l) ! 92: #else ! 93: #ifdef PTX /* Sequent DYNIX PTX 1.3 */ ! 94: #define bzero(s,n) memset(s,0,n) ! 95: #define bcopy(h,a,l) memcpy(a,h,l) ! 96: #endif /* PTX */ ! 97: #endif /* SVR4 */ ! 98: ! 99: #define NAMECPYL 100 /* Local copy of hostname */ ! 100: static char namecopy[NAMECPYL]; ! 101: ! 102: char ipaddr[20] = { '\0' }; /* Global copy of IP address */ ! 103: ! 104: /* ! 105: VMSTCPIP means "DEC_TCPIP or MULTINET or WINTCP" (defined in ckcnet.h). ! 106: */ ! 107: #ifdef VMSTCPIP ! 108: /* ! 109: General global variables, but so far used only by MultiNet and WIN/TCP. ! 110: Kept within #ifdef MULTINET..#endif to keep strict compilers (and lint) ! 111: from complaining about unused variables. ! 112: */ ! 113: static jmp_buf njbuf; /* For timeout longjumps */ ! 114: #endif /* VMSTCPIP */ ! 115: ! 116: #endif /* NETCONN */ ! 117: ! 118: int ttnet = NET_NONE; /* Network type */ ! 119: int ttnproto = NP_NONE; /* Network virtual terminal protocol */ ! 120: int tn_init = 0; /* Telnet protocol initialized flag */ ! 121: int tn_duplex = 1; /* Initial echo status */ ! 122: char *tn_term = NULL; /* Terminal type override */ ! 123: int tn_nlm = 1; /* Telnet CR -> CR LF mode */ ! 124: ! 125: #ifndef NETCONN ! 126: /* ! 127: Network support not defined. ! 128: Dummy functions here in case #ifdef's forgotten elsewhere. ! 129: */ ! 130: int /* Open network connection */ ! 131: netopen(name, lcl, nett) char *name; int *lcl, nett; { ! 132: return(-1); ! 133: } ! 134: int /* Close network connection */ ! 135: netclos() { ! 136: return(-1); ! 137: } ! 138: int /* Check network input buffer */ ! 139: nettchk() { ! 140: return(-1); ! 141: } ! 142: int /* Flush network input buffer */ ! 143: netflui() { ! 144: return(-1); ! 145: } ! 146: int /* Send network BREAK */ ! 147: netbreak() { ! 148: return(-1); ! 149: } ! 150: int /* Input character from network */ ! 151: netinc(timo) int timo; { ! 152: } ! 153: int /* Output character to network */ ! 154: #ifdef CK_ANSIC ! 155: nettoc(char c) ! 156: #else ! 157: nettoc(c) char c; ! 158: #endif /* CK_ANSIC */ ! 159: /* nettoc */ { ! 160: return(-1); ! 161: } ! 162: int ! 163: nettol(s,n) char *s; int n; { ! 164: return(-1); ! 165: } ! 166: ! 167: #else /* NETCONN is defined (rest of this module...) */ ! 168: ! 169: #ifdef VMSTCPIP ! 170: ! 171: /* For buffered network reads... */ ! 172: /* ! 173: If the buffering code is written right, it shouldn't matter how long this ! 174: buffer is -- it could even be shorter than a Kermit packet. ! 175: */ ! 176: #define TTIBUFL 8192 /* Maybe 8K?... */ ! 177: ! 178: CHAR ttibuf[TTIBUFL+1]; ! 179: int ttibp = 0, ttibn = 0; ! 180: /* ! 181: Read bytes from network into internal buffer ttibuf[]. ! 182: To be called when input buffer is empty, i.e. when ttibn == 0. ! 183: ! 184: Other network reading routines, like ttinc, ttinl, ttxin, should check the ! 185: internal buffer first, and call this routine for a refill if necessary. ! 186: ! 187: Returns -1 on error, 0 if nothing happens. When data is read successfully, ! 188: returns number of bytes read, and sets global ttibn to that number and ! 189: ttibp (the buffer pointer) to zero. ! 190: */ ! 191: int ! 192: ttbufr() { /* TT Buffer Read */ ! 193: int count; ! 194: ! 195: if (ttnet != NET_TCPB) { /* First make sure current net is */ ! 196: return(-1); /* TCP/IP; if not, do nothing. */ ! 197: } else { ! 198: if (ttibn > 0) /* Out internal buffer is not empty, */ ! 199: return(ttibn); /* so keep using it. */ ! 200: #ifdef WINTCP ! 201: count = 512; /* This works for WIN/TCP */ ! 202: #else /* Not WINTCP, i.e it's Multinet */ ! 203: #ifdef DEC_TCPIP ! 204: count = 512; /* This works for WIN/TCP */ ! 205: #else /* Not WINTCP, i.e it's Multinet */ ! 206: count = nettchk(); /* Check network input buffer, */ ! 207: if (ttibn > 0) return(ttibn); /* which can put a char there! */ ! 208: if (count < 0) /* Read error */ ! 209: return(-1); ! 210: else if (count > TTIBUFL) /* Too many to read */ ! 211: count = TTIBUFL; ! 212: else if (count == 0) /* None, so force blocking read */ ! 213: count = 1; ! 214: #endif /* DEC_TCPIP */ ! 215: #endif /* WINTCP */ ! 216: debug(F101,"ttbufr count 1","",count); ! 217: ! 218: #ifdef COMMENT ! 219: /* ! 220: This is for nonblocking reads, which we don't do any more. This code didn't ! 221: work anyway, in the sense that a broken connection was never sensed. ! 222: */ ! 223: if ((count = socket_read(ttyfd,ttibuf,count)) < 1) { ! 224: if (count == -1 && socket_errno == EWOULDBLOCK) { ! 225: debug(F100,"ttbufr finds nothing","",0); ! 226: return(0); ! 227: } else if (count == 0) { ! 228: debug(F100,"ttbufr socket eof","",0); ! 229: return(-1); ! 230: } else { ! 231: debug(F101,"ttbufr socket_read error","",socket_errno); ! 232: return(-1); ! 233: } ! 234: } ! 235: #else ! 236: /* This is for blocking reads */ ! 237: if ((count = socket_read(ttyfd,ttibuf,count)) < 1) { ! 238: debug(F101,"ttbufr socket_read","",count); ! 239: debug(F101,"ttbufr socket_errno","",socket_errno); ! 240: return(-1); ! 241: } ! 242: #endif /* COMMENT */ ! 243: ttibp = 0; /* Reset buffer pointer. */ ! 244: ttibn = count; ! 245: #ifdef DEBUG ! 246: debug(F101,"ttbufr count 2","",count); /* Got some bytes. */ ! 247: if (count > 0) ttibuf[count] = '\0'; ! 248: debug(F111,"ttbufr ttibuf",ttibuf,ttibp); ! 249: #endif /* DEBUG */ ! 250: return(ttibn); /* Return buffer count. */ ! 251: } ! 252: } ! 253: #endif /* VMSTCPIP */ ! 254: ! 255: /* ! 256: C-Kermit network open/close functions for BSD-sockets. ! 257: Much of this code shared by SunLink X.25, which also uses the socket library. ! 258: */ ! 259: ! 260: /* N E T O P E N -- Open a network connection. */ ! 261: ! 262: /* Returns 0 on success, -1 on failure. */ ! 263: ! 264: #define TELNET_PORT 23 /* Should do lookup, but it won't change */ ! 265: ! 266: /* This symbol is not known to, e.g., Ultrix 2.0 */ ! 267: #ifndef TELOPT_TTYPE ! 268: #define TELOPT_TTYPE 24 ! 269: #endif /* TELOPT_TTYPE */ ! 270: ! 271: /* N E T O P N -- Open a network connection. */ ! 272: /* ! 273: Call with: ! 274: name of host (or host:service), ! 275: lcl - local-mode flag to be set if this function succeeds, ! 276: network type - value defined in ckunet.h. ! 277: */ ! 278: ! 279: #ifdef EXCELAN ! 280: /* ! 281: Most other BSD sockets implementations define these in header files ! 282: and libraries. ! 283: */ ! 284: struct servent { ! 285: unsigned short s_port; ! 286: }; ! 287: ! 288: struct hostent { ! 289: short h_addrtype; ! 290: struct in_addr h_addr; ! 291: int h_length; ! 292: }; ! 293: ! 294: struct servent * ! 295: getservbyname(service, connection) char *service,*connection; { ! 296: static struct servent servrec; ! 297: int port; ! 298: ! 299: port = 0; ! 300: if (strcmp(service, "telnet") == 0) port = 23; ! 301: else if (strcmp(service, "smtp") == 0) port = 25; ! 302: else port = atoi(service); ! 303: ! 304: debug(F101,"getservbyname return port ","",port); ! 305: ! 306: if (port > 0) { ! 307: servrec.s_port = htons(port); ! 308: return( &servrec ); ! 309: } ! 310: return( (struct servent *) NULL ); ! 311: } ! 312: ! 313: struct hostent * ! 314: gethostbyname(hostname) char *hostname; { ! 315: return( (struct hostent *) NULL ); ! 316: } ! 317: ! 318: unsigned long ! 319: inet_addr(name) char *name; { ! 320: unsigned long addr; ! 321: ! 322: addr = rhost(&name); ! 323: debug(F111,"inet_addr ",name,(int)addr); ! 324: return(addr); ! 325: } ! 326: ! 327: char * ! 328: inet_ntoa(in) struct in_addr in; { ! 329: static char name[80]; ! 330: sprintf(name, "%d.%d.%d.%d", in.s_net, in.s_host, in.s_lh, in.s_impno); ! 331: return(name); ! 332: } ! 333: #endif /* EXCELAN */ ! 334: ! 335: /* N E T O P E N -- Open a network connection */ ! 336: /* ! 337: Calling conventions same as ttopen(), except third argument is network ! 338: type rather than modem type. Designed to be called from within ttopen. ! 339: */ ! 340: int ! 341: netopen(name, lcl, nett) char *name; int *lcl, nett; { ! 342: char *p; ! 343: #ifdef SO_OOBINLINE ! 344: int on = 1; ! 345: #endif /* SO_OOBINLINE */ ! 346: int i, x; ! 347: struct servent *service, servrec; ! 348: struct hostent *host; ! 349: struct sockaddr_in saddr; ! 350: #ifdef EXCELAN ! 351: struct sockaddr_in send_socket; ! 352: #endif /* EXCELAN */ ! 353: ! 354: #ifdef SUNX25 /* Code for SunLink X.25 support */ ! 355: #define X29PID 1 /* X.29 Protocol ID */ ! 356: VOID x25oobh(); ! 357: CONN_DB x25host; ! 358: FACILITY_DB x25facil; ! 359: static int needh = 1; ! 360: PID_T pid; ! 361: extern int linkid, lcn, x25ver; ! 362: extern int revcall, closgr, cudata; ! 363: extern char udata[MAXCUDATA]; ! 364: #endif /* SUNX25 */ ! 365: ! 366: debug(F101,"netopen nett","",nett); ! 367: *ipaddr = '\0'; /* Initialize IP address string */ ! 368: ! 369: #ifdef SUNX25 ! 370: if (nett == NET_SX25) { /* If network type is X.25 */ ! 371: netclos(); /* Close any previous net connection */ ! 372: ttnproto = NP_NONE; /* No protocol selected yet */ ! 373: ! 374: /* Set up host structure */ ! 375: bzero ((char *)&x25host,sizeof(x25host)); ! 376: if ((x25host.hostlen = pkx121 (name,x25host.host)) < 0) { ! 377: fprintf (stderr,"Invalid X.121 host address %s\n",name); ! 378: errno = 0; ! 379: return (-1); ! 380: } ! 381: x25host.datalen = X29PIDLEN; ! 382: x25host.data[0] = X29PID; ! 383: ! 384: /* Set call user data if specified */ ! 385: if (cudata) { ! 386: strncpy(x25host.data+X29PIDLEN,udata,(int)strlen(udata)); ! 387: x25host.datalen += (int)strlen(udata); ! 388: } ! 389: ! 390: /* Open SunLink X.25 socket */ ! 391: if ((ttyfd = socket (AF_X25, SOCK_STREAM, 0)) < 0) { ! 392: debug(F101,"netopen socket error","",errno); ! 393: perror ("X.25 connect socket error"); ! 394: return (-1); ! 395: } ! 396: ! 397: /* Setting X.25 out-of-band data handler */ ! 398: pid = getpid(); ! 399: if (ioctl(ttyfd,SIOCSPGRP,&pid)) { ! 400: perror("Setting process group id"); ! 401: return(-1); ! 402: } ! 403: (VOID) signal(SIGURG,x25oobh); ! 404: ! 405: /* Set reverse charge call and closed user group if requested */ ! 406: bzero ((char *)&x25facil,sizeof(x25facil)); ! 407: if (revcall) x25facil.reverse_charge = revcall; ! 408: if (closgr > -1) { ! 409: x25facil.cug_req = 1; ! 410: x25facil.cug_index = closgr; ! 411: } ! 412: if (ioctl(ttyfd,X25_WR_FACILITY,&x25facil) < 0) { ! 413: perror ("Setting X.25 facilities"); ! 414: return (-1); ! 415: } ! 416: ! 417: /* Need X.25 header with bits Q and M */ ! 418: if (ioctl (ttyfd,X25_HEADER,&needh) < 0) { ! 419: perror ("Setting X.25 header"); ! 420: return (-1); ! 421: } ! 422: ! 423: /* Connects to remote host via SunLink X.25 */ ! 424: if (connect(ttyfd,&x25host,sizeof(x25host)) < 0) { ! 425: debug(F101,"netopen connect errno","",errno); ! 426: i = errno; ! 427: if (errno) { ! 428: perror("netopen"); ! 429: x25diag(); ! 430: } ! 431: (VOID) close (ttyfd); ! 432: ttyfd = -1; ! 433: errno = i; ! 434: return (-1); ! 435: } ! 436: ! 437: /* Get X.25 link identification used for the connection */ ! 438: if (ioctl(ttyfd,X25_GET_LINK,&linkid) < 0) { ! 439: perror ("Getting X.25 link id"); ! 440: return (-1); ! 441: } ! 442: ! 443: /* Get X.25 logical channel number used for the connection */ ! 444: if (ioctl(ttyfd,X25_RD_LCGN,&lcn) < 0) { ! 445: perror ("Getting X.25 lcn"); ! 446: return (-1); ! 447: } ! 448: ! 449: /* Get SunLink X.25 version */ ! 450: if (ioctl(ttyfd,X25_VERSION,&x25ver) < 0) { ! 451: perror ("Getting SunLink X.25 version"); ! 452: return (-1); ! 453: } ! 454: ttnet = nett; /* Sunlink X.25 network */ ! 455: ttnproto = NP_X3; /* PAD X.3, X.28, X.29 protocol */ ! 456: if (*lcl < 0) *lcl = 1; /* Local mode */ ! 457: return(0); ! 458: } else /* Note that SUNX25 support can coexist with TCP/IP support. */ ! 459: #endif /* SUNX25 */ ! 460: /* ! 461: Add support for other networks here. ! 462: */ ! 463: if (nett != NET_TCPB) return(-1); /* BSD socket support */ ! 464: ! 465: netclos(); /* Close any previous connection. */ ! 466: strncpy(namecopy, name, NAMECPYL); /* Copy the hostname. */ ! 467: ttnproto = NP_NONE; /* No protocol selected yet. */ ! 468: debug(F110,"netopen namecopy",namecopy,0); ! 469: ! 470: p = namecopy; /* Was a service requested? */ ! 471: while (*p != '\0' && *p != ':') p++; /* Look for colon */ ! 472: if (*p == ':') { /* Have a colon */ ! 473: *p++ = '\0'; /* Get service name or number */ ! 474: } else { /* Otherwise use telnet */ ! 475: p = "telnet"; ! 476: } ! 477: debug(F110,"netopen service requested",p,0); ! 478: if (isdigit(*p)) { /* Use socket number without lookup */ ! 479: service = &servrec; ! 480: service->s_port = htons((unsigned short)atoi(p)); ! 481: } else { /* Otherwise lookup the service name */ ! 482: service = getservbyname(p, "tcp"); ! 483: } ! 484: if (!service) { ! 485: fprintf(stderr, "Cannot find port for service %s\n", p); ! 486: #ifdef MULTINET ! 487: debug(F101,"netopen can't get service","",socket_errno); ! 488: #else ! 489: debug(F101,"netopen can't get service","",errno); ! 490: #endif /* MULTINET */ ! 491: errno = 0; /* rather than mislead */ ! 492: return(-1); ! 493: } ! 494: /* Set up socket structure and get host address */ ! 495: ! 496: bzero((char *)&saddr, sizeof(saddr)); ! 497: if ((host = gethostbyname(namecopy)) != NULL) { ! 498: saddr.sin_family = host->h_addrtype; ! 499: bcopy(host->h_addr, (caddr_t)&saddr.sin_addr, host->h_length); ! 500: } else { ! 501: #ifdef INADDRX ! 502: /* inet_addr() is of type struct in_addr */ ! 503: struct in_addr ina; ! 504: unsigned long uu; ! 505: #ifdef datageneral ! 506: extern struct in_addr inet_addr(); ! 507: #endif /* datageneral */ ! 508: ina = inet_addr(namecopy); ! 509: uu = *(unsigned long *)&ina; ! 510: #else /* Not INADDRX */ ! 511: /* inet_addr() is unsigned long */ ! 512: unsigned long uu; ! 513: uu = inet_addr(namecopy); ! 514: #endif /* INADDRX */ ! 515: if ((saddr.sin_addr.s_addr = uu) != ((unsigned long)-1)) ! 516: saddr.sin_family = AF_INET; ! 517: else { ! 518: fprintf(stderr, "Can't get address for %s\n", namecopy); ! 519: #ifdef MULTINET ! 520: debug(F101,"netopen can't get address","",socket_errno); ! 521: #else ! 522: debug(F101,"netopen can't get address","",errno); ! 523: #endif /* MULTINET */ ! 524: errno = 0; /* rather than mislead */ ! 525: return(-1); ! 526: } ! 527: } ! 528: ! 529: /* Get a file descriptor for the connection. */ ! 530: ! 531: saddr.sin_port = service->s_port; ! 532: sprintf(ipaddr,"%s", inet_ntoa(saddr.sin_addr)); ! 533: if (!quiet && *ipaddr) printf(" Trying %s...\n", ipaddr); ! 534: ! 535: #ifdef EXCELAN ! 536: send_socket.sin_family = AF_INET; ! 537: send_socket.sin_addr.s_addr = 0; ! 538: send_socket.sin_port = 0; ! 539: if ((ttyfd = socket(SOCK_STREAM, (struct sockproto *)0, ! 540: &send_socket, SO_REUSEADDR)) < 0) ! 541: #else ! 542: if ((ttyfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) ! 543: #endif /* EXCELAN */ ! 544: { ! 545: #ifdef EXCELAN ! 546: experror("TCP socket error"); ! 547: #else ! 548: #ifdef MULTINET ! 549: socket_perror("TCP socket error"); ! 550: debug(F101,"netopen socket error","",socket_errno); ! 551: #else ! 552: perror("TCP socket error"); ! 553: debug(F101,"netopen socket error","",errno); ! 554: #endif /* MULTINET */ ! 555: #endif /* EXCELAN */ ! 556: return (-1); ! 557: } ! 558: errno = 0; ! 559: ! 560: /* Now connect to the socket on the other end. */ ! 561: ! 562: #ifdef EXCELAN ! 563: if (connect(ttyfd, &saddr) < 0) ! 564: #else ! 565: if (connect(ttyfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) ! 566: #endif /* EXCELAN */ ! 567: { ! 568: i = errno; /* save error code */ ! 569: close(ttyfd); ! 570: ttyfd = -1; ! 571: errno = i; /* and report this error */ ! 572: #ifdef EXCELAN ! 573: if (errno) experror("netopen connect"); ! 574: #else ! 575: #ifdef MULTINET ! 576: debug(F101,"netopen connect error","",socket_errno); ! 577: if (errno) socket_perror("netopen connect"); ! 578: #else ! 579: debug(F101,"netopen connect errno","",errno); ! 580: #ifdef WINTCP ! 581: perror("netopen connect"); ! 582: #endif /* WINTCP */ ! 583: #ifdef DEC_TCPIP ! 584: perror("netopen connect"); ! 585: #endif /* DEC_TCPIP */ ! 586: #endif /* MULTINET */ ! 587: #endif /* EXCELAN */ ! 588: return(-1); ! 589: } ! 590: #ifdef SO_OOBINLINE ! 591: /* ! 592: The symbol SO_OOBINLINE is not known to Ultrix 2.0. ! 593: It means "leave out of band data inline". The normal value is 0x0100, ! 594: but don't try this on systems where the symbol is undefined. ! 595: */ ! 596: #ifdef datageneral ! 597: setsockopt(ttyfd, SOL_SOCKET, SO_OOBINLINE,(char *) &on, sizeof on); ! 598: #else ! 599: #ifdef BSD43 ! 600: setsockopt(ttyfd, SOL_SOCKET, SO_OOBINLINE,(char *) &on, sizeof on); ! 601: #else ! 602: #ifdef OSF1 ! 603: setsockopt(ttyfd, SOL_SOCKET, SO_OOBINLINE,(char *) &on, sizeof on); ! 604: #else ! 605: #ifdef POSIX ! 606: setsockopt(ttyfd, SOL_SOCKET, SO_OOBINLINE,(char *) &on, sizeof on); ! 607: #else ! 608: #ifdef SOLARIS ! 609: /* ! 610: Maybe this applies to all SVR4 versions, but the other (else) way has been ! 611: compiling and working fine on all the others, so best not to change it. ! 612: */ ! 613: setsockopt(ttyfd, SOL_SOCKET, SO_OOBINLINE,(char *) &on, sizeof on); ! 614: #else ! 615: setsockopt(ttyfd, SOL_SOCKET, SO_OOBINLINE, &on, sizeof on); ! 616: #endif /* SOLARIS */ ! 617: #endif /* POSIX */ ! 618: #endif /* BSD43 */ ! 619: #endif /* OSF1 */ ! 620: #endif /* datageneral */ ! 621: #endif /* SO_OOBINLINE */ ! 622: ! 623: /* See if the service is TELNET. */ ! 624: if ((x = ntohs((unsigned short)service->s_port)) == TELNET_PORT) ! 625: ttnproto = NP_TELNET; /* Yes, set global flag. */ ! 626: debug(F101,"netopen service","",x); ! 627: ttnet = nett; /* TCP/IP (sockets) network */ ! 628: tn_init = 0; /* Telnet protocol not init'd yet */ ! 629: if (*lcl < 0) *lcl = 1; /* Local mode. */ ! 630: return(0); /* Done. */ ! 631: } ! 632: ! 633: /* N E T C L O S -- Close current network connection. */ ! 634: ! 635: int ! 636: netclos() { ! 637: int x = 0; ! 638: if (ttyfd < 0) /* Was open? */ ! 639: return(0); /* Wasn't. */ ! 640: if (ttyfd > -1) /* Was. */ ! 641: #ifdef VMSTCPIP ! 642: x = socket_close(ttyfd); /* Close it. */ ! 643: #else ! 644: x = close(ttyfd); ! 645: #endif /* VMSTCPIP */ ! 646: ttyfd = -1; /* Mark it as closed. */ ! 647: tn_init = 0; /* Remember about telnet protocol... */ ! 648: *ipaddr = '\0'; /* Zero the IP address string */ ! 649: return(x); ! 650: } ! 651: ! 652: /* N E T T C H K -- Check if network up, and how many bytes can be read */ ! 653: /* ! 654: Returns number of bytes waiting, or -1 if connection has been dropped. ! 655: */ ! 656: int /* Check how many bytes are ready */ ! 657: nettchk() { /* for reading from network */ ! 658: #ifdef VMSTCPIP ! 659: unsigned int count; ! 660: int x, y; ! 661: char c; ! 662: ! 663: debug(F101,"nettchk entry ttibn","",ttibn); ! 664: debug(F101,"nettchk entry ttibp","",ttibp); ! 665: socket_errno = 0; ! 666: /* ! 667: Note: this socket_ioctl() call does NOT return an error if the ! 668: connection has been broken. (At least not in Multinet.) ! 669: */ ! 670: if (socket_ioctl(ttyfd,FIONREAD,&count) < 0) { ! 671: debug(F101,"nettchk socket_ioctl error","",socket_errno); ! 672: if (ttibn < 1) return(-1); ! 673: else return(ttibn); ! 674: } ! 675: debug(F101,"nettchk count","",count); ! 676: ! 677: #ifndef DEC_TCPIP ! 678: /* ! 679: Let's see if we can skip this for UCX, since it seems to cause trouble. ! 680: */ ! 681: if (count == 0) { ! 682: /* ! 683: Here we need to tell the difference between a 0 count on an active ! 684: connection, and a 0 count because the remote end of the socket broke the ! 685: connection. There is no mechanism in TGV MultiNet (or WIN/TCP?) to query ! 686: the status of the connection, so we have to do a read. -1 means there was ! 687: no data available (socket_errno == EWOULDBLOCK), 0 means the connection is ! 688: down. But if, by chance, we actually get a character, we have to put it ! 689: where it won't be lost. ! 690: */ ! 691: y = 1; /* Turn on nonblocking reads */ ! 692: debug(F101,"nettchk before FIONBIO","",x); ! 693: x = socket_ioctl(ttyfd,FIONBIO,&y); ! 694: debug(F101,"nettchk FIONBIO","",x); ! 695: x = socket_read(ttyfd,&c,1); /* Returns -1 if no data */ ! 696: debug(F101,"nettchk socket_read","",x); ! 697: y = 0; /* Turn them back off */ ! 698: socket_ioctl(ttyfd,FIONBIO,&y); ! 699: if (x == 0) return(-1); /* Connection is broken. */ ! 700: if (x == 1) { /* Oops, actually got a byte? */ ! 701: debug(F101,"nettchk socket_read char","",c); ! 702: debug(F101,"nettchk ttibp","",ttibp); ! 703: debug(F101,"nettchk ttibn","",ttibn); ! 704: /* ! 705: So put the byte we got into the buffer at the current position. ! 706: Increment the buffer count, but DON'T increment the buffer pointer. ! 707: */ ! 708: ttibuf[ttibp+ttibn] = c; ! 709: ttibn++; ! 710: #ifdef DEBUG ! 711: ttibuf[ttibp+ttibn] = '\0'; ! 712: debug(F111,"nettchk ttibn",ttibuf,ttibn); ! 713: #endif /* DEBUG */ ! 714: } ! 715: } ! 716: #endif /* DEC_TCPIP */ ! 717: debug(F101,"nettchk returns","",count+ttibn); ! 718: return(count + ttibn); ! 719: ! 720: #else /* Not VMSTCPIP */ ! 721: /* ! 722: UNIX just uses ttchk(), in which the ioctl() calls on the file descriptor ! 723: seem to work OK. ! 724: */ ! 725: return(0); ! 726: #endif /* VMSTCPIP */ ! 727: /* ! 728: But what about X.25? ! 729: */ ! 730: } ! 731: ! 732: /* N E T I N C -- Input character from network */ ! 733: ! 734: int ! 735: netinc(timo) int timo; { ! 736: #ifdef VMSTCPIP ! 737: int x; unsigned char c; /* The locals. */ ! 738: ! 739: if (ttibn > 0) { /* Something in internal buffer? */ ! 740: debug(F100,"netinc char in buf","",0); /* Yes. */ ! 741: x = 0; /* Success. */ ! 742: } else { /* Else must read from network. */ ! 743: x = -1; /* Assume failure. */ ! 744: #ifdef DEBUG ! 745: debug(F101,"netinc goes to net, timo","",timo); ! 746: ttibuf[ttibp+1] = '\0'; ! 747: debug(F111,"netinc ttibuf",ttibuf,ttibp); ! 748: #endif /* DEBUG */ ! 749: if (timo <= 0) { /* Untimed case. */ ! 750: while (1) { /* Wait forever if necessary. */ ! 751: if (ttbufr() < 0) /* Refill buffer. */ ! 752: break; /* Error, fail. */ ! 753: if (ttibn > 0) { /* Success. */ ! 754: x = 0; ! 755: break; ! 756: } ! 757: } ! 758: } else { /* Timed case... */ ! 759: saval = signal(SIGALRM,ttimoff); /* Enable timer interrupt */ ! 760: alarm(timo); /* for requested interval. */ ! 761: if (setjmp(njbuf)) { /* Timer went off? */ ! 762: x = -1; /* Yes, fail. */ ! 763: } else { ! 764: while (1) { ! 765: if (ttbufr() < 0) /* Keep trying to refill it. */ ! 766: break; /* Till we get an error. */ ! 767: if (ttibn > 0) { /* Or we get a character. */ ! 768: x = 0; ! 769: break; ! 770: } ! 771: } ! 772: } ! 773: ttimoff(); /* Timer off. */ ! 774: } ! 775: } ! 776: if (x < 0) { /* Return -1 if we failed. */ ! 777: debug(F100,"netinc timed out","",0); ! 778: return(-1); ! 779: } else { /* Otherwise */ ! 780: ttibn--; /* Return what we got. */ ! 781: c = ttibuf[ttibp++]; ! 782: debug(F101,"netinc returning","",c); ! 783: return((c & 0xff)); ! 784: } ! 785: #else /* Not MULTINET or WINTCP */ ! 786: return(-1); ! 787: #endif /* VMSTCPIP */ ! 788: } ! 789: ! 790: /* N E T T O L -- Output a string of bytes to the network */ ! 791: /* ! 792: Call with s = pointer to string, n = length. ! 793: Returns number of bytes actually written on success, or ! 794: -1 on i/o error, -2 if called improperly. ! 795: */ ! 796: int ! 797: nettol(s,n) char *s; int n; { ! 798: #ifdef VMSTCPIP ! 799: int count; ! 800: if (ttnet == NET_TCPB) { ! 801: if ((count = socket_write(ttyfd,s,n)) < 1) { ! 802: debug(F101,"nettol socket_write error","",socket_errno); ! 803: return(-1); ! 804: } ! 805: debug(F111,"nettol socket_write",s,count); ! 806: return(count); ! 807: } else return(-2); ! 808: #else ! 809: debug(F100,"nettol VMSTCPIP not defined","",0); ! 810: return(-2); ! 811: #endif /* VMSTCPIP */ ! 812: } ! 813: ! 814: /* N E T T O C -- Output character to network */ ! 815: /* ! 816: Call with character to be transmitted. ! 817: Returns 0 if transmission was successful, or ! 818: -1 upon i/o error, or -2 if called improperly. ! 819: */ ! 820: int ! 821: #ifdef CK_ANSIC ! 822: nettoc(char c) ! 823: #else ! 824: nettoc(c) char c; ! 825: #endif /* CK_ANSIC */ ! 826: /* nettoc */ { ! 827: #ifdef VMSTCPIP ! 828: unsigned char cc; ! 829: cc = c; ! 830: if (ttnet == NET_TCPB) { ! 831: debug(F101,"nettoc cc","",cc); ! 832: if (socket_write(ttyfd,&cc,1) < 1) { ! 833: debug(F101,"nettoc socket_write error","",socket_errno); ! 834: return(-1); ! 835: } ! 836: debug(F101,"nettoc socket_write","", cc); ! 837: return(0); ! 838: } else return(-2); ! 839: #else ! 840: return(-2); ! 841: #endif /* MULTINET */ ! 842: } ! 843: ! 844: /* N E T F L U I -- Flush network input buffer */ ! 845: ! 846: int ! 847: netflui() { ! 848: int n; ! 849: #ifdef VMSTCPIP ! 850: ttibuf[ttibp+1] = '\0'; ! 851: debug(F111,"netflui 1",ttibuf,ttibn); ! 852: ttibn = ttibp = 0; /* Flush internal buffer *FIRST* */ ! 853: if ((n = nettchk()) > 0) { /* Now see what's waiting on the net */ ! 854: if (n > TTIBUFL) n = TTIBUFL; /* and sponge it up */ ! 855: debug(F101,"netflui 2","",n); /* ... */ ! 856: n = socket_read(ttyfd,ttibuf,n) ; /* into our buffer */ ! 857: if (n >= 0) ttibuf[n] = '\0'; ! 858: debug(F111,"netflui 3",ttibuf,n); ! 859: ttibuf[0] = '\0'; ! 860: } ! 861: #else ! 862: /* ! 863: It seems the UNIX ioctl()s don't do the trick, so we have to read the ! 864: stuff ourselves. This should be pretty much portable, if not elegant. ! 865: */ ! 866: if ((n = ttchk()) > 0) { ! 867: debug(F101,"netflui","",n); ! 868: while ((n--) && ttinc(0) > -1) ; /* Don't worry, it's buffered. */ ! 869: } ! 870: #endif /* VMSTCPIP */ ! 871: return(0); ! 872: } ! 873: ! 874: #ifdef TNCODE /* Compile in telnet support code */ ! 875: ! 876: /* TCP/IP Telnet negotiation support code */ ! 877: ! 878: static int sgaflg = 0; /* telnet SGA flag */ ! 879: static int wttflg = 0; /* telnet terminal type flag */ ! 880: ! 881: #ifndef TELCMDS ! 882: char *telcmds[] = { ! 883: "SE", "NOP", "DMARK", "BRK", "IP", "AO", "AYT", "EC", ! 884: "EL", "GA", "SB", "WILL", "WONT", "DO", "DONT", "IAC", ! 885: }; ! 886: int ntelcmds = sizeof(telcmds) / sizeof(char *); ! 887: #endif /* TELCMDS */ ! 888: ! 889: #ifndef TELOPTS ! 890: char *telopts[] = { ! 891: "BINARY", "ECHO", "RCP", "SUPPRESS GO AHEAD", "NAME", ! 892: "STATUS", "TIMING MARK", "RCTE", "NAOL", "NAOP", ! 893: "NAOCRD", "NAOHTS", "NAOHTD", "NAOFFD", "NAOVTS", ! 894: "NAOVTD", "NAOLFD", "EXTEND ASCII", "LOGOUT", "BYTE MACRO", ! 895: "DATA ENTRY TERMINAL", "SUPDUP", "SUPDUP OUTPUT", ! 896: "SEND LOCATION", "TERMINAL TYPE", "END OF RECORD" ! 897: #ifdef TELOPT_TUID ! 898: ,"TACACS UID" ! 899: #ifdef TELOPT_OUTMRK ! 900: ,"OUTPUT MARKING" ! 901: #ifdef TELOPT_TTYLOC ! 902: ,"TTYLOC" ! 903: #ifdef TELOPT_3270REGIME ! 904: ,"3270 REGIME" ! 905: #ifdef TELOPT_X3PAD ! 906: ,"X.3 PAD" ! 907: #ifdef TELOPT_NAWS ! 908: ,"NAWS" ! 909: #ifdef TELOPT_TSPEED ! 910: ,"TSPEED" ! 911: #ifdef TELOPT_LFLOW ! 912: ,"LFLOW" ! 913: #ifdef TELOPT_LINEMODE ! 914: ,"LINEMODE" ! 915: #endif ! 916: #endif ! 917: #endif ! 918: #endif ! 919: #endif ! 920: #endif ! 921: #endif ! 922: #endif ! 923: #endif ! 924: }; ! 925: #endif /* TELOPTS */ ! 926: ! 927: int ntelopts = sizeof(telopts) / sizeof(char *); ! 928: #endif /* TNCODE */ ! 929: ! 930: ! 931: /* Send network BREAK */ ! 932: /* ! 933: Returns -1 on error, 0 if nothing happens, 1 if BREAK sent successfully. ! 934: */ ! 935: int ! 936: netbreak() { ! 937: CHAR buf[3]; ! 938: if (ttnet == NET_TCPB) { ! 939: if (ttnproto == NP_TELNET) { ! 940: #ifdef TNCODE ! 941: buf[0] = (CHAR) IAC; buf[1] = (CHAR) BREAK; ! 942: if (ttol(buf,2) < 2) return(-1); ! 943: debug(F101,"telnet BREAK ok","",BREAK); ! 944: return(1); ! 945: #else ! 946: debug(F100,"netbreak no TNCODE","",0); ! 947: return(0); ! 948: #endif /* TNCODE */ ! 949: } ! 950: /* Insert other TCP/IP protocols here */ ! 951: } ! 952: /* Insert other networks here */ ! 953: return(0); ! 954: } ! 955: ! 956: /* Send a telnet option, avoid loops. */ ! 957: /* Returns 1 if command was sent, 0 if not, -1 on error */ ! 958: ! 959: int ! 960: tn_sopt(cmd,opt) int cmd, opt; { /* TELNET SEND OPTION */ ! 961: CHAR buf[4]; ! 962: int n; ! 963: if (ttnet != NET_TCPB) return(0); /* Must be TCP/IP */ ! 964: if (ttnproto != NP_TELNET) return(0); /* Must be telnet protocol */ ! 965: n = cmd - SE; ! 966: if (n < 0 || n > ntelcmds) return(0); ! 967: #ifdef TNCODE ! 968: buf[0] = (CHAR) IAC; ! 969: buf[1] = (CHAR) cmd & 0xff; ! 970: buf[2] = (CHAR) opt & 0xff; ! 971: if (ttol(buf,3) < 3) ! 972: return(-1); ! 973: debug(F111,"telnet cmd >",telcmds[n],cmd); ! 974: debug(F111,"telnet opt >", ! 975: (opt < ntelopts) ? telopts[opt] : "UNKNOWN", opt ); ! 976: if (debses && cmd != SB) ! 977: printf("[%s %s]",telcmds[n], ! 978: (opt < ntelopts) ? telopts[opt] : "UNKNOWN"); ! 979: return(1); ! 980: #else ! 981: debug(F100,"tn_sopt no TNCODE","",0); ! 982: return(0); ! 983: #endif /* TNCODE */ ! 984: } ! 985: ! 986: /* Initialize a telnet connection. */ ! 987: /* Returns -1 on error, 0 if nothing happens, 1 if init msgs sent ok */ ! 988: ! 989: int ! 990: tn_ini() { ! 991: #ifndef TNCODE ! 992: debug(F100,"tn_ini no TNCODE","",0); ! 993: return(0); ! 994: #else /* TELNET protocol support */ ! 995: debug(F101,"tn_ini ttnproto","",ttnproto); ! 996: debug(F101,"tn_ini tn_init","",tn_init); ! 997: ! 998: if (ttnet != NET_TCPB) /* Make sure connection is TCP/IP. */ ! 999: return(0); ! 1000: if (tn_init) /* Have we done this already? */ ! 1001: return(0); /* Don't do it again. */ ! 1002: debug(F101,"tn_ini tn_duplex","",tn_duplex); ! 1003: duplex = tn_duplex; /* Assume local echo. */ ! 1004: sgaflg = 0; /* Assume Go-Ahead suppressed. */ ! 1005: wttflg = 0; /* Did not send WILL TERM TYPE yet. */ ! 1006: if (ttnproto == NP_NONE) { /* If not talking to a telnet port, */ ! 1007: ttnproto = NP_TELNET; /* pretend it's telnet anyway, */ ! 1008: tn_init = 1; /* but don't send initial options. */ ! 1009: debug(F100,"tn_ini skipping telnet negotiations","",0); ! 1010: return(0); ! 1011: } ! 1012: /* Talking to telnet port, so send WILL TERMINAL TYPE and DO SGA */ ! 1013: ! 1014: if (tn_sopt(WILL,TELOPT_TTYPE) < 0) /* Will send terminal type. */ ! 1015: return(-1); ! 1016: wttflg = 1; /* Remember I said I would. */ ! 1017: if (tn_sopt(DO,TELOPT_SGA) < 0) /* Please suppress go-ahead. */ ! 1018: return(-1); ! 1019: #ifdef COMMENT ! 1020: if (tn_sopt(DO,TELOPT_ECHO) < 0) /* Ask the server to echo, since */ ! 1021: return(-1); /* I'm assuming it will. */ ! 1022: #endif /* COMMENT */ ! 1023: tn_init = 1; /* Set telnet-initialized flag. */ ! 1024: ! 1025: /* Don't send anthing else! */ ! 1026: ! 1027: debug(F101,"tn_ini duplex","",duplex); ! 1028: return(1); ! 1029: #endif /* TNCODE */ ! 1030: } ! 1031: ! 1032: /* ! 1033: Process in-band Telnet negotiation characters from the remote host. ! 1034: Call with the telnet IAC character and the current duplex setting ! 1035: (0 = remote echo, 1 = local echo). ! 1036: Returns: ! 1037: 3 if server has sent us a quoted IAC ! 1038: 2 if local echo must be changed to remote ! 1039: 1 if remote echo must be changed to local ! 1040: 0 if nothing happens or no action necessary ! 1041: -1 on failure (= internal or i/o error) ! 1042: */ ! 1043: ! 1044: #define TSBUFSIZ 41 ! 1045: char sb[TSBUFSIZ]; /* Buffer for subnegotiations */ ! 1046: ! 1047: int ! 1048: #ifdef CK_ANSIC /* TELNET DO OPTION */ ! 1049: tn_doop( CHAR z, int echo, int (*fn)(int) ) ! 1050: #else ! 1051: tn_doop(z, echo, fn) CHAR z; int echo; int (*fn)(); ! 1052: #endif /* CK_ANSIC */ ! 1053: /* tn_doop */ { ! 1054: int c, x, y, n, m, flag; ! 1055: ! 1056: #ifndef TNCODE ! 1057: debug(F100,"tn_doop no TNCODE","",0); ! 1058: return(0); ! 1059: #else ! 1060: if (z != (CHAR) IAC) { ! 1061: debug(F101,"tn_doop bad call","",z); ! 1062: return(-1); ! 1063: } ! 1064: if (ttnet != NET_TCPB) return(0); ! 1065: if (ttnproto != NP_TELNET) return(0); /* Check protocol */ ! 1066: ! 1067: /* Have IAC, read command character. */ ! 1068: ! 1069: c = (*fn)(0) & 0xff; /* Read command character */ ! 1070: m = c - SE; /* Check validity */ ! 1071: if (m < 0 || m > ntelcmds) { ! 1072: debug(F101,"tn_doop bad cmd","",c); ! 1073: return(0); ! 1074: } ! 1075: if (seslog) { /* Copy to session log, if any. */ ! 1076: if (zchout(ZSFILE, (char) z) < 0) seslog = 0; /* Log IAC. */ ! 1077: else if (zchout(ZSFILE, (char) c) < 0) seslog = 0; /* Log command */ ! 1078: } ! 1079: debug(F111,"telnet cmd <",telcmds[m],c); /* Debug log. */ ! 1080: ! 1081: if (c == (CHAR) IAC) return(3); /* Quoted IAC */ ! 1082: if (c < SB) return(0); /* Other command with no arguments. */ ! 1083: ! 1084: /* SB, WILL, WONT, DO, or DONT need more bytes... */ ! 1085: ! 1086: if ((x = (*fn)(0)) < 0) return(-1); /* Get the option. */ ! 1087: x &= 0xff; /* Trim to 8 bits. */ ! 1088: ! 1089: debug(F111,"telnet opt <", ! 1090: (x < ntelopts) ? telopts[x] : "UNKNOWN", x ); ! 1091: if (seslog) /* Session log */ ! 1092: if (zchout(ZSFILE, (char) x) < 0) seslog = 0; ! 1093: ! 1094: /* Now handle the command */ ! 1095: ! 1096: if (debses && c != SB) /* Debug to screen. */ ! 1097: printf("<%s %s>",telcmds[m], ! 1098: (x < ntelopts) ? telopts[x] : "UNKNOWN" ); ! 1099: switch (x) { ! 1100: case TELOPT_ECHO: /* ECHO negotiation. */ ! 1101: switch (c) { /* Command */ ! 1102: case WILL: /* Host says it will echo. */ ! 1103: if (echo) /* Only reply if change required. */ ! 1104: return((tn_sopt(DO,x) < 0) ? -1 : 2); /* Please do. */ ! 1105: else return(0); /* Otherwise no change. */ ! 1106: case WONT: /* Host says it won't echo. */ ! 1107: if (!echo) /* If I'm full duplex */ ! 1108: return ((tn_sopt(DONT,x) < 0) ? -1 : 1); /* Switch to half */ ! 1109: else return(0); /* Otherwise, no change. */ ! 1110: case DO: /* Host wants me to echo */ ! 1111: if (tn_sopt(WONT,x) < 0) /* but the client never echoes */ ! 1112: return(-1); /* back to the server. */ ! 1113: default: /* Don't reply to anything else */ ! 1114: return(0); ! 1115: } ! 1116: ! 1117: case TELOPT_SGA: /* Suppress Go-Ahead */ ! 1118: switch (c) { /* Command... */ ! 1119: case WONT: /* Host says it won't. */ ! 1120: if (!sgaflg) { ! 1121: sgaflg = 1; /* Remember. */ ! 1122: if (tn_sopt(DONT,x) < 0) /* acknowledge, */ ! 1123: return(-1); ! 1124: } ! 1125: return(echo ? 0 : 1); /* Switch to half duplex */ ! 1126: case WILL: /* Server says it will SGA */ ! 1127: if (sgaflg) { /* ACK only if necessary */ ! 1128: if (tn_sopt(DO,x) < 0) ! 1129: return(-1); ! 1130: sgaflg = 0; /* Remember new SGA state. */ ! 1131: } ! 1132: return(0); /* But don't change echo state. */ ! 1133: } ! 1134: ! 1135: #ifdef TELOPT_TTYPE ! 1136: case TELOPT_TTYPE: /* Terminal Type */ ! 1137: switch (c) { ! 1138: case DO: /* DO terminal type. */ ! 1139: if (wttflg == 0) { /* If I haven't said so before, */ ! 1140: if (tn_sopt((CHAR)WILL,x) < 0) /* say I'll send it if asked. */ ! 1141: return(-1); ! 1142: wttflg++; ! 1143: } ! 1144: return(0); ! 1145: case SB: ! 1146: debug(F100,"TELNET subnegotiation:","",0); ! 1147: n = flag = 0; /* Flag for when done reading SB */ ! 1148: while (n < TSBUFSIZ) { /* Loop looking for IAC SE */ ! 1149: if ((y = (*fn)(0)) < 0) /* Read a byte */ ! 1150: return(-1); ! 1151: y &= 0xff; /* Make sure it's just 8 bits. */ ! 1152: sb[n++] = y; /* Deposit in buffer. */ ! 1153: if (seslog) /* Take care of session log */ ! 1154: if (zchout(ZSFILE, (char) y) < 0) ! 1155: seslog = 0; ! 1156: if (y == IAC) { /* If this is an IAC */ ! 1157: if (flag) { /* If previous char was IAC */ ! 1158: n--; /* it's quoted, keep one IAC */ ! 1159: flag = 0; /* and turn off the flag. */ ! 1160: } else flag = 1; /* Otherwise set the flag. */ ! 1161: } else if (flag) { /* Something else following IAC */ ! 1162: if (y != SE) /* If not SE, it's a protocol error */ ! 1163: flag = 0; ! 1164: break; ! 1165: } ! 1166: } ! 1167: if (!flag) { /* Make sure we got a valid SB */ ! 1168: debug(F100, "TELNET Subnegotian prematurely broken", "",0); ! 1169: return(-1); ! 1170: } ! 1171: if (debses) { /* Debug to screen. */ ! 1172: int i; ! 1173: printf("<SB %s ",telopts[TELOPT_TTYPE]); ! 1174: for (i = 0; i < n-2; i++) printf("%02x",sb[i]); ! 1175: printf(" IAC SE>"); ! 1176: } ! 1177: debug(F101,"TELNET suboption<","",sb[0]); ! 1178: if (sb[0] == 1) { /* SEND terminal type? */ ! 1179: if (tn_sttyp() < 0) /* Yes, so send it. */ ! 1180: return(-1); ! 1181: } ! 1182: default: /* Others, ignore */ ! 1183: return(0); ! 1184: } ! 1185: #endif /* TELOPT_TTYPE */ ! 1186: ! 1187: default: /* All others: refuse */ ! 1188: switch(c) { ! 1189: case WILL: /* You will? */ ! 1190: if (tn_sopt(DONT,x) < 0) /* Please don't. */ ! 1191: return(-1); /* (Could this cause a loop?) */ ! 1192: break; ! 1193: case DO: /* You want me to? */ ! 1194: if (tn_sopt(WONT,x) < 0) /* I won't. */ ! 1195: return(-1); ! 1196: break; ! 1197: case DONT: /* You don't want me to? */ ! 1198: if (tn_sopt(WONT,x) < 0) /* I won't. */ ! 1199: return(-1); /* (Could this cause a loop?) */ ! 1200: case WONT: /* You won't? */ ! 1201: break; /* I didn't want you to. */ ! 1202: } /* Anything else, treat as user data */ ! 1203: return(0); ! 1204: } ! 1205: #endif /* TNCODE */ ! 1206: } ! 1207: ! 1208: /* Telnet send terminal type */ ! 1209: /* Returns -1 on error, 0 if nothing happens, 1 if type sent successfully */ ! 1210: ! 1211: int ! 1212: tn_sttyp() { /* Send telnet terminal type. */ ! 1213: #ifndef TNCODE ! 1214: debug(F100,"tn_sttyp no TNCODE","",0); ! 1215: return(0); ! 1216: #else ! 1217: char *ttn; int ttl, i; /* Name & length of terminal type. */ ! 1218: ! 1219: if (ttnet != NET_TCPB) return(0); ! 1220: if (ttnproto != NP_TELNET) return(0); ! 1221: ! 1222: ttn = NULL; ! 1223: ! 1224: if (tn_term) { /* Terminal type override? */ ! 1225: debug(F110,"tn_sttyp",tn_term,0); ! 1226: if (*tn_term) ttn = tn_term; ! 1227: } else debug(F100,"tn_sttyp no term override","",0); ! 1228: #ifndef datageneral ! 1229: if (!ttn) /* If no override, */ ! 1230: ttn = getenv("TERM"); /* get it from the environment. */ ! 1231: #endif /* datageneral */ ! 1232: if ((ttn == ((char *)0)) || ((ttl = (int)strlen(ttn)) >= TSBUFSIZ)) { ! 1233: ttn = "UNKNOWN"; ! 1234: ttl = 7; ! 1235: } ! 1236: sb[0] = IAC; /* I Am a Command */ ! 1237: sb[1] = SB; /* Subnegotiation */ ! 1238: sb[2] = TELOPT_TTYPE; /* Terminal Type */ ! 1239: sb[3] = (CHAR) 0; /* Is... */ ! 1240: for (i = 4; *ttn; ttn++,i++) /* Copy and uppercase it */ ! 1241: sb[i] = (islower(*ttn)) ? toupper(*ttn) : *ttn; ! 1242: ttn = sb; /* Point back to beginning */ ! 1243: sb[i++] = IAC; /* End of Subnegotiation */ ! 1244: sb[i++] = SE; /* marked by IAC SE */ ! 1245: if (ttol((CHAR *)sb,i) < 0) /* Send it. */ ! 1246: return(-1); ! 1247: #ifdef DEBUG ! 1248: sb[i-2] = '\0'; /* For debugging */ ! 1249: debug(F111,"telnet SB sent ttype",sb+4,ttl); ! 1250: #endif /* DEBUG */ ! 1251: if (debses) /* Debug to screen. */ ! 1252: printf("[SB TERMINAL TYPE 00 %s IAC SE]",sb+4); ! 1253: return(1); ! 1254: #endif /* TNCODE */ ! 1255: } ! 1256: ! 1257: #ifdef SUNX25 ! 1258: /* ! 1259: SunLink X.25 support by Marcello Frutig, Catholic University, ! 1260: Rio de Janeiro, Brazil, 1990. ! 1261: */ ! 1262: ! 1263: /* PAD X.3, X.28 and X.29 support */ ! 1264: ! 1265: static CHAR x29err [MAXPADPARMS+3] = { X29_ERROR, INVALID_PAD_PARM, '\0' }; ! 1266: ! 1267: ! 1268: /* Initialize PAD */ ! 1269: ! 1270: extern CHAR padparms[MAXPADPARMS+1]; ! 1271: ! 1272: VOID ! 1273: initpad() { ! 1274: padparms[PAD_BREAK_CHARACTER] = 0; /* Break character */ ! 1275: padparms[PAD_ESCAPE] = 1; /* Escape permitted */ ! 1276: padparms[PAD_ECHO] = 1; /* Kermit PAD does echo */ ! 1277: padparms[PAD_DATA_FORWARD_CHAR] = 2; /* forward character CR */ ! 1278: padparms[PAD_DATA_FORWARD_TIMEOUT] = 0; /* no timeout forward condition */ ! 1279: padparms[PAD_FLOW_CONTROL_BY_PAD] = 0; /* not used */ ! 1280: padparms[PAD_SUPPRESSION_OF_SIGNALS] = 1; /* allow PAD service signals */ ! 1281: padparms[PAD_BREAK_ACTION] = 21; /* brk action: INT pk + brk ind*/ ! 1282: padparms[PAD_SUPPRESSION_OF_DATA] = 0; /* no supression of user data */ ! 1283: padparms[PAD_PADDING_AFTER_CR] = 0; /* no padding after CR */ ! 1284: padparms[PAD_LINE_FOLDING] = 0; /* no line fold */ ! 1285: padparms[PAD_LINE_SPEED] = 0; /* line speed - don't care */ ! 1286: padparms[PAD_FLOW_CONTROL_BY_USER] = 0; /* flow cont of PAD - not used */ ! 1287: padparms[PAD_LF_AFTER_CR] = 0; /* no LF insertion after CR */ ! 1288: padparms[PAD_PADDING_AFTER_LF] = 0; /* no padding after LF */ ! 1289: padparms[PAD_EDITING] = 1; /* can edit */ ! 1290: padparms[PAD_CHAR_DELETE_CHAR] = 8; /* character delete character */ ! 1291: padparms[PAD_BUFFER_DELETE_CHAR] = 21; /* buffer delete character */ ! 1292: padparms[PAD_BUFFER_DISPLAY_CHAR] = 18; /* buffer display character */ ! 1293: } ! 1294: ! 1295: ! 1296: /* Set PAD parameters */ ! 1297: ! 1298: VOID ! 1299: setpad(s,n) CHAR *s; int n; { ! 1300: int i; ! 1301: CHAR *ps = s; ! 1302: ! 1303: for (i = 0; i < n; i++) { ! 1304: if (*ps > MAXPADPARMS) ! 1305: x29err[i+2] = *ps; ! 1306: else ! 1307: padparms[*ps] = *(ps+1); ! 1308: ps += 2; ! 1309: } ! 1310: } ! 1311: ! 1312: /* Read PAD parameters */ ! 1313: ! 1314: VOID ! 1315: readpad(s,n,r) CHAR *s; int n; CHAR *r; { ! 1316: int i; ! 1317: CHAR *ps = s; ! 1318: CHAR *pr = r; ! 1319: ! 1320: *pr++ = X29_PARAMETER_INDICATION; ! 1321: for (i = 0; i < n; i++, ps++) { ! 1322: if (*ps > MAXPADPARMS) { ! 1323: x29err[i+2] = *ps++; ! 1324: } else { ! 1325: *pr++ = *ps; ! 1326: *pr++ = padparms[*ps++]; ! 1327: } ! 1328: } ! 1329: } ! 1330: ! 1331: int ! 1332: qbitpkt(s,n) CHAR *s; int n; { ! 1333: CHAR *ps = s; ! 1334: int x29cmd = *ps; ! 1335: CHAR *psa = s+1; ! 1336: CHAR x29resp[(MAXPADPARMS*2)+1]; ! 1337: ! 1338: switch (x29cmd) { ! 1339: ! 1340: case X29_SET_PARMS: ! 1341: setpad (ps+1,n/2); ! 1342: if ((int)strlen(x29err) > 2) { ! 1343: ttol (x29err,(int)strlen(x29err)); ! 1344: x29err[2] = '\0'; ! 1345: } ! 1346: return (-2); ! 1347: case X29_READ_PARMS: ! 1348: readpad (ps+1,n/2,x29resp); ! 1349: setqbit (); ! 1350: ttol (x29resp,n+1); ! 1351: if ((int)strlen(x29err) > 2) { ! 1352: ttol (x29err,(int)strlen(x29err)); ! 1353: x29err[2] = '\0'; ! 1354: } ! 1355: resetqbit(); ! 1356: break; ! 1357: case X29_SET_AND_READ_PARMS: ! 1358: setpad (ps+1,n/2); ! 1359: readpad (ps+1,n/2,x29resp); ! 1360: setqbit(); ! 1361: ttol (x29resp,n+1); ! 1362: if ((int)strlen(x29err) > 2) { ! 1363: ttol (x29err,(int)strlen(x29err)); ! 1364: x29err [2] = '\0'; ! 1365: } ! 1366: resetqbit(); ! 1367: return (-2); ! 1368: case X29_INVITATION_TO_CLEAR: ! 1369: (VOID) x25clear(); ! 1370: return (-1) ; ! 1371: case X29_INDICATION_OF_BREAK: ! 1372: break; ! 1373: } ! 1374: return (0); ! 1375: } ! 1376: ! 1377: /* PAD break action processor */ ! 1378: ! 1379: VOID ! 1380: breakact() { ! 1381: extern char x25obuf[MAXOX25]; ! 1382: extern int obufl; ! 1383: extern int active; ! 1384: extern unsigned char tosend; ! 1385: static CHAR indbrk[3] = { ! 1386: X29_INDICATION_OF_BREAK, ! 1387: PAD_SUPPRESSION_OF_DATA, ! 1388: 1 ! 1389: }; ! 1390: CHAR intudat, cause, diag; ! 1391: ! 1392: if (x25stat() < 0) return(0); /* Ignore if no virtual call established */ ! 1393: ! 1394: if (padparms[PAD_BREAK_ACTION] != 0) /* Forward condition */ ! 1395: if (ttol(x25obuf,obufl) < 0) { ! 1396: perror ("\r\nCan't send characters"); ! 1397: active = 0; ! 1398: } else { ! 1399: bzero (x25obuf,sizeof(x25obuf)); ! 1400: obufl = 0; ! 1401: tosend = 0; ! 1402: }; ! 1403: ! 1404: switch (padparms[PAD_BREAK_ACTION]) { ! 1405: ! 1406: case 0 : break; /* do nothing */ ! 1407: case 1 : /* send interrupt packet with interrupt user data field = 1 */ ! 1408: intudat = 1; ! 1409: x25intr (intudat); ! 1410: break; ! 1411: case 2 : /* send reset packet with cause and diag = 0 */ ! 1412: cause = diag = 0; ! 1413: x25reset (cause,diag); ! 1414: break; ! 1415: case 5 : /* send interrupt packet with interrupt user data field = 0 */ ! 1416: intudat = 0; ! 1417: x25intr (intudat) ; ! 1418: setqbit (); ! 1419: /* send indication of break without a parameter field */ ! 1420: ttoc(X29_INDICATION_OF_BREAK); ! 1421: resetqbit (); ! 1422: break; ! 1423: case 8 : active = 0; /* leave data transfer */ ! 1424: conol ("\r\n"); ! 1425: break; ! 1426: case 21: /* send interrupt packet with interrupt user data field = 0 */ ! 1427: intudat = 0; ! 1428: x25intr (intudat); ! 1429: setpad (indbrk+1,2); /* set pad to discard input */ ! 1430: setqbit (); ! 1431: /* send indication of break with parameter field */ ! 1432: ttol (indbrk,sizeof(indbrk)); ! 1433: resetqbit (); ! 1434: break; ! 1435: } ! 1436: } ! 1437: ! 1438: /* X.25 support functions */ ! 1439: ! 1440: X25_CAUSE_DIAG diag; ! 1441: ! 1442: /* ! 1443: Convert a null-terminated string representing an X.121 address ! 1444: to a packed BCD form. ! 1445: */ ! 1446: ! 1447: int ! 1448: pkx121(str,bcd) char *str; CHAR *bcd; { ! 1449: int i, j; ! 1450: u_char c; ! 1451: ! 1452: i = j = 0; ! 1453: while (str[i]) { ! 1454: if ( i >= 15 || str [i] < '0' || str [i] > '9' ) ! 1455: return (-1); ! 1456: c = str [i] - '0'; ! 1457: if ( i & 1 ) ! 1458: bcd [j++] |= c; ! 1459: else ! 1460: bcd [j] = c << 4; ! 1461: i++; ! 1462: } ! 1463: return (i); ! 1464: } ! 1465: ! 1466: /* Reads and prints X.25 diagnostic */ ! 1467: ! 1468: int ! 1469: x25diag () { ! 1470: int i; ! 1471: ! 1472: bzero ((char *)&diag,sizeof(diag)); ! 1473: if (ioctl(ttyfd,X25_RD_CAUSE_DIAG,&diag)) { ! 1474: perror ("Reading X.25 diagnostic"); ! 1475: return(-1); ! 1476: } ! 1477: if (diag.datalen > 0) { ! 1478: printf ("X.25 Diagnostic :"); ! 1479: for (i = 0; i < diag.datalen; i++) printf (" %02x",diag.data[i]); ! 1480: printf ("\r\n"); ! 1481: } ! 1482: return(0); ! 1483: } ! 1484: ! 1485: /* X.25 Out-of-Band Signal Handler */ ! 1486: ! 1487: VOID ! 1488: x25oobh() { ! 1489: int oobtype; ! 1490: u_char oobdata; ! 1491: ! 1492: (VOID) signal(SIGURG,x25oobh); ! 1493: do { ! 1494: if (ioctl(ttyfd,X25_OOB_TYPE,&oobtype)) { ! 1495: perror ("Getting signal type"); ! 1496: return; ! 1497: } ! 1498: switch (oobtype) { ! 1499: case INT_DATA: ! 1500: if (recv(ttyfd,oobdata,1,MSG_OOB) < 0) { ! 1501: perror ("Receiving X.25 interrupt data"); ! 1502: return; ! 1503: } ! 1504: printf ("\r\nInterrupt received, data = %d\r\n", oobdata); ! 1505: break; ! 1506: case VC_RESET: ! 1507: printf ("\r\nVirtual circuit reset\r\n"); ! 1508: x25diag (); ! 1509: break; ! 1510: case N_RESETS: ! 1511: printf ("\r\nReset timeout\r\n"); ! 1512: break; ! 1513: case N_CLEARS: ! 1514: printf ("\r\nClear timeout\r\n"); ! 1515: break; ! 1516: case MSG_TOO_LONG: ! 1517: printf ("\r\nMessage discarded, too long\r\n"); ! 1518: break; ! 1519: default: ! 1520: if (oobtype) printf("\r\nUnknown oob type %d\r\n",oobtype); ! 1521: break; ! 1522: } ! 1523: } while (oobtype); ! 1524: } ! 1525: ! 1526: /* Send a X.25 interrupt packet */ ! 1527: ! 1528: int ! 1529: #ifdef CK_ANSIC ! 1530: x25intr(char intr) ! 1531: #else ! 1532: x25intr(intr) char intr; ! 1533: #endif /* CK_ANSIC */ ! 1534: /* x25intr */ { ! 1535: if (send(ttyfd,&intr,1,MSG_OOB) < 0) return(-1); ! 1536: debug(F100,"X.25 intr","",0); ! 1537: return(0); ! 1538: } ! 1539: ! 1540: /* Reset X.25 virtual circuit */ ! 1541: int ! 1542: #ifdef CK_ANSIC ! 1543: x25reset(char cause, char diagn) ! 1544: #else ! 1545: x25reset(cause, diagn) char cause; char diagn; ! 1546: #endif /* CK_ANSIC */ ! 1547: /* x25reset */ { ! 1548: bzero ((char *)&diag,sizeof(diag)); ! 1549: diag.flags = 0; ! 1550: diag.datalen = 2; ! 1551: diag.data[0] = cause; ! 1552: diag.data[1] = diagn; ! 1553: if (ioctl(ttyfd,X25_WR_CAUSE_DIAG,&diag) < 0) ! 1554: return(-1); ! 1555: debug(F100,"X.25 reset","",0); ! 1556: return(0); ! 1557: } ! 1558: ! 1559: /* Clear X.25 virtual circuit */ ! 1560: int ! 1561: x25clear() { ! 1562: int i; ! 1563: debug(F100,"X.25 clear","",0); ! 1564: bzero ((char *)&diag,sizeof(diag)); ! 1565: diag.flags = (1 << DIAG_TYPE); ! 1566: diag.datalen = 2; ! 1567: diag.data[0] = 0; ! 1568: diag.data[1] = 0; ! 1569: ioctl (ttyfd,X25_WR_CAUSE_DIAG,&diag); /* Send Clear Request */ ! 1570: return(ttclos(0)); /* Close socket */ ! 1571: } ! 1572: ! 1573: /* X.25 status */ ! 1574: int ! 1575: x25stat() { ! 1576: if (ttyfd < 0) return (-1); ! 1577: return(0); ! 1578: } ! 1579: ! 1580: /* Set Q_BIT on */ ! 1581: VOID ! 1582: setqbit() { ! 1583: static int qbiton = 1 << Q_BIT; ! 1584: ioctl (ttyfd,X25_SEND_TYPE,&qbiton); ! 1585: } ! 1586: ! 1587: /* Set Q_BIT off */ ! 1588: VOID ! 1589: resetqbit() { ! 1590: static int qbitoff = 0; ! 1591: ioctl (ttyfd,X25_SEND_TYPE,&qbitoff); ! 1592: } ! 1593: ! 1594: /* Read n characters from X.25 circuit into buf */ ! 1595: ! 1596: int ! 1597: x25xin(n,buf) int n; CHAR *buf; { ! 1598: register int x, c; ! 1599: int qpkt; ! 1600: ! 1601: do { ! 1602: x = read(ttyfd,buf,n); ! 1603: if (buf[0] & (1 << Q_BIT)) { /* If Q_BIT packet, process it */ ! 1604: /* If return -1 : invitation to clear; -2 : PAD changes */ ! 1605: if ((c=qbitpkt(buf+1,x-2)) < 0) return(c); ! 1606: qpkt = 1; ! 1607: } else qpkt = 0; ! 1608: } while (qpkt); ! 1609: if (x > 0) buf[x] = '\0'; ! 1610: if (x < 1) x = -1; ! 1611: debug(F101,"x25xin x","",x); ! 1612: ! 1613: return(x); ! 1614: } ! 1615: ! 1616: #ifdef COMMENT /* NO LONGER NEEDED! */ ! 1617: /* X.25 read a line */ ! 1618: ! 1619: int ! 1620: #ifdef PARSENSE ! 1621: #ifdef CK_ANSIC ! 1622: x25inl(CHAR *dest, int max,int timo, CHAR eol, CHAR start) ! 1623: #else ! 1624: x25inl(dest,max,timo,eol,start) int max,timo; CHAR *dest, eol, start; ! 1625: #endif /* CK_ANSIC */ ! 1626: #else /* not PARSENSE */ ! 1627: #ifdef CK_ANSIC ! 1628: x25inl(CHAR *dest, int max,int timo, CHAR eol) ! 1629: #else ! 1630: x25inl(dest,max,timo,eol) int max,timo; CHAR *dest, eol; ! 1631: #endif /* __SDTC__ */ ! 1632: #endif /* PARSENSE */ ! 1633: /* x25inl */ { ! 1634: CHAR *pdest; ! 1635: int pktype, goteol, rest, n; ! 1636: int i, flag = 0; ! 1637: extern int ttprty, ttpflg; ! 1638: int ttpmsk; ! 1639: ! 1640: ttpmsk = (ttprty) ? 0177 : 0377; /* Set parity stripping mask */ ! 1641: ! 1642: debug(F101,"x25inl max","",max); ! 1643: debug(F101,"x25inl eol","",eol); ! 1644: pdest = dest; ! 1645: rest = max; ! 1646: goteol = 0; ! 1647: do { ! 1648: n = read(ttyfd,pdest,rest); ! 1649: n--; ! 1650: pktype = *pdest & 0x7f; ! 1651: switch (pktype) { ! 1652: case 1 << Q_BIT: ! 1653: if (qbitpkt(pdest+1,--n) < 0) return(-2); ! 1654: break; ! 1655: default: ! 1656: if (flag == 0) { /* if not in packet, search start */ ! 1657: for (i = 1; (i < n) && ! 1658: !(flag = ((dest[i] & 0x7f) == start)); ! 1659: i++); ! 1660: if (flag == 0) { /* not found, discard junk */ ! 1661: debug(F101,"x25inl skipping","",n); ! 1662: continue; ! 1663: } else { /* found, discard junk before start */ ! 1664: int k; ! 1665: n = n - i + 1; ! 1666: for (k = 1; k <= n; k++, i++) dest[k] = dest[i]; ! 1667: } ! 1668: } ! 1669: for (i = 0; (i < n) && /* search for eol */ ! 1670: !(goteol=(((*pdest = *(pdest+1)&ttpmsk)&0x7f)== eol)); ! 1671: i++,pdest++); ! 1672: *pdest = '\0'; ! 1673: rest -= n; ! 1674: } ! 1675: } while ( (rest > 0) && (!goteol) ); ! 1676: ! 1677: if (goteol) { ! 1678: n = max - rest; ! 1679: debug (F111,"x25inl X.25 got",(char *) dest,n); ! 1680: if (timo) ttimoff(); ! 1681: if (ttpflg++ == 0 && ttprty == 0) { ! 1682: if ((ttprty = parchk(dest,start,n)) > 0) { ! 1683: int j; ! 1684: debug(F101,"x25inl senses parity","",ttprty); ! 1685: debug(F110,"x25inl packet before",(char *)dest,0); ! 1686: ttpmsk = 0x7f; ! 1687: for (j = 0; j < n; j++) ! 1688: dest[j] &= 0x7f; /* Strip parity from packet */ ! 1689: debug(F110,"x25inl packet after ",dest,0); ! 1690: } else { ! 1691: debug(F101,"parchk","",ttprty); ! 1692: if (ttprty < 0) { ttprty = 0; n = -1; } ! 1693: } ! 1694: } ! 1695: ttimoff(); ! 1696: return(n); ! 1697: } ! 1698: ttimoff(); ! 1699: return(-1); ! 1700: } ! 1701: #endif /* COMMENT */ ! 1702: #endif /* SUNX25 */ ! 1703: ! 1704: #endif /* NETCONN */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.