source: trunk/third/kermit/ckuscr.c @ 20081

Revision 20081, 17.7 KB checked in by zacheiss, 21 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r20080, which included commits to RCS files with non-trunk default branches.
Line 
1#include "ckcsym.h"
2
3#ifndef NOICP
4#ifndef NOSCRIPT
5char *loginv = "Script Command, 8.0.032, 20 Dec 2001";
6
7/*  C K U S C R  --  expect-send script implementation  */
8
9/*
10  Copyright (C) 1985, 2002,
11    Trustees of Columbia University in the City of New York.
12    All rights reserved.  See the C-Kermit COPYING.TXT file or the
13    copyright text in the ckcmai.c module for disclaimer and permissions.
14
15  Original (version 1, 1985) author: Herm Fischer, Encino, CA.
16  Contributed to Columbia University in 1985 for inclusion in C-Kermit 4.0.
17  Maintained since 1985 by Frank da Cruz, Columbia University,
18  fdc@columbia.edu.
19
20  The module takes a UUCP-style script of the "expect send [expect send] ..."
21  format.  It is intended to operate similarly to the way the common
22  UUCP L.sys login entries work.  Conditional responses are supported:
23  expect[-send-expect[...]], as with UUCP.  The send keyword EOT sends a
24  Control-d, and the keyword BREAK sends a break.  Letters prefixed
25  by '~' are '~b' backspace, '~s' space, '~n' linefeed, '~r' return, '~x' xon,
26  '~t' tab, '~q' ? (not allowed on kermit command lines), '~' ~, '~'',
27  '~"', '~c' don't append return, '~o[o[o]]' octal character.  As with
28  some uucp systems, sent strings are followed by ~r (not ~n) unless they
29  end with ~c. Null expect strings (e.g., ~0 or --) cause a short
30  delay, and are useful for sending sequences requiring slight pauses.
31
32  This module calls externally defined system-dependent functions for
33  communications i/o, as defined in ckcplm.txt, the C-Kermit Program Logic
34  Manual, and thus should be portable to all systems that implement those
35  functions, and where alarm() and signal() work as they do in UNIX.
36*/
37#include "ckcdeb.h"
38#include <signal.h>
39#ifdef NT
40#include <setjmpex.h>
41#else /* NT */
42#include <setjmp.h>
43#endif /* NT */
44#include "ckcasc.h"
45#include "ckcker.h"
46#include "ckuusr.h"
47#include "ckcnet.h"
48#include "ckcsig.h"
49
50_PROTOTYP( VOID flushi, (void) );
51_PROTOTYP( static VOID myflsh, (void) );
52_PROTOTYP( static int sequenc, (void) );
53_PROTOTYP( static VOID recvseq, (void) );
54_PROTOTYP( static int outseq, (void) );
55
56#ifdef MAC
57#define signal msignal
58#define SIGTYP long
59#define alarm malarm
60#define SIG_IGN 0
61#define SIGALRM 1
62#define SIGINT  2
63SIGTYP (*msignal(int type, SIGTYP (*func)(int)))(int);
64#endif /* MAC */
65
66#ifdef AMIGA
67#define signal asignal
68#define alarm aalarm
69#define SIGALRM (_NUMSIG+1)
70#define SIGTYP void
71SIGTYP (*asignal(int type, SIGTYP (*func)(int)))(int);
72unsigned aalarm(unsigned);
73#endif /* AMIGA */
74
75#ifdef STRATUS
76/* VOS doesn't have alarm(), but it does have some things we can work with. */
77/* however, we have to catch all the signals in one place to do this, so    */
78/* we intercept the signal() routine and call it from our own replacement.  */
79#define signal vsignal
80#define alarm valarm
81SIGTYP (*vsignal(int type, SIGTYP (*func)(int)))(int);
82int valarm(int interval);
83#endif /* STRATUS */
84
85extern int sessft;
86extern int local, flow, seslog, mdmtyp, msgflg, duplex, backgrd, secho, quiet;
87extern int network, nettype, ttnproto;
88extern long speed;
89extern char ttname[];
90
91#ifdef NTSIG
92extern int TlsIndex;
93#endif /* NTSIG */
94#ifdef IKSD
95extern int inserver;
96#endif /* IKSD */
97
98static int is_tn = 0;                   /* Do Telnet negotiations */
99
100#ifndef NOSPL
101#ifdef DCMDBUF
102extern struct cmdptr *cmdstk;
103#else
104extern struct cmdptr cmdstk[];
105#endif /* DCMDBUF */
106extern int techo, cmdlvl;
107extern int mecho;
108#endif /* NOSPL */
109
110static int scr_echo;                    /* Whether to echo script commands */
111
112static int exp_alrm = 15;               /* Time to wait for expect string */
113#define SND_ALRM 15                     /* Time to allow for sending string */
114#define NULL_EXP 2                      /* Time to pause on null expect strg*/
115#define DEL_MSEC 300                    /* Milliseconds to pause on ~d */
116
117#define SBUFL 512
118static char seq_buf[SBUFL+2], *s;       /* expect-send sequence buffer */
119static int got_it, no_cr;
120
121/*  Connect state parent/child communication signal handlers */
122
123#ifdef COMMENT
124#ifdef CK_POSIX_SIG
125static sigjmp_buf alrmrng;
126#else
127static jmp_buf alrmrng;
128#endif /* CK_POSIX_SIG */
129#else
130static ckjmpbuf alrmrng;
131#endif /* COMMENT */
132
133static SIGTYP
134#ifdef CK_ANSIC
135scrtime(int foo)                        /* modem read failure handler, */
136#else
137scrtime(foo) int foo;                   /* Alarm handler */
138#endif /* CK_ANSIC */
139/* scrtime */ {
140
141#ifdef BEBOX
142#ifdef BE_DR_7
143    alarm_expired();
144#endif /* BE_DR_7 */
145#endif /* BEBOX */
146#ifdef NTSIG
147    if (foo == SIGALRM)
148      PostAlarmSigSem();
149    else
150      PostCtrlCSem();
151#else /* NTSIG */
152#ifdef NT
153    cklongjmp(ckjaddr(alrmrng),1);
154#else /* NT */
155    cklongjmp(alrmrng,1);
156#endif /* NT */
157#endif /* NTSIG */
158    SIGRETURN;
159}
160
161/*
162 Sequence interpreter -- pick up next sequence from command string,
163 decode escapes and place into seq_buf.
164
165 If string contains a ~d (delay) then sequenc() returns a 1 expecting
166 to be called again after the ~d executes.
167*/
168static int
169sequenc() {
170    int i;
171    char c, oct_char;
172
173    no_cr = 0;                          /* output needs cr appended */
174    for (i = 0; i < SBUFL; ) {
175        if (*s == '\0' || *s == '-' || isspace(*s) ) { /* done */
176            seq_buf[i] = '\0';
177            return(0) ;
178        }
179        if (*s == '~') {                /* escape character */
180            s++;
181            switch (c = *s) {
182                case 'n':  seq_buf[i++] = LF; break;
183                case 'r':  seq_buf[i++] = CR; break;
184                case 't':  seq_buf[i++] = '\t'; break;
185                case 'b':  seq_buf[i++] = '\b'; break;
186                case 'q':  seq_buf[i++] = '?';  break;
187#ifdef COMMENT
188/* The default case should catch these now... */
189                case '~':  seq_buf[i++] = '~';  break;
190                case '-':  seq_buf[i++] = '-';  break;
191#endif /* COMMENT */
192                case '\'': seq_buf[i++] = '\''; break;
193                case '\"': seq_buf[i++] = '\"'; break;
194                case 's':  seq_buf[i++] = ' ';  break;
195                case 'x':  seq_buf[i++] = '\021'; break;
196                case 'c':  no_cr = 1; break;
197                case 'd': {                     /* send what we have & then */
198                    seq_buf[i] = '\0';          /* expect to send rest after */
199                    no_cr = 1;                  /* sender delays a little */
200                    s++;
201                    return(1);
202                }
203                case 'w': {                     /* wait count */
204                    exp_alrm = 15;              /* default to 15 sec */
205                    if (isdigit(*(s+1))) {
206                        s++;
207                        exp_alrm = *s & 15;
208                        if (isdigit(*(s+1)) ) {
209                            s++;
210                            exp_alrm = exp_alrm * 10 + (*s & 15);
211                        }
212                    }
213                    break;
214                }
215                default:
216                    if ( isdigit(c) ) {         /* octal character */
217                        oct_char = (char) (c & 7); /* most significant digit */
218                        if (isdigit( *(s+1) ) ) {
219                            s++;
220                            oct_char = (char) ((oct_char<<3) | ( *s & 7 ));
221                            if (isdigit( *(s+1) ) ) {
222                                s++;
223                                oct_char = (char) ((oct_char<<3) | ( *s & 7 ));
224                            }
225                        }
226                        seq_buf[i++] = oct_char;
227                        break;
228                    } else seq_buf[i++] = *s; /* Treat ~ as quote */
229              }
230        } else seq_buf[i++] = *s;       /* Plain old character */
231        s++;
232    }
233    seq_buf[i] = '\0';
234    return(0);                          /* end of space, return anyway */
235}
236
237
238/* Output buffering for "recvseq" and "flushi" */
239
240#define MAXBURST 256            /* maximum size of input burst */
241static CHAR conbuf[MAXBURST];   /* buffer to hold output for console */
242static int concnt = 0;          /* number of characters buffered */
243static CHAR sesbuf[MAXBURST];   /* buffer to hold output for session log */
244static int sescnt = 0;          /* number of characters buffered */
245
246static VOID
247myflsh() {
248    if (concnt > 0) {
249        conxo(concnt, (char *) conbuf);
250        concnt = 0;
251    }
252    if (sescnt > 0) {
253        logstr((char *) sesbuf, sescnt);
254        sescnt = 0;
255    }
256}
257
258/* these variables are used to pass data between the recvseq() */
259/* and the dorseq().  They are necessary because in some versions */
260/* dorseq() is executed in a separate thread and data cannot be */
261/* passed by parameter. */
262
263static char *rseqe, * rseqgot, * rseqtrace ;
264static int rseql;
265
266static SIGTYP
267#ifdef CK_ANSIC
268dorseq(void * threadinfo)
269#else /* CK_ANSIC */
270dorseq(threadinfo) VOID * threadinfo;
271#endif /* CK_ANSIC */
272/* dorseq */ {
273    int i, x;
274    int burst = 0;                      /* chars remaining in input burst */
275
276#ifdef NTSIG
277    setint();
278    if (threadinfo) {                   /* Thread local storage... */
279        TlsSetValue(TlsIndex,threadinfo);
280    }
281#endif /* NTSIG */
282#ifdef CK_LOGIN
283#ifdef NT
284#ifdef IKSD
285    if (inserver)
286      setntcreds();
287#endif /* IKSD */
288#endif /* NT */
289#endif /* CK_LOGIN */
290
291    while (!got_it) {
292        for (i = 0; i < rseql-1; i++) rseqgot[i] = rseqgot[i+1];
293        x = ttinc(0);                   /* Read a character */
294        debug(F101,"recvseq","",x);
295        if (x < 0) {
296#ifdef NTSIG
297            ckThreadEnd(threadinfo);
298#endif /* NTSIG */
299            SIGRETURN;                  /* Check for error */
300        }
301#ifdef NETCONN
302#ifdef TNCODE
303/* Check for telnet protocol negotiation */
304        if (((x & 0xff) == IAC) && is_tn) { /* Telnet negotiation */
305            myflsh();
306            burst = 0;
307            switch (tn_doop((CHAR)(x & 0xff),duplex,ttinc)) {
308              case 2: duplex = 0; continue;
309              case 1: duplex = 1;
310              default: continue;
311            }
312        }
313#endif /* TNCODE */
314#endif /* NETCONN */
315        rseqgot[rseql-1] = (char) (x & 0x7f); /* Got a character */
316        burst--;                        /* One less waiting */
317        if (scr_echo) conbuf[concnt++] = rseqgot[rseql-1]; /* Buffer it */
318        if (seslog)                     /* Log it in session log */
319#ifdef UNIX
320          if (sessft != 0 || rseqgot[rseql-1] != '\r')
321#else
322#ifdef OSK
323            if (sessft != 0 || rseqgot[rseql-1] != '\012')
324#endif /* OSK */
325#endif /* UNIX */
326              if (rseqgot[rseql-1])     /* Filter out NULs */
327                sesbuf[sescnt++] = rseqgot[rseql-1];
328        if ((int)strlen(rseqtrace) < SBUFL-2 )
329          strcat(rseqtrace,dbchr(rseqgot[rseql-1]));
330        got_it = (!strncmp(rseqe, rseqgot, rseql));
331        if (burst <= 0) {               /* Flush buffered output */
332            myflsh();
333            if ((burst = ttchk()) < 0) { /* Get size of next input burst */
334#ifdef NTSIG
335                ckThreadEnd(threadinfo);
336#endif /* NTSIG */
337                SIGRETURN;
338            }
339            /* prevent overflow of "conbuf" and "sesbuf" */
340            if (burst > MAXBURST)
341              burst = MAXBURST;
342        }
343    }
344#ifdef NTSIG
345    ckThreadEnd(threadinfo);
346#endif /* NTSIG */
347    SIGRETURN;
348}
349
350static SIGTYP
351#ifdef CK_ANSIC
352failrseq(void * threadinfo)
353#else /* CK_ANSIC */
354failrseq(threadinfo) VOID * threadinfo;
355#endif /* CK_ANSIC */
356/* failrseq */ {
357     got_it = 0;                        /* Timed out here */
358     SIGRETURN;
359}
360
361/*
362  Receive sequence -- see if expected response comes,
363  return success (or failure) in got_it.
364*/
365static VOID
366recvseq() {
367    char *e, got[7], trace[SBUFL];
368    int i, l;
369
370    sequenc();
371    l = (int)strlen(e=seq_buf);         /* no more than 7 chars allowed */
372    if (l > 7) {
373        e += l-7;
374        l = 7;
375    }
376    tlog(F111,"expecting sequence",e,(long) l);
377    if (l == 0) {                       /* null sequence, delay a little */
378        sleep (NULL_EXP);
379        got_it = 1;
380        tlog(F100,"got it (null sequence)","",0L);
381        return;
382    }
383    *trace = '\0';
384    for (i = 0; i < 7; i++) got[i]='\0';
385
386    rseqtrace = trace;
387    rseqe = e;
388    rseqgot = got;
389    rseql = l;
390
391    alrm_execute(ckjaddr(alrmrng), exp_alrm, scrtime, dorseq, failrseq);
392
393    tlog(F110,"received sequence: ",trace,0L);
394    tlog(F101,"returning with got-it code","",(long) got_it);
395    myflsh();                           /* Flush buffered output */
396    return;
397}
398
399/*
400 Output A Sequence starting at pointer s,
401 return 0 if okay,
402 1 if failed to read (modem hangup or whatever)
403*/
404static int oseqret = 0;                 /* Return code for outseq */
405                                        /* Out here to prevent clobbering */
406                                        /* by longjmp. */
407
408static SIGTYP
409#ifdef CK_ANSIC
410dooseq(void * threadinfo)
411#else /* CK_ANSIC */
412dooseq(threadinfo) VOID * threadinfo;
413#endif /* CK_ANSIC */
414{
415    int l;
416    char *sb;
417#ifdef TCPSOCKET
418    extern int tn_nlm, tn_b_nlm;
419#endif /* TCPSOCKET */
420
421#ifdef NTSIG
422    setint();
423    if (threadinfo) {                   /* Thread local storage... */
424        TlsSetValue(TlsIndex,threadinfo);
425    }
426#endif /* NTSIG */
427#ifdef CK_LOGIN
428#ifdef NT
429#ifdef IKSD
430    if (inserver)
431      setntcreds();
432#endif /* IKSD */
433#endif /* NT */
434#endif /* CK_LOGIN */
435
436    l = (int)strlen(seq_buf);
437    tlog(F111,"sending sequence ",seq_buf,(long) l);
438
439    if (!strcmp(seq_buf,"EOT")) {
440        ttoc(dopar('\004'));
441        if (scr_echo) conol("<EOT>");
442        if (seslog && duplex)
443            logstr("<EOT>",5);
444    } else if (!strcmp(seq_buf,"BREAK") ||
445               !strcmp(seq_buf,"\\b") ||
446               !strcmp(seq_buf,"\\B")) {
447        ttsndb();
448        if (scr_echo) conol("<BREAK>");
449        if (seslog)
450          logstr("{BREAK}",7);
451    } else {
452        if (l > 0) {
453            for ( sb = seq_buf; *sb; sb++)
454              *sb = dopar(*sb); /* add parity */
455            ttol((CHAR *)seq_buf,l); /* send it */
456            if (scr_echo && duplex) {
457#ifndef NOLOCAL
458#ifdef OS2
459                {                       /* Echo to emulator */
460                    char *s = seq_buf;
461                    while (*s) {
462                        scriptwrtbuf((USHORT)*s);
463                    }
464                }
465#endif /* OS2 */
466#endif /* NOLOCAL */
467                conxo(l,seq_buf);
468            }
469            if (seslog && duplex) /* log it */
470              logstr(seq_buf,strlen(seq_buf));
471        }
472        if (!no_cr) {
473            ttoc( dopar(CR) );
474#ifdef TCPSOCKET
475            if (is_tn) {
476                if (!TELOPT_ME(TELOPT_BINARY) && tn_nlm != TNL_CR)
477                  ttoc((char)((tn_nlm == TNL_CRLF) ?
478                              dopar(LF) : dopar(NUL)));
479                else if (TELOPT_ME(TELOPT_BINARY) &&
480                         (tn_b_nlm == TNL_CRLF || tn_b_nlm == TNL_CRNUL))
481                  ttoc((char)((tn_b_nlm == TNL_CRLF) ?
482                              dopar(LF) : dopar(NUL)));
483            }
484#endif /* TCPSOCKET */
485            if (seslog && duplex)
486              logchar(dopar(CR));
487        }
488    }
489#ifdef NTSIG
490    ckThreadEnd(threadinfo);
491#endif /* NTSIG */
492    SIGRETURN;
493}
494
495SIGTYP
496#ifdef CK_ANSIC
497failoseq(void * threadinfo)
498#else /* CK_ANSIC */
499failoseq(threadinfo) VOID * threadinfo;
500#endif /* CK_ANSIC */
501/* failoseq */ {
502     oseqret = -1;              /* else -- alarm rang */
503     SIGRETURN;
504}
505
506static int
507outseq() {
508    int delay;
509
510    oseqret = 0;                        /* Initialize return code */
511    while(1) {
512        delay = sequenc();
513        alrm_execute( ckjaddr(alrmrng), SND_ALRM, scrtime, dooseq, failoseq ) ;
514
515        if (!delay)
516          return(oseqret);
517#ifndef MAC
518        msleep(DEL_MSEC);               /* delay, loop to next send */
519#endif /* MAC */
520    }
521}
522
523
524/*  L O G I N  --  (historical misnomer) Execute the SCRIPT command */
525
526int
527dologin(cmdstr) char *cmdstr; {
528
529#ifdef OS2
530#ifdef NT
531    SIGTYP (* savealm)(int);            /* Save incoming alarm function */
532#else /* NT */
533    SIGTYP (* volatile savealm)(int);   /* Save incoming alarm function */
534#endif /* NT */
535#else /* OS2 */
536    SIGTYP (*savealm)();                /* Save incoming alarm function */
537#endif /* OS2 */
538    char *e;
539
540    s = cmdstr;                         /* Make global to this module */
541
542    tlog(F100,loginv,"",0L);
543
544    if (speed < 0L) speed = ttgspd();
545    if (ttopen(ttname,&local,mdmtyp,0) < 0) {
546        ckmakmsg(seq_buf,SBUFL,"Sorry, can't open ",ttname,NULL,NULL);
547        perror(seq_buf);
548        return(0);
549    }
550    /* Whether to echo script commands ... */
551    scr_echo = (!quiet && !backgrd && secho);
552#ifndef NOSPL
553    if (scr_echo && cmdlvl > 1) {
554        if (cmdstk[cmdlvl].src == CMD_TF)
555          scr_echo = techo;
556        if (cmdstk[cmdlvl].src == CMD_MD)
557          scr_echo = mecho;
558    }
559#endif /* NOSPL */
560    if (scr_echo) {
561#ifdef NETCONN
562        if (network)
563          printf("Executing SCRIPT to host %s.\n",ttname);
564        else
565#endif /* NETCONN */
566          printf("Executing SCRIPT through %s, speed %ld.\n",ttname,speed);
567    }
568#ifdef TNCODE
569    /* TELNET input must be scanned for IAC */
570    is_tn = (local && network && IS_TELNET()) ||
571            (!local && sstelnet);
572#endif /* TNCODE */
573
574    *seq_buf = 0;
575    for (e = s; *e; e++) ckstrncat(seq_buf,dbchr(*e),SBUFL);
576#ifdef COMMENT
577/* Skip this because it tends to contain a password... */
578    if (scr_echo) printf("SCRIPT string: %s\n",seq_buf);
579#endif /* COMMENT */
580    tlog(F110,"SCRIPT string: ",seq_buf, 0L);
581
582/* Condition console terminal and communication line... */
583
584    if (ttvt(speed,flow) < 0) {
585        printf("Sorry, Can't condition communication line\n");
586        return(0);
587    }
588    /* Save initial timer interrupt value */
589    savealm = signal(SIGALRM,SIG_IGN);
590
591    flushi();                           /* Flush stale input */
592
593/* start expect - send sequence */
594
595    while (*s) {                        /* While not done with buffer */
596
597        while (*s && isspace(*s)) s++;  /* Skip over separating whitespaces */
598                                        /* Gather up expect sequence */
599        got_it = 0;
600        recvseq();
601
602        while (!got_it) {               /* Have it yet? */
603            if (*s++ != '-')            /* No, is there a conditional send? */
604              goto failret;             /* No, return failure */
605            flushi();                   /* Yes, flush out input buffer */
606            if (outseq())               /* If unable to send, */
607              goto failret;             /* return failure. */
608            if (*s++ != '-')            /* If no conditional response here, */
609              goto failret;             /* return failure. */
610            recvseq();                  /* All OK, read response from host. */
611        }                               /* Loop back and check got_it */
612
613        while (*s && !isspace(*s++) ) ; /* Skip over conditionals */
614        while (*s && isspace(*s)) s++;  /* Skip over separating whitespaces */
615        flushi();                       /* Flush */
616        if (*s) if (outseq()) goto failret; /* If any */
617    }
618    signal(SIGALRM,savealm);
619    if (scr_echo) printf("Script successful.\n");
620    tlog(F100,"Script successful.","",0L);
621    return(1);
622
623failret:
624    signal(SIGALRM,savealm);
625    if (scr_echo) printf("Sorry, script failed\n");
626    tlog(F100,"Script failed","",0L);
627    return(0);
628}
629
630/*  F L U S H I  --  Flush, but log, SCRIPT input buffer  */
631
632VOID
633flushi() {
634    int n, x;
635    if (
636        seslog                          /* Logging session? */
637        || scr_echo                     /* Or console echoing? */
638#ifdef NETCONN
639#ifdef TNCODE
640        /* TELNET input must be scanned for IAC */
641        || is_tn
642#endif /* TNCODE */
643#endif /* NETCONN */
644        ) {
645        if ((n = ttchk()) < 0)          /* Yes, anything in buffer? */
646          return;
647        if (n > MAXBURST) n = MAXBURST; /* Make sure not too much, */
648        myflsh();                       /* and that buffers are empty. */
649        while (n-- > 0) {
650            x = ttinc(0);               /* Collect a character */
651#ifdef NETCONN
652#ifdef TNCODE
653/* Check for telnet protocol negotiation */
654            if (is_tn && ((x & 0xff) == IAC) ) {
655                myflsh();               /* Sync output */
656                switch (tn_doop((CHAR)(x & 0xff),duplex,ttinc)) {
657                  case 2: duplex = 0; break;
658                  case 1: duplex = 1;
659                  default: break;
660                }
661
662                /* Recalculate flush count */
663                if ((n = ttchk()) < 0)
664                  return;
665                if (n > MAXBURST) n = MAXBURST;
666                continue;
667            }
668#endif /* TNCODE */
669#endif /* NETCONN */
670            if (scr_echo) conbuf[concnt++] = (CHAR) x; /* buffer for console */
671            if (seslog)
672#ifdef UNIX
673              if (sessft != 0 || x != '\r')
674#else
675#ifdef OSK
676              if (sessft != 0 || x != '\012')
677#endif /* OSK */
678#endif /* UNIX */
679                sesbuf[sescnt++] = (CHAR) x; /* buffer for session log */
680        }
681        myflsh();
682    } else ttflui();                    /* Otherwise just flush. */
683}
684
685#else /* NOSCRIPT */
686char *loginv = "Script Command Disabled";
687#endif /* NOSCRIPT */
688#endif /* NOICP */
Note: See TracBrowser for help on using the repository browser.