source: trunk/third/x3270/telnet.c @ 9081

Revision 9081, 32.6 KB checked in by ghudson, 28 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r9080, which included commits to RCS files with non-trunk default branches.
Line 
1/*
2 * Modifications Copyright 1993, 1994, 1995 by Paul Mattes.
3 * Original X11 Port Copyright 1990 by Jeff Sparkes.
4 *  Permission to use, copy, modify, and distribute this software and its
5 *  documentation for any purpose and without fee is hereby granted,
6 *  provided that the above copyright notice appear in all copies and that
7 *  both that copyright notice and this permission notice appear in
8 *  supporting documentation.
9 *
10 * Copyright 1989 by Georgia Tech Research Corporation, Atlanta, GA 30332.
11 *  All Rights Reserved.  GTRC hereby grants public use of this software.
12 *  Derivative works based on this software must incorporate this copyright
13 *  notice.
14 */
15
16/*
17 *      telnet.c
18 *              This module initializes and manages a telnet socket to
19 *              the given IBM host.
20 */
21
22#include "globals.h"
23#include <sys/socket.h>
24#include <sys/ioctl.h>
25#include <netinet/in.h>
26#define TELCMDS 1
27#define TELOPTS 1
28#if !defined(LOCAL_TELNET_H) /*[*/
29#include <arpa/telnet.h>
30#else /*][*/
31#include "telnet.h"
32#endif /*]*/
33#include <errno.h>
34#include <fcntl.h>
35#include <netdb.h>
36
37#include "appres.h"
38
39#include "ansic.h"
40#include "ctlrc.h"
41#include "kybdc.h"
42#include "macrosc.h"
43#include "mainc.h"
44#include "menubarc.h"
45#include "popupsc.h"
46#include "statusc.h"
47#include "telnetc.h"
48#include "trace_dsc.h"
49#include "utilc.h"
50
51#if !defined(TELOPT_NAWS) /*[*/
52#define TELOPT_NAWS     31
53#endif /*]*/
54
55#if !defined(TELOPT_TN3270E) /*[*/
56#define TELOPT_TN3270E  40
57#endif /*]*/
58
59#define BUFSZ           4096
60#define TRACELINE       72
61
62#define N_OPTS          256
63
64/* Globals */
65time_t          ns_time;
66int             ns_brcvd;
67int             ns_rrcvd;
68int             ns_bsent;
69int             ns_rsent;
70unsigned char  *obuf;           /* 3270 output buffer */
71int             obuf_size = 0;
72unsigned char  *obptr = (unsigned char *) NULL;
73int             linemode = 1;
74char           *termtype;
75void            trace_netdata();
76
77/* Externals */
78extern unsigned long inet_addr();
79extern FILE    *tracef;
80extern struct timeval ds_ts;
81
82/* Statics */
83static int      sock = -1;      /* active socket */
84static char    *hostname = CN;
85static unsigned char myopts[N_OPTS], hisopts[N_OPTS];   /* telnet option flags */
86static unsigned char *ibuf = (unsigned char *) NULL;    /* 3270 input buffer */
87static unsigned char *ibptr;
88static int      ibuf_size = 0;  /* size of ibuf */
89static unsigned char netrbuf[BUFSZ];    /* network input buffer */
90static unsigned char sbbuf[1024], *sbptr;       /* telnet sub-option buffer */
91static unsigned char lbuf[BUFSZ], *lbptr;       /* line-mode input buffer */
92static unsigned char telnet_state;
93static char     trace_msg[256];
94static int      ansi_data = 0;
95static int      syncing;
96static int      lnext = 0;
97static int      backslashed = 0;
98static int      t_valid = 0;
99static char     ttype_tmpval[13];
100
101static char     vintr;
102static char     vquit;
103static char     verase;
104static char     vkill;
105static char     veof;
106static char     vwerase;
107static char     vrprnt;
108static char     vlnext;
109
110static char    *nnn();          /* expand a number */
111static int      telnet_fsm();
112static void     net_rawout();
113static void     do_data();
114static void     do_intr();
115static void     do_quit();
116static void     do_cerase();
117static void     do_werase();
118static void     do_kill();
119static void     do_rprnt();
120static void     do_eof();
121static void     do_eol();
122static void     do_lnext();
123static void     check_in3270();
124static void     store3270in();
125static void     check_linemode();
126static char     parse_ctlchar();
127static void     net_interrupt();
128static int      non_blocking();
129static void     net_connected();
130static void     trace_str();
131static char    *cmd();
132static char    *opt();
133
134/* telnet states */
135#define TNS_DATA        0       /* receiving data */
136#define TNS_IAC         1       /* got an IAC */
137#define TNS_WILL        2       /* got an IAC WILL */
138#define TNS_WONT        3       /* got an IAC WONT */
139#define TNS_DO          4       /* got an IAC DO */
140#define TNS_DONT        5       /* got an IAC DONT */
141#define TNS_SB          6       /* got an IAC SB */
142#define TNS_SB_IAC      7       /* got an IAC after an IAC SB */
143
144/* telnet predefined messages */
145static unsigned char    do_opt[]        = {
146        IAC, DO, '_' };
147static unsigned char    dont_opt[]      = {
148        IAC, DONT, '_' };
149static unsigned char    will_opt[]      = {
150        IAC, WILL, '_' };
151static unsigned char    wont_opt[]      = {
152        IAC, WONT, '_' };
153
154char *telquals[2] = { "IS", "SEND" };
155
156
157/*
158 * net_connect
159 *      Establish a telnet socket to the given host passed as an argument.
160 *      Called only once and is responsible for setting up the telnet
161 *      variables.  Returns the file descriptor of the connected socket.
162 */
163int
164net_connect(host, portname, pending)
165char    *host;
166char    *portname;
167Boolean *pending;
168{
169        struct servent  *sp;
170        struct hostent  *hp;
171        unsigned short          port;
172        char                    passthru_haddr[8];
173        int                     passthru_len;
174        unsigned short          passthru_port;
175        struct sockaddr_in      sin;
176        int                     on = 1;
177#if defined(OMTU) /*[*/
178        int                     mtu = OMTU;
179#endif /*]*/
180
181#       define close_fail       { (void) close(sock); sock = -1; return -1; }
182
183        if (!t_valid) {
184                vintr   = parse_ctlchar(appres.intr);
185                vquit   = parse_ctlchar(appres.quit);
186                verase  = parse_ctlchar(appres.erase);
187                vkill   = parse_ctlchar(appres.kill);
188                veof    = parse_ctlchar(appres.eof);
189                vwerase = parse_ctlchar(appres.werase);
190                vrprnt  = parse_ctlchar(appres.rprnt);
191                vlnext  = parse_ctlchar(appres.lnext);
192                t_valid = 1;
193        }
194
195        *pending = False;
196
197        if (hostname)
198                XtFree(hostname);
199        hostname = XtNewString(host);
200
201        /* get the passthru host and port number */
202        if (passthru_host) {
203                char *hn;
204
205                hn = getenv("INTERNET_HOST");
206                if (hn == CN)
207                        hn = "internet-gateway";
208
209                hp = gethostbyname(hn);
210                if (hp == (struct hostent *) 0) {
211                        popup_an_error("Unknown passthru host: %s", hn);
212                        return -1;
213                }
214                (void) MEMORY_MOVE(passthru_haddr,
215                                   (char *) hp->h_addr,
216                                   hp->h_length);
217                passthru_len = hp->h_length;
218
219                sp = getservbyname("telnet-passthru","tcp");
220                if (sp != (struct servent *)NULL)
221                        passthru_port = sp->s_port;
222                else
223                        passthru_port = htons(3514);
224        }
225
226        /* get the port number */
227        port = (unsigned short) atoi(portname);
228        if (port)
229                port = htons(port);
230        else {
231                if (!(sp = getservbyname(portname, "tcp"))) {
232                        popup_an_error("Unknown port number or service: %s",
233                            portname);
234                        return -1;
235                }
236                port = sp->s_port;
237        }
238        current_port = ntohs(port);
239
240
241        /* fill in the socket address of the given host */
242        (void) memset((char *) &sin, 0, sizeof(sin));
243        if (passthru_host) {
244                sin.sin_family = AF_INET;
245                (void) MEMORY_MOVE((char *) &sin.sin_addr,
246                                   passthru_haddr,
247                                   passthru_len);
248                sin.sin_port = passthru_port;
249        } else {
250                if ((hp = gethostbyname(host)) == (struct hostent *) 0) {
251                        sin.sin_family = AF_INET;
252                        sin.sin_addr.s_addr = inet_addr(host);
253                        if (sin.sin_addr.s_addr == -1) {
254                                popup_an_error("Unknown host:\n%s", hostname);
255                                return -1;
256                        }
257                }
258                else {
259                        sin.sin_family = hp->h_addrtype;
260                        (void) MEMORY_MOVE((char *) &sin.sin_addr,
261                                           (char *) hp->h_addr,
262                                           hp->h_length);
263                }
264                sin.sin_port = port;
265        }
266
267        /* create the socket */
268        if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
269                popup_an_errno(errno, "socket");
270                return -1;
271        }
272
273        /* set options for inline out-of-band data and keepalives */
274        if (setsockopt(sock, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0) {
275                popup_an_errno(errno, "setsockopt(SO_OOBINLINE)");
276                close_fail;
277        }
278        if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof(on)) < 0) {
279                popup_an_errno(errno, "setsockopt(SO_KEEPALIVE)");
280                close_fail;
281        }
282#if defined(OMTU) /*[*/
283        if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&mtu, sizeof(mtu)) < 0) {
284                popup_an_errno(errno, "setsockopt(SO_SNDBUF)");
285                close_fail;
286        }
287#endif /*]*/
288
289        /* set the socket to be non-delaying */
290        if (non_blocking(True) < 0)
291                close_fail;
292
293        /* don't share the socket with our children */
294        (void) fcntl(sock, F_SETFD, 1);
295
296        /* connect */
297        if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) == -1) {
298                if (errno == EWOULDBLOCK
299#if defined(EINPROGRESS) /*[*/
300                 || errno == EINPROGRESS
301#endif /*]*/
302                                        ) {
303                        trace_str("Connection pending.\n");
304                        *pending = True;
305                } else {
306                        popup_an_errno(errno, "Connect to %s, port %d",
307                            hostname, current_port);
308                        close_fail;
309                }
310        } else {
311                if (non_blocking(False) < 0)
312                        close_fail;
313                net_connected();
314        }
315
316        /* set up termporary termtype */
317        if (appres.termname == CN && std_ds_host) {
318                (void) sprintf(ttype_tmpval, "IBM-327%c-%d",
319                    appres.m3279 ? '9' : '8', model_num);
320                termtype = ttype_tmpval;
321        }
322
323        /* all done */
324        return sock;
325}
326#undef close_fail
327
328static void
329net_connected()
330{
331        (void) sprintf(trace_msg, "Connected to %s, port %u.\n",
332            hostname, current_port);
333        trace_str(trace_msg);
334
335        /* set up telnet options */
336        (void) memset((char *) myopts, 0, sizeof(myopts));
337        (void) memset((char *) hisopts, 0, sizeof(hisopts));
338        telnet_state = TNS_DATA;
339        ibptr = ibuf;
340        sbptr = &sbbuf[0];
341
342        /* clear statistics and flags */
343        (void) time(&ns_time);
344        ns_brcvd = 0;
345        ns_rrcvd = 0;
346        ns_bsent = 0;
347        ns_rsent = 0;
348        syncing = 0;
349
350        check_linemode(True);
351
352        /* write out the passthru hostname and port nubmer */
353        if (passthru_host) {
354                char *buf;
355
356                buf = XtMalloc(strlen(hostname) + 32);
357                (void) sprintf(buf, "%s %d\r\n", hostname, current_port);
358                (void) write(sock, buf, strlen(buf));
359                XtFree(buf);
360        }
361}
362
363/*
364 * net_disconnect
365 *      Shut down the socket.
366 */
367void
368net_disconnect()
369{
370        if (CONNECTED)
371                (void) shutdown(sock, 2);
372        (void) close(sock);
373        sock = -1;
374        trace_str("SENT disconnect\n");
375
376        /* Restore terminal type to its default. */
377        if (appres.termname == CN)
378                termtype = full_model_name;
379}
380
381
382/*
383 * net_input
384 *      Called by the toolkit whenever there is input available on the
385 *      socket.  Reads the data, processes the special telnet commands
386 *      and calls process_ds to process the 3270 data stream.
387 */
388void
389net_input()
390{
391        register unsigned char  *cp;
392        int     nr;
393
394        ansi_data = 0;
395
396        nr = read(sock, (char *) netrbuf, BUFSZ);
397        if (nr < 0) {
398                if (errno == EWOULDBLOCK)
399                        return;
400                if (HALF_CONNECTED && errno == EAGAIN) {
401                        if (non_blocking(False) < 0) {
402                                x_disconnect(True);
403                                return;
404                        }
405                        x_connected();
406                        net_connected();
407                        return;
408                }
409                (void) sprintf(trace_msg, "RCVD socket error %d\n", errno);
410                trace_str(trace_msg);
411                if (HALF_CONNECTED)
412                        popup_an_errno(errno, "Connect to %s, port %d",
413                            hostname, current_port);
414                else if (errno != ECONNRESET)
415                        popup_an_errno(errno, "Socket read");
416                x_disconnect(True);
417                return;
418        } else if (nr == 0) {
419                /* Host disconnected. */
420                trace_str("RCVD disconnect\n");
421                x_disconnect(False);
422                return;
423        }
424
425        /* Process the data. */
426
427        if (HALF_CONNECTED) {
428                if (non_blocking(False) < 0) {
429                        x_disconnect(True);
430                        return;
431                }
432                x_connected();
433                net_connected();
434        }
435
436        trace_netdata('<', netrbuf, nr);
437
438        ns_brcvd += nr;
439        for (cp = netrbuf; cp < (netrbuf + nr); cp++)
440                if (telnet_fsm(*cp)) {
441                        x_disconnect(True);
442                        return;
443                }
444
445        if (ansi_data) {
446                trace_str("\n");
447                ansi_data = 0;
448        }
449}
450
451
452/*
453 * set16
454 *      Put a 16-bit value in a buffer.
455 *      Returns the number of bytes required.
456 */
457static int
458set16(buf, n)
459char *buf;
460int n;
461{
462        char *b0 = buf;
463
464        n %= 256 * 256;
465        if ((n / 256) == IAC)
466                *(unsigned char *)buf++ = IAC;
467        *buf++ = (n / 256);
468        n %= 256;
469        if (n == IAC)
470                *(unsigned char *)buf++ = IAC;
471        *buf++ = n;
472        return buf - b0;
473}
474
475/*
476 * send_naws
477 *      Send a Telnet window size sub-option negotation.
478 */
479static void
480send_naws()
481{
482        char naws_msg[14];
483        int naws_len = 0;
484
485        (void) sprintf(naws_msg, "%c%c%c", IAC, SB, TELOPT_NAWS);
486        naws_len += 3;
487        naws_len += set16(naws_msg + naws_len, maxCOLS);
488        naws_len += set16(naws_msg + naws_len, maxROWS);
489        (void) sprintf(naws_msg + naws_len, "%c%c", IAC, SE);
490        naws_len += 2;
491        net_rawout((unsigned char *)naws_msg, naws_len);
492        (void) sprintf(trace_msg, "SENT %s NAWS %d %d %s\n", cmd(SB), maxCOLS,
493            maxROWS, cmd(SE));
494        trace_str(trace_msg);
495}
496
497
498/*
499 * telnet_fsm
500 *      Telnet finite-state machine.
501 *      Returns 0 for okay, -1 for errors.
502 */
503static int
504telnet_fsm(c)
505unsigned char c;
506{
507        char    *see_chr;
508        int     sl;
509
510        switch (telnet_state) {
511            case TNS_DATA:      /* normal data processing */
512                if (c == IAC) { /* got a telnet command */
513                        telnet_state = TNS_IAC;
514                        if (ansi_data) {
515                                trace_str("\n");
516                                ansi_data = 0;
517                        }
518                } else if (IN_ANSI) {
519                        if (kybdlock & KL_AWAITING_FIRST) {
520                                kybdlock_clr(KL_AWAITING_FIRST, "telnet_fsm");
521                                status_reset();
522                                ps_process();
523                        }
524                        if (!ansi_data) {
525                                trace_str("<.. ");
526                                ansi_data = 4;
527                        }
528                        see_chr = ctl_see((int) c);
529                        ansi_data += (sl = strlen(see_chr));
530                        if (ansi_data >= TRACELINE) {
531                                trace_str(" ...\n... ");
532                                ansi_data = 4 + sl;
533                        }
534                        trace_str(see_chr);
535                        if (!syncing) {
536                                ansi_process((unsigned int) c);
537                                sms_store(c);
538                        }
539                } else {
540                        store3270in(c);
541                }
542                break;
543            case TNS_IAC:       /* process a telnet command */
544                if (c != EOR && c != IAC) {
545                        (void) sprintf(trace_msg, "RCVD %s ", cmd(c));
546                        trace_str(trace_msg);
547                }
548                switch (c) {
549                    case IAC:   /* escaped IAC, insert it */
550                        if (IN_ANSI) {
551                                if (!ansi_data) {
552                                        trace_str("<.. ");
553                                        ansi_data = 4;
554                                }
555                                see_chr = ctl_see((int) c);
556                                ansi_data += (sl = strlen(see_chr));
557                                if (ansi_data >= TRACELINE) {
558                                        trace_str(" ...\n ...");
559                                        ansi_data = 4 + sl;
560                                }
561                                trace_str(see_chr);
562                                ansi_process((unsigned int) c);
563                                sms_store(c);
564                        } else
565                                store3270in(c);
566                        telnet_state = TNS_DATA;
567                        break;
568                    case EOR:   /* eor, process accumulated input */
569                        if (IN_3270) {
570                                ns_rrcvd++;
571                                if (!syncing && (ibptr - ibuf))
572                                        if (process_ds(ibuf, ibptr - ibuf))
573                                                return -1;
574                        } else
575                                XtWarning("EOR received when not in 3270 mode, ignored.");
576                        trace_str("RCVD EOR\n");
577                        ibptr = ibuf;
578                        telnet_state = TNS_DATA;
579                        break;
580                    case WILL:
581                        telnet_state = TNS_WILL;
582                        break;
583                    case WONT:
584                        telnet_state = TNS_WONT;
585                        break;
586                    case DO:
587                        telnet_state = TNS_DO;
588                        break;
589                    case DONT:
590                        telnet_state = TNS_DONT;
591                        break;
592                    case SB:
593                        telnet_state = TNS_SB;
594                        sbptr = sbbuf;
595                        break;
596                    case DM:
597                        trace_str("\n");
598                        if (syncing) {
599                                syncing = 0;
600                                x_except_on();
601                        }
602                        telnet_state = TNS_DATA;
603                        break;
604                    case GA:
605                        trace_str("\n");
606                        telnet_state = TNS_DATA;
607                        break;
608                    default:
609                        trace_str("???\n");
610                        telnet_state = TNS_DATA;
611                        break;
612                }
613                break;
614            case TNS_WILL:      /* telnet WILL DO OPTION command */
615                trace_str(opt(c));
616                trace_str("\n");
617                switch (c) {
618                    case TELOPT_SGA:
619                    case TELOPT_BINARY:
620                    case TELOPT_EOR:
621                    case TELOPT_TTYPE:
622                    case TELOPT_ECHO: /* hack! hack! */
623                        if (!hisopts[c]) {
624                                hisopts[c] = 1;
625                                do_opt[2] = c;
626                                net_rawout(do_opt, sizeof(do_opt));
627                                (void) sprintf(trace_msg, "SENT %s %s\n",
628                                    cmd(DO), opt(c));
629                                trace_str(trace_msg);
630                        }
631                        check_in3270();
632                        break;
633                    default:
634                        dont_opt[2] = c;
635                        net_rawout(dont_opt, sizeof(dont_opt));
636                        (void) sprintf(trace_msg, "SENT %s %s\n",
637                            cmd(DONT), opt(c));
638                        trace_str(trace_msg);
639                        break;
640                }
641                telnet_state = TNS_DATA;
642                check_linemode(False);
643                break;
644            case TNS_WONT:      /* telnet WONT DO OPTION command */
645                trace_str(opt(c));
646                trace_str("\n");
647                switch (c) {
648                    case TELOPT_BINARY:
649                    case TELOPT_EOR:
650                    case TELOPT_TTYPE:
651                        if (hisopts[c]) {
652                                dont_opt[2] = c;
653                                net_rawout(dont_opt, sizeof(dont_opt));
654                                (void) sprintf(trace_msg, "SENT %s %s\n",
655                                    cmd(DONT), opt(c));
656                                trace_str(trace_msg);
657                        }
658                        /* fall through */
659                    default:
660                        hisopts[c] = 0;
661                        break;
662                }
663                telnet_state = TNS_DATA;
664                check_in3270();
665                check_linemode(False);
666                break;
667            case TNS_DO:        /* telnet PLEASE DO OPTION command */
668                trace_str(opt(c));
669                trace_str("\n");
670                switch (c) {
671                    case TELOPT_BINARY:
672                    case TELOPT_EOR:
673                    case TELOPT_TTYPE:
674                    case TELOPT_SGA:
675                    case TELOPT_NAWS:
676                    case TELOPT_TM:
677                        if (!myopts[c]) {
678                                myopts[c] = 1;
679                                will_opt[2] = c;
680                                net_rawout(will_opt, sizeof(will_opt));
681                                (void) sprintf(trace_msg, "SENT %s %s\n",
682                                    cmd(WILL), opt(c));
683                                trace_str(trace_msg);
684                        }
685                        check_in3270();
686                        if (c == TELOPT_NAWS)
687                                send_naws();
688                        break;
689                    default:
690                        wont_opt[2] = c;
691                        net_rawout(wont_opt, sizeof(wont_opt));
692                        (void) sprintf(trace_msg, "SENT %s %s\n",
693                            cmd(WONT), opt(c));
694                        trace_str(trace_msg);
695                        break;
696                }
697                telnet_state = TNS_DATA;
698                break;
699            case TNS_DONT:      /* telnet PLEASE DON'T DO OPTION command */
700                trace_str(opt(c));
701                trace_str("\n");
702                switch (c) {
703                    case TELOPT_BINARY:
704                    case TELOPT_EOR:
705                    case TELOPT_TTYPE:
706                        wont_opt[2] = c;
707                        net_rawout(wont_opt, sizeof(wont_opt));
708                        (void) sprintf(trace_msg, "SENT %s %s\n",
709                            cmd(WONT), opt(c));
710                        trace_str(trace_msg);
711                        /* fall through */
712                    default:
713                        myopts[c] = 0;
714                        break;
715                }
716                telnet_state = TNS_DATA;
717                break;
718            case TNS_SB:        /* telnet sub-option string command */
719                if (c == IAC)
720                        telnet_state = TNS_SB_IAC;
721                else
722                        *sbptr++ = c;
723                break;
724            case TNS_SB_IAC:    /* telnet sub-option string command */
725                if (c == SE) {
726                        telnet_state = TNS_DATA;
727                        if (sbbuf[0] == TELOPT_TTYPE && sbbuf[1] == TELQUAL_SEND) {
728                                char *tt_out;
729
730                                (void) sprintf(trace_msg, "%s %s\n",
731                                    opt(sbbuf[0]), telquals[sbbuf[1]]);
732                                trace_str(trace_msg);
733
734                                tt_out = XtMalloc(4 + strlen(termtype) + 2 + 1);
735                                (void) sprintf(tt_out, "%c%c%c%c%s%c%c",
736                                    IAC, SB, TELOPT_TTYPE, TELQUAL_IS,
737                                    termtype,
738                                    IAC, SE);
739                                net_rawout((unsigned char *)tt_out,
740                                           4 + strlen(termtype) + 2);
741                                XtFree(tt_out);
742
743                                (void) sprintf(trace_msg,
744                                    "SENT %s %s %s %s %s\n",
745                                    cmd(SB), opt(TELOPT_TTYPE),
746                                    telquals[TELQUAL_IS], termtype, cmd(SE));
747                                trace_str(trace_msg);
748                                check_in3270();
749                        }
750                } else {
751                        *sbptr = c;     /* just stuff it */
752                        telnet_state = TNS_SB;
753                }
754                break;
755        }
756        return 0;
757}
758
759
760/*
761 * net_exception
762 *      Called when there is an exceptional condition on the socket.
763 */
764void
765net_exception()
766{
767        trace_str("RCVD urgent data indication\n");
768        if (!syncing) {
769                syncing = 1;
770                x_except_off();
771        }
772}
773
774/*
775 * Flavors of Network Output:
776 *
777 *   3270 mode
778 *      net_output      send a 3270 record
779 *
780 *   ANSI mode; call each other in turn
781 *      net_sendc       net_cookout for 1 byte
782 *      net_sends       net_cookout for a null-terminated string
783 *      net_cookout     send user data with cooked-mode processing, ANSI mode
784 *      net_cookedout   send user data, ANSI mode, already cooked
785 *      net_rawout      send telnet protocol data, ANSI mode
786 *
787 */
788
789
790/*
791 * net_rawout
792 *      Send out raw telnet data.  We assume that there will always be enough
793 *      space to buffer what we want to transmit, so we don't handle EAGAIN or
794 *      EWOULDBLOCK.
795 */
796static void
797net_rawout(buf, len)
798unsigned char   *buf;
799int     len;
800{
801        int     nw;
802
803        trace_netdata('>', buf, len);
804
805        while (len) {
806#if defined(OMTU) /*[*/
807                int n2w = len;
808                int pause = 0;
809
810                if (n2w > OMTU) {
811                        n2w = OMTU;
812                        pause = 1;
813                }
814#else
815#               define n2w len
816#endif
817                nw = write(sock, (char *) buf, n2w);
818                if (nw < 0) {
819                        (void) sprintf(trace_msg, "RCVD socket error %d\n",
820                            errno);
821                        trace_str(trace_msg);
822                        if (errno == EPIPE || errno == ECONNRESET) {
823                                x_disconnect(False);
824                                return;
825                        } else if (errno == EINTR) {
826                                goto bot;
827                        } else {
828                                popup_an_errno(errno, "Socket write");
829                                x_disconnect(True);
830                                return;
831                        }
832                }
833                ns_bsent += nw;
834                len -= nw;
835                buf += nw;
836            bot:
837#if defined(OMTU) /*[*/
838                if (pause)
839                        sleep(1);
840#endif /*]*/
841                ;
842        }
843}
844
845
846/*
847 * net_cookedout
848 *      Send user data out in ANSI mode, without cooked-mode processing.
849 */
850static void
851net_cookedout(buf, len)
852char    *buf;
853int     len;
854{
855        int i;
856
857        if (toggled(DS_TRACE)) {
858                (void) fprintf(tracef, ">");
859                for (i = 0; i < len; i++)
860                        (void) fprintf(tracef, " %s", ctl_see((int) *(buf+i)));
861                (void) fprintf(tracef, "\n");
862        }
863        net_rawout((unsigned char *) buf, len);
864}
865
866
867/*
868 * net_cookout
869 *      Send output in ANSI mode, including cooked-mode processing if
870 *      appropriate.
871 */
872static void
873net_cookout(buf, len)
874char    *buf;
875int     len;
876{
877
878        if (!IN_ANSI || (kybdlock & KL_AWAITING_FIRST))
879                return;
880
881        if (linemode) {
882                register int    i;
883                char    c;
884
885                for (i = 0; i < len; i++) {
886                        c = buf[i];
887
888                        /* Input conversions. */
889                        if (!lnext && c == '\r' && appres.icrnl)
890                                c = '\n';
891                        else if (!lnext && c == '\n' && appres.inlcr)
892                                c = '\r';
893
894                        /* Backslashes. */
895                        if (c == '\\' && !backslashed)
896                                backslashed = 1;
897                        else
898                                backslashed = 0;
899
900                        /* Control chars. */
901                        if (c == '\n')
902                                do_eol(c);
903                        else if (c == vintr)
904                                do_intr(c);
905                        else if (c == vquit)
906                                do_quit(c);
907                        else if (c == verase)
908                                do_cerase(c);
909                        else if (c == vkill)
910                                do_kill(c);
911                        else if (c == vwerase)
912                                do_werase(c);
913                        else if (c == vrprnt)
914                                do_rprnt(c);
915                        else if (c == veof)
916                                do_eof(c);
917                        else if (c == vlnext)
918                                do_lnext(c);
919                        else
920                                do_data(c);
921                }
922                return;
923        } else
924                net_cookedout(buf, len);
925}
926
927
928/*
929 * Cooked mode input processing.
930 */
931
932static void
933cooked_init()
934{
935        lbptr = lbuf;
936        lnext = 0;
937        backslashed = 0;
938}
939
940static void
941ansi_process_s(data)
942char *data;
943{
944        while (*data)
945                ansi_process((unsigned int) *data++);
946}
947
948static void
949forward_data()
950{
951        net_cookedout((char *) lbuf, lbptr - lbuf);
952        cooked_init();
953}
954
955static void
956do_data(c)
957char c;
958{
959        if (lbptr+1 < lbuf + sizeof(lbuf)) {
960                *lbptr++ = c;
961                if (c == '\r')
962                        *lbptr++ = '\0';
963                if (c == '\t')
964                        ansi_process((unsigned int) c);
965                else
966                        ansi_process_s(ctl_see((int) c));
967        } else
968                ansi_process_s("\007");
969        lnext = 0;
970        backslashed = 0;
971}
972
973static void
974do_intr(c)
975char c;
976{
977        if (lnext) {
978                do_data(c);
979                return;
980        }
981        ansi_process_s(ctl_see((int) c));
982        cooked_init();
983        net_interrupt();
984}
985
986static void
987do_quit(c)
988char c;
989{
990        if (lnext) {
991                do_data(c);
992                return;
993        }
994        ansi_process_s(ctl_see((int) c));
995        cooked_init();
996        net_break();
997}
998
999static void
1000do_cerase(c)
1001char c;
1002{
1003        int len;
1004
1005        if (backslashed) {
1006                lbptr--;
1007                ansi_process_s("\b");
1008                do_data(c);
1009                return;
1010        }
1011        if (lnext) {
1012                do_data(c);
1013                return;
1014        }
1015        if (lbptr > lbuf) {
1016                len = strlen(ctl_see((int) *--lbptr));
1017
1018                while (len--)
1019                        ansi_process_s("\b \b");
1020        }
1021}
1022
1023static void
1024do_werase(c)
1025char c;
1026{
1027        int any = 0;
1028        int len;
1029
1030        if (lnext) {
1031                do_data(c);
1032                return;
1033        }
1034        while (lbptr > lbuf) {
1035                char ch = *--lbptr;
1036
1037                if (ch == ' ' || ch == '\t') {
1038                        if (any) {
1039                                ++lbptr;
1040                                break;
1041                        }
1042                } else
1043                        any = 1;
1044                len = strlen(ctl_see((int) ch));
1045
1046                while (len--)
1047                        ansi_process_s("\b \b");
1048        }
1049}
1050
1051static void
1052do_kill(c)
1053char c;
1054{
1055        int i, len;
1056
1057        if (backslashed) {
1058                lbptr--;
1059                ansi_process_s("\b");
1060                do_data(c);
1061                return;
1062        }
1063        if (lnext) {
1064                do_data(c);
1065                return;
1066        }
1067        while (lbptr > lbuf) {
1068                len = strlen(ctl_see((int) *--lbptr));
1069
1070                for (i = 0; i < len; i++)
1071                        ansi_process_s("\b \b");
1072        }
1073}
1074
1075static void
1076do_rprnt(c)
1077char c;
1078{
1079        unsigned char *p;
1080
1081        if (lnext) {
1082                do_data(c);
1083                return;
1084        }
1085        ansi_process_s(ctl_see((int) c));
1086        ansi_process_s("\r\n");
1087        for (p = lbuf; p < lbptr; p++)
1088                ansi_process_s(ctl_see((int) *p));
1089}
1090
1091static void
1092do_eof(c)
1093char c;
1094{
1095        if (backslashed) {
1096                lbptr--;
1097                ansi_process_s("\b");
1098                do_data(c);
1099                return;
1100        }
1101        if (lnext) {
1102                do_data(c);
1103                return;
1104        }
1105        do_data(c);
1106        forward_data();
1107}
1108
1109static void
1110do_eol(c)
1111char c;
1112{
1113        if (lnext) {
1114                do_data(c);
1115                return;
1116        }
1117        if (lbptr+2 >= lbuf + sizeof(lbuf)) {
1118                ansi_process_s("\007");
1119                return;
1120        }
1121        *lbptr++ = '\r';
1122        *lbptr++ = '\n';
1123        ansi_process_s("\r\n");
1124        forward_data();
1125}
1126
1127static void
1128do_lnext(c)
1129char c;
1130{
1131        if (lnext) {
1132                do_data(c);
1133                return;
1134        }
1135        lnext = 1;
1136        ansi_process_s("^\b");
1137}
1138
1139
1140
1141/*
1142 * check_in3270
1143 *      Check for mode switches between ANSI and 3270 mode.
1144 */
1145static void
1146check_in3270()
1147{
1148        Boolean now3270 = myopts[TELOPT_BINARY] &&
1149            myopts[TELOPT_EOR] &&
1150            myopts[TELOPT_TTYPE] &&
1151            hisopts[TELOPT_BINARY] &&
1152            hisopts[TELOPT_EOR];
1153
1154        if (now3270 != IN_3270) {
1155                (void) sprintf(trace_msg, "No%c operating in 3270 mode.\n",
1156                    now3270 ? 'w' : 't');
1157                trace_str(trace_msg);
1158                x_in3270(now3270);
1159
1160                /* Allocate the initial 3270 input buffer. */
1161                if (now3270 && !ibuf_size) {
1162                        ibuf = (unsigned char *)XtMalloc(BUFSIZ);
1163                        ibuf_size = BUFSIZ;
1164                        ibptr = ibuf;
1165                }
1166        }
1167}
1168
1169/*
1170 * store3270in
1171 *      Store a character in the 3270 input buffer, checking for buffer
1172 *      overflow and reallocating ibuf if necessary.
1173 */
1174static void
1175store3270in(c)
1176unsigned char c;
1177{
1178        if (ibptr - ibuf >= ibuf_size) {
1179                ibuf_size += BUFSIZ;
1180                ibuf = (unsigned char *)XtRealloc((char *)ibuf, ibuf_size);
1181                ibptr = ibuf + ibuf_size - BUFSIZ;
1182        }
1183        *ibptr++ = c;
1184}
1185
1186/*
1187 * space3270out
1188 *      Ensure that <n> more characters will fit in the 3270 output buffer.
1189 */
1190void
1191space3270out(n)
1192int n;
1193{
1194        if (obuf_size == 0) {
1195                obuf_size = BUFSIZ;
1196                obuf = (unsigned char *)XtMalloc(obuf_size);
1197                obptr = obuf;
1198                return;
1199        }
1200        if ((obptr + n) - obuf >= obuf_size) {
1201                int nc = obptr - obuf;
1202
1203                obuf_size += BUFSIZ;
1204                obuf = (unsigned char *)XtRealloc((char *)obuf, obuf_size);
1205                obptr = obuf + nc;
1206        }
1207}
1208
1209
1210/*
1211 * check_linemode
1212 *      Set the global variable 'linemode', which says whether we are in
1213 *      character-by-character mode or line mode.
1214 */
1215static void
1216check_linemode(init)
1217Boolean init;
1218{
1219        int wasline = linemode;
1220
1221        /*
1222         * The next line is a deliberate kluge to effectively ignore the SGA
1223         * option.  If the host will echo for us, we assume
1224         * character-at-a-time; otherwise we assume fully cooked by us.
1225         *
1226         * This allows certain IBM hosts which volunteer SGA but refuse
1227         * ECHO to operate more-or-less normally, at the expense of
1228         * implementing the (hopefully useless) "character-at-a-time, local
1229         * echo" mode.
1230         *
1231         * We still implement "switch to line mode" and "switch to character
1232         * mode" properly by asking for both SGA and ECHO to be off or on, but
1233         * we basically ignore the reply for SGA.
1234         */
1235        linemode = !hisopts[TELOPT_ECHO] /* && !hisopts[TELOPT_SGA] */;
1236
1237        if (init || linemode != wasline) {
1238                menubar_newmode();
1239                if (!init) {
1240                        (void) sprintf(trace_msg, "Operating in %s mode.\n",
1241                            linemode ? "line" : "character-at-a-time");
1242                        trace_str(trace_msg);
1243                }
1244                if (linemode)
1245                        cooked_init();
1246        }
1247}
1248
1249
1250/*
1251 * nnn
1252 *      Expands a number to a character string, for displaying unknown telnet
1253 *      commands and options.
1254 */
1255static char *
1256nnn(c)
1257int     c;
1258{
1259        static char     buf[64];
1260
1261        (void) sprintf(buf, "%d", c);
1262        return buf;
1263}
1264
1265#if !defined(TELCMD) || !defined(TELCMD_OK) /*[*/
1266#undef TELCMD
1267#define TELCMD(x)       telcmds[(x) - SE]
1268#undef TELCMD_OK
1269#define TELCMD_OK(x)    ((x) >= SE && (x) <= IAC)
1270#endif /*]*/
1271
1272/*
1273 * cmd
1274 *      Expands a TELNET command into a character string.
1275 */
1276static char *
1277cmd(c)
1278unsigned char c;
1279{
1280        if (TELCMD_OK(c))
1281                return TELCMD(c);
1282        else
1283                return nnn((int)c);
1284}
1285
1286#if !defined(TELOPT) || !defined(TELOPT_OK) /*[*/
1287#undef TELOPT
1288#define TELOPT(x)       telopts[(x)]
1289#undef TELOPT_OK
1290#define TELOPT_OK(x)    ((x) >= TELOPT_BINARY && (x) <= TELOPT_EOR)
1291#endif /*]*/
1292
1293/*
1294 * opt
1295 *      Expands a TELNET option into a character string.
1296 */
1297static char *
1298opt(c)
1299unsigned char c;
1300{
1301        if (TELOPT_OK(c))
1302                return TELOPT(c);
1303        else if (c == TELOPT_TN3270E)
1304                return "TN3270E";
1305        else
1306                return nnn((int)c);
1307}
1308
1309
1310#define LINEDUMP_MAX    32
1311
1312void
1313trace_netdata(direction, buf, len)
1314char direction;
1315unsigned char *buf;
1316int len;
1317{
1318        int offset;
1319        struct timeval ts;
1320        double tdiff;
1321
1322        if (!toggled(DS_TRACE))
1323                return;
1324        (void) gettimeofday(&ts, (struct timezone *)NULL);
1325        if (IN_3270) {
1326                tdiff = ((1.0e6 * (double)(ts.tv_sec - ds_ts.tv_sec)) +
1327                        (double)(ts.tv_usec - ds_ts.tv_usec)) / 1.0e6;
1328                (void) fprintf(tracef, "%c +%gs\n", direction, tdiff);
1329        }
1330        ds_ts = ts;
1331        for (offset = 0; offset < len; offset++) {
1332                if (!(offset % LINEDUMP_MAX))
1333                        (void) fprintf(tracef, "%s%c 0x%-3x ",
1334                            (offset ? "\n" : ""), direction, offset);
1335                (void) fprintf(tracef, "%02x", buf[offset]);
1336        }
1337        (void) fprintf(tracef, "\n");
1338}
1339
1340
1341/*
1342 * net_output
1343 *      Send 3270 output over the network, tacking on the necessary
1344 *      telnet end-of-record command.
1345 */
1346void
1347net_output()
1348{
1349        /* Count the number of IACs in the message. */
1350        {
1351                char *buf = (char *)obuf;
1352                int len = obptr - obuf;
1353                char *iac;
1354                int cnt = 0;
1355
1356                while (len && (iac = memchr(buf, IAC, len)) != CN) {
1357                        cnt++;
1358                        len -= iac - buf + 1;
1359                        buf = iac + 1;
1360                }
1361                if (cnt) {
1362                        space3270out(cnt);
1363                        len = obptr - obuf;
1364                        buf = (char *)obuf;
1365
1366                        /* Now quote them. */
1367                        while (len && (iac = memchr(buf, IAC, len)) != CN) {
1368                                int ml = len - (iac - buf);
1369
1370                                MEMORY_MOVE(iac + 1, iac, ml);
1371                                len -= iac - buf + 1;
1372                                buf = iac + 2;
1373                                obptr++;
1374                        }
1375                }
1376        }
1377
1378        /* Add IAC EOR to the end and send it. */
1379        space3270out(2);
1380        *obptr++ = IAC;
1381        *obptr++ = EOR;
1382        net_rawout(obuf, obptr - obuf);
1383
1384        trace_str("SENT EOR\n");
1385        ns_rsent++;
1386        cooked_init();  /* in case we go back to ANSI mode */
1387}
1388
1389/*
1390 * Add IAC EOR to a buffer.
1391 */
1392void
1393net_add_eor(buf, len)
1394unsigned char *buf;
1395int len;
1396{
1397        buf[len++] = IAC;
1398        buf[len++] = EOR;
1399}
1400
1401
1402/*
1403 * net_sendc
1404 *      Send a character of user data over the network in ANSI mode.
1405 */
1406void
1407net_sendc(c)
1408char    c;
1409{
1410        if (c == '\r' && !linemode)     /* CR must be quoted */
1411                net_cookout("\r\n", 2);
1412        else
1413                net_cookout(&c, 1);
1414}
1415
1416
1417/*
1418 * net_sends
1419 *      Send a null-terminated string of user data in ANSI mode.
1420 */
1421void
1422net_sends(s)
1423char    *s;
1424{
1425        net_cookout(s, strlen(s));
1426}
1427
1428
1429/*
1430 * net_send_erase
1431 *      Sends the KILL character in ANSI mode.
1432 */
1433void
1434net_send_erase()
1435{
1436        net_cookout(&verase, 1);
1437}
1438
1439
1440/*
1441 * net_send_kill
1442 *      Sends the KILL character in ANSI mode.
1443 */
1444void
1445net_send_kill()
1446{
1447        net_cookout(&vkill, 1);
1448}
1449
1450
1451/*
1452 * net_send_werase
1453 *      Sends the WERASE character in ANSI mode.
1454 */
1455void
1456net_send_werase()
1457{
1458        net_cookout(&vwerase, 1);
1459}
1460
1461
1462/*
1463 * External entry points to negotiate line or character mode.
1464 */
1465void
1466net_linemode()
1467{
1468        if (!CONNECTED)
1469                return;
1470        if (hisopts[TELOPT_ECHO]) {
1471                dont_opt[2] = TELOPT_ECHO;
1472                net_rawout(dont_opt, sizeof(dont_opt));
1473                (void) sprintf(trace_msg, "SENT %s %s\n",
1474                    cmd(DONT), opt(TELOPT_ECHO));
1475                trace_str(trace_msg);
1476        }
1477        if (hisopts[TELOPT_SGA]) {
1478                dont_opt[2] = TELOPT_SGA;
1479                net_rawout(dont_opt, sizeof(dont_opt));
1480                (void) sprintf(trace_msg, "SENT %s %s\n",
1481                    cmd(DONT), opt(TELOPT_SGA));
1482                trace_str(trace_msg);
1483        }
1484}
1485
1486void
1487net_charmode()
1488{
1489        if (!CONNECTED)
1490                return;
1491        if (!hisopts[TELOPT_ECHO]) {
1492                do_opt[2] = TELOPT_ECHO;
1493                net_rawout(do_opt, sizeof(do_opt));
1494                (void) sprintf(trace_msg, "SENT %s %s\n", cmd(DO),
1495                    opt(TELOPT_ECHO));
1496                trace_str(trace_msg);
1497        }
1498        if (!hisopts[TELOPT_SGA]) {
1499                do_opt[2] = TELOPT_SGA;
1500                net_rawout(do_opt, sizeof(do_opt));
1501                (void) sprintf(trace_msg, "SENT %s %s\n", cmd(DO),
1502                    opt(TELOPT_SGA));
1503                trace_str(trace_msg);
1504        }
1505}
1506
1507
1508/*
1509 * net_break
1510 *      Send telnet break, which is used to implement 3270 ATTN.
1511 *
1512 */
1513void
1514net_break()
1515{
1516        static unsigned char buf[] = { IAC, BREAK };
1517
1518        /* I don't know if we should first send TELNET synch ? */
1519        net_rawout(buf, sizeof(buf));
1520        trace_str("SENT BREAK\n");
1521}
1522
1523/*
1524 * net_interrupt
1525 *      Send telnet IP.
1526 *
1527 */
1528static void
1529net_interrupt()
1530{
1531        static unsigned char buf[] = { IAC, IP };
1532
1533        /* I don't know if we should first send TELNET synch ? */
1534        net_rawout(buf, sizeof(buf));
1535        trace_str("SENT IP\n");
1536}
1537
1538/*
1539 * parse_ctlchar
1540 *      Parse an stty control-character specification.
1541 *      A cheap, non-complaining implementation.
1542 */
1543static char
1544parse_ctlchar(s)
1545char *s;
1546{
1547        if (!s || !*s)
1548                return 0;
1549        if ((int) strlen(s) > 1) {
1550                if (*s != '^')
1551                        return 0;
1552                else if (*(s+1) == '?')
1553                        return 0177;
1554                else
1555                        return *(s+1) - '@';
1556        } else
1557                return *s;
1558}
1559
1560/*
1561 * net_linemode_chars
1562 *      Report line-mode characters.
1563 */
1564struct ctl_char *
1565net_linemode_chars()
1566{
1567        static struct ctl_char c[9];
1568
1569        c[0].name = "intr";     (void) strcpy(c[0].value, ctl_see(vintr));
1570        c[1].name = "quit";     (void) strcpy(c[1].value, ctl_see(vquit));
1571        c[2].name = "erase";    (void) strcpy(c[2].value, ctl_see(verase));
1572        c[3].name = "kill";     (void) strcpy(c[3].value, ctl_see(vkill));
1573        c[4].name = "eof";      (void) strcpy(c[4].value, ctl_see(veof));
1574        c[5].name = "werase";   (void) strcpy(c[5].value, ctl_see(vwerase));
1575        c[6].name = "rprnt";    (void) strcpy(c[6].value, ctl_see(vrprnt));
1576        c[7].name = "lnext";    (void) strcpy(c[7].value, ctl_see(vlnext));
1577        c[8].name = 0;
1578
1579        return c;
1580}
1581
1582/*
1583 * Construct a string to reproduce the current TELNET options.
1584 * Returns a Boolean indicating whether it is necessary.
1585 */
1586Boolean
1587net_snap_options()
1588{
1589        Boolean any = False;
1590        int i;
1591        static unsigned char ttype_str[] = {
1592                IAC, DO, TELOPT_TTYPE,
1593                IAC, SB, TELOPT_TTYPE, TELQUAL_SEND, IAC, SE
1594        };
1595
1596        if (!CONNECTED)
1597                return False;
1598
1599        obptr = obuf;
1600
1601        /* Do TTYPE first. */
1602        if (myopts[TELOPT_TTYPE]) {
1603                space3270out(sizeof(ttype_str));
1604                for (i = 0; i < sizeof(ttype_str); i++)
1605                        *obptr++ = ttype_str[i];
1606        }
1607
1608        /* Do the other options. */
1609        for (i = 0; i < N_OPTS; i++) {
1610                space3270out(6);
1611                if (i == TELOPT_TTYPE)
1612                        continue;
1613                if (hisopts[i]) {
1614                        *obptr++ = IAC;
1615                        *obptr++ = WILL;
1616                        *obptr++ = (unsigned char)i;
1617                        any = True;
1618                }
1619                if (myopts[i]) {
1620                        *obptr++ = IAC;
1621                        *obptr++ = DO;
1622                        *obptr++ = (unsigned char)i;
1623                        any = True;
1624                }
1625        }
1626        return any;
1627}
1628
1629/*
1630 * Set blocking/non-blocking mode on the socket.  On error, pops up an error
1631 * message, but does not close the socket.
1632 */
1633static int
1634non_blocking(on)
1635Boolean on;
1636{
1637#if !defined(BLOCKING_CONNECT_ONLY) /*[*/
1638# if defined(FIONBIO) /*[*/
1639        int i = on ? 1 : 0;
1640
1641        if (ioctl(sock, FIONBIO, &i) < 0) {
1642                popup_an_errno(errno, "ioctl(FIONBIO)");
1643                return -1;
1644        }
1645# else /*][*/
1646        int f;
1647
1648        if ((f = fcntl(sock, F_GETFL, 0)) == -1) {
1649                popup_an_errno(errno, "fcntl(F_GETFL)");
1650                return -1;
1651        }
1652        if (on)
1653                f |= O_NDELAY;
1654        else
1655                f &= ~O_NDELAY;
1656        if (fcntl(sock, F_SETFL, f) < 0) {
1657                popup_an_errno(errno, "fcntl(F_SETFL)");
1658                return -1;
1659        }
1660# endif /*]*/
1661#endif /*]*/
1662        return 0;
1663}
1664
1665static void
1666trace_str(s)
1667char *s;
1668{
1669        if (!toggled(DS_TRACE))
1670                return;
1671        (void) fprintf(tracef, "%s", s);
1672}
Note: See TracBrowser for help on using the repository browser.