1 #ifndef lint
2 static char RCSid[] = "$Header: gap2d.c,v 2.0 85/11/21 07:23:00 jqj Exp $";
3 #endif
4 /*
5 * server for GAP-style (TransportObject=server,teletype) telnet connections
6 * Note that we support only GAP version 2, although a server for version 3
7 * exists. The version 2 server has not been tested as thoroughly as has the
8 * version 3; it does NOT support RESERVE functionality.
9 */
10
11 /* $Log: gap2d.c,v $
12 * Revision 2.0 85/11/21 07:23:00 jqj
13 * 4.3BSD standard release
14 *
15 * Revision 1.2 85/05/23 06:22:18 jqj
16 * *** empty log message ***
17 *
18 * Revision 1.2 85/05/23 06:22:18 jqj
19 * *** empty log message ***
20 *
21 * Revision 1.1 85/05/22 09:46:52 jqj
22 * Initial revision
23 */
24 #include <stdio.h>
25 #include <signal.h>
26 #include <sgtty.h>
27 #include <sys/types.h>
28 #include <sys/time.h>
29 #include <sys/uio.h>
30 #include <sys/socket.h>
31 #include <netns/ns.h>
32 #include <netns/idp.h>
33 #include <netns/sp.h>
34 #include <sys/wait.h>
35 #include <xnscourier/realcourierconnection.h>
36 #include "GAP2.h"
37 #include "gapcontrols.h"
38 #include <xnscourier/except.h>
39 #include <errno.h>
40
41 #define BELL '\07'
42 #define BANNER "\r\n\r\n4.3 BSD UNIX (%s)\r\n\r\r\n\r%s"
43
44 int pty, net;
45 extern CourierConnection *_serverConnection;
46 char buf[sizeof(struct sphdr)+SPPMAXDATA];
47 struct sphdr our_sphdr;
48 struct iovec our_iovec[2] = {{((caddr_t)&our_sphdr), sizeof(our_sphdr)}};
49 /*
50 * I/O data buffers, pointers, and counters.
51 */
52 char ptyibuf[512], *ptyip = ptyibuf;
53 char ptyobuf[BUFSIZ], *pfrontp = ptyobuf, *pbackp = ptyobuf;
54 char *netip = buf;
55 char netobuf[512], *nfrontp = netobuf, *nbackp = netobuf;
56 int pcc, ncc;
57 char line[12];
58 extern char **environ;
59 extern int errno;
60
61 char *envinit[3];
62 char wsenv[50];
63
64 /*
65 * session parameters
66 */
67 Cardinal frametimeout; /* 0 or time in seconds to wait */
68
69
70 /*
71 * This modified version of the server is necessary since GAP specifies
72 * that the telnet data-transfer session occurs after the RPC to create
73 * it has returned!
74 */
Server(skipcount,skippedwords)75 Server(skipcount,skippedwords)
76 int skipcount;
77 Unspecified skippedwords[];
78 {
79 Cardinal _procedure;
80 register Unspecified *_buf;
81 LongCardinal programnum;
82 Cardinal versionnum;
83 Cardinal _n;
84
85 #ifdef DEBUG
86 BUGOUT("Server: %d %d",skipcount,skippedwords);
87 #endif
88 for (;;) {
89 _buf = ReceiveCallMessage(&_procedure, skipcount, skippedwords);
90 DURING switch (_procedure) {
91 case 3:
92 server_GAP2_Delete(_buf);
93 break;
94 case 2:
95 server_GAP2_Create(_buf);
96 net = _serverConnection->fd;
97 gaptelnet(); /* returns on connection close */
98 break;
99 case 0:
100 server_GAP2_Reset(_buf);
101 break;
102 default:
103 NoSuchProcedureValue("GAP", _procedure);
104 break;
105 } HANDLER {
106 Deallocate(_buf);
107 switch (Exception.Code) {
108 case GAP2_terminalAddressInvalid:
109 case GAP2_terminalAddressInUse:
110 case GAP2_controllerDoesNotExist:
111 case GAP2_controllerAlreadyExists:
112 case GAP2_gapCommunicationError:
113 case GAP2_gapNotExported:
114 case GAP2_bugInGAPCode:
115 case GAP2_tooManyGateStreams:
116 case GAP2_inconsistentParams:
117 case GAP2_transmissionMediumUnavailable:
118 case GAP2_dialingHardwareProblem:
119 case GAP2_noDialingHardware:
120 case GAP2_badAddressFormat:
121 case GAP2_mediumConnectFailed:
122 case GAP2_illegalTransport:
123 case GAP2_noCommunicationHardware:
124 case GAP2_unimplemented:
125 _buf = Allocate(0);
126 SendAbortMessage(Exception.Code-ERROR_OFFSET, 0, _buf);
127 break;
128 default:
129 _buf = Allocate(0);
130 SendRejectMessage(unspecifiedError, 0, _buf);
131 break;
132 }
133 } END_HANDLER;
134 Deallocate(_buf);
135 for (;;) {
136 skipcount = LookAheadCallMsg(&programnum, &versionnum,
137 skippedwords);
138 if (skipcount < 0) return(0); /* timed out */
139 if (programnum != 3 || versionnum != 2)
140 ExecCourierProgram(programnum, versionnum,
141 skipcount, skippedwords);
142 } /* loop if can't exec that program */
143 }
144 }
145
146 void
GAP2_Delete(session)147 GAP2_Delete(session)
148 GAP2_SessionHandle session;
149 {
150 }
151
152 void
GAP2_Reset()153 GAP2_Reset()
154 {
155 }
156
157 GAP2_CreateResults
GAP2_Create(conn,BDTproc,sessionparams,transports,createTimeout)158 GAP2_Create(conn, BDTproc, sessionparams, transports,
159 createTimeout)
160 CourierConnection *conn;
161 int BDTproc;
162 GAP2_SessionParameterObject sessionparams;
163 struct {Cardinal length;
164 GAP2_TransportObject *sequence;
165 } transports;
166 GAP2_WaitTime createTimeout;
167 {
168 GAP2_CreateResults result;
169 char *c1, *c2, *host;
170 int t, pid;
171 struct sgttyb b;
172 char *xntoa(), *wsname();
173 struct sockaddr_ns who;
174 int whosize = sizeof(who);
175 LongCardinal servicetype;
176 GAP2_CommParamObject *cp;
177 GAP2_Duplexity duplexity; /* fullDuplex, halfDuplex */
178
179 #ifdef DEBUG
180 BUGOUT("CREATE");
181 #endif
182 switch (sessionparams.designator) {
183 case ttyHost:
184 frametimeout = sessionparams.ttyHost_case.frameTimeout/1000;
185 /* could set other parameters here */
186 break;
187 default:
188 raise(GAP2_unimplemented, 0);
189 /*NOTREACHED*/
190 }
191 if (transports.length != 2) {
192 raise(GAP2_illegalTransport);
193 /*NOTREACHED*/
194 }
195 switch (transports.sequence[0].designator) {
196 case rs232c: /* maybe some day */
197 cp = &transports.sequence[0].rs232c_case.commParams;
198 if (cp->accessDetail.designator == directConn) {
199 duplexity = cp->duplex;
200 servicetype = 0; /* fake it */
201 break;
202 }
203 raise(GAP2_noCommunicationHardware, 0);
204 /*NOTREACHED*/
205 default:
206 raise(GAP2_illegalTransport, 0);
207 /*NOTREACHED*/
208 }
209 if (transports.sequence[1].designator != teletype)
210 raise(GAP2_illegalTransport, 0);
211 /* ignore createTimeout */
212 /* ignore credentials and verifier */
213
214 for (c1 = "pq"; *c1 != 0; c1++)
215 for (c2 = "0123456789abcdef"; *c2 != 0; c2++) {
216 sprintf(line, "/dev/pty%c%c", *c1, *c2);
217 pty = open(line, 2);
218 if (pty < 0) continue;
219 line[strlen("/dev/")] = 't';
220 t = open(line, 2);
221 if (t > 0) goto gotpty;
222 close(pty);
223 }
224 raise(GAP2_tooManyGateStreams, 0);
225 /*NOTREACHED*/
226 gotpty:
227 getpeername(_serverConnection->fd, &who, &whosize);
228 host = wsname(who.sns_addr);
229 #ifdef DEBUG
230 BUGOUT("gotpty <%s> %d <%s>",line, pty, host);
231 #endif
232 ioctl(t, TIOCGETP, &b);
233 b.sg_flags = CRMOD|XTABS|ANYP;
234 ioctl(t, TIOCSETP, &b);
235 ioctl(pty, TIOCGETP, &b);
236 if (duplexity == fullduplex)
237 b.sg_flags |= ECHO;
238 else
239 b.sg_flags &= ~ECHO;
240 ioctl(pty, TIOCSETP, &b);
241 /* we do the fork now so we can return failures as REPORTS */
242 pid = fork();
243 if (pid < 0) {
244 close(pty); close(t);
245 raise(GAP2_tooManyGateStreams, 0);
246 /*NOTREACHED*/
247 }
248 else if (pid == 0) { /* in the execed fork */
249 sleep(1); /* let parent get ready for us */
250 close(_serverConnection->fd); /* close net */
251 close(pty);
252 dup2(t, 0);
253 dup2(t, 1);
254 dup2(t, 2);
255 if (t > 2) close(t);
256 envinit[0] = "TERM=network";
257 (void)sprintf(wsenv, "WORKSTATION=%s", xntoa(who.sns_addr));
258 envinit[1] = wsenv;
259 envinit[2] = (char*) 0;
260 #ifdef DEBUG
261 BUGOUT("about to exec /bin/login");
262 #endif
263 execl("/bin/login","login", "-h", host, 0);
264 #ifdef DEBUG
265 BUGOUT("exec of /bin/login failed");
266 #endif
267 perror("/bin/login");
268 exit(1);
269 /*NOTREACHED*/
270 }
271 close(t);
272 #ifdef DEBUG
273 BUGOUT("fork successful");
274 #endif
275 result.session[0] = pid;
276 return(result);
277 }
278
279 jmp_buf childdiedbuf;
280
281 /*
282 * Main loop. Select from pty and network, and
283 * hand data to telnet receiver finite state machine.
284 * Returns 0 on orderly shutdown, 1 on abnormal shutdown.
285 */
gaptelnet()286 gaptelnet()
287 {
288 int on = 1;
289 char hostname[32];
290 int childdied();
291 int ibits = 0, obits = 0;
292 register int c;
293 struct sphdr *si = (struct sphdr *)buf;
294 static struct timeval timeout = {600,0};
295 int keepalives = 0;
296 int i;
297
298 #ifdef DEBUG
299 BUGOUT("gaptelnet net=%d,pty=%d",net,pty);
300 #endif
301 if (setjmp(childdiedbuf) != 0)
302 return(0); /* child died */
303 signal(SIGCHLD, childdied);
304 signal(SIGTSTP, SIG_IGN);
305 ioctl(net, FIONBIO, &on);
306 ioctl(pty, FIONBIO, &on);
307
308
309 /*
310 * Show banner that getty never gave.
311 */
312 gethostname(hostname, sizeof (hostname));
313 sprintf(nfrontp, BANNER, hostname, "");
314 nfrontp += strlen(nfrontp);
315 /*
316 * Send status message indicating we're ready to go
317 */
318 changeSPPopts(net, GAPCTLnone, 1);
319 sendoobdata(GAPCTLmediumUp);
320 for (;;) {
321 #ifdef DEBUG
322 BUGOUT("looping in gaptelnet");
323 #endif
324 ibits = obits = 0;
325 /*
326 * Never look for input if there's still
327 * stuff in the corresponding output buffer
328 */
329 if (nfrontp - nbackp || pcc > 0)
330 obits |= (1 << net);
331 else
332 ibits |= (1 << pty);
333 if (pfrontp - pbackp || ncc > 0)
334 obits |= (1 << pty);
335 else
336 ibits |= (1 << net);
337 if (ncc < 0 && pcc < 0)
338 break;
339 timeout.tv_sec = 600;
340 timeout.tv_usec = 0;
341 select(16, &ibits, &obits, 0, &timeout);
342 if (ibits == 0 && obits == 0) {
343 /* timeout means no activity for a long time */
344 #ifdef DEBUG
345 BUGOUT("timeout from select");
346 #endif
347 if (keepalives++ < 2) {
348 /* first 2 times through send warning */
349 if (nfrontp == nbackp && pcc == 0) {
350 /* but only if not blocked on output */
351 #define WARNING "\r\nYou've been idle much too long. Respond or log off.\r\n"
352 strcpy(nfrontp, WARNING);
353 nfrontp += sizeof(WARNING);
354 }
355 sleep(5);
356 continue;
357 }
358 #ifdef DEBUG
359 BUGOUT("keepalive expired -- calling cleanup");
360 #endif
361 /* keepalive count has expired */
362 cleanup();
363 return(1);
364 }
365
366 /*
367 * Something to read from the network...
368 */
369 if (ibits & (1 << net)) {
370 ncc = read(net, buf, sizeof(buf));
371 #ifdef DEBUG
372 BUGOUT("read from net %d",ncc);
373 #endif
374 if (ncc < 0 && errno == EWOULDBLOCK)
375 ncc = 0;
376 else if (ncc < sizeof(struct sphdr)) {
377 #ifdef DEBUG
378 BUGOUT("short read, %d. calling cleanup",ncc);
379 #endif
380 cleanup(); /* will probably fail or block */
381 return(1);
382 }
383 else if (si->sp_cc & SP_OB) {
384 /* a status or OOB control */
385 switch (buf[sizeof(struct sphdr)]) {
386 case GAPCTLinterrupt:
387 /* shove interrupt char in buffer */
388 interrupt();
389 break; /* from switch */
390 case GAPCTLareYouThere:
391 sendoobdata(GAPCTLiAmHere);
392 break; /* from switch */
393 default:
394 /* Ignore other controls instead of:
395 * sendoobdata(
396 * GAPCTLunexpectedRemoteBehavior);
397 */
398 break; /* from switch */
399 }
400 ncc = 0; /* no chars here */
401 }
402 else if (si->sp_dt==GAPCTLnone) {
403 /* the normal case */
404 ncc -= sizeof(struct sphdr);
405 netip = buf + sizeof(struct sphdr);
406 keepalives = 0;
407 }
408 else if(si->sp_dt==GAPCTLcleanup) {
409 #ifdef DEBUG
410 BUGOUT("got CLEANUP packet. Done");
411 #endif
412 cleanup(); /* normal termination */
413 return(0);
414 }
415 else if (si->sp_dt==SPPSST_END) {
416 /* got premature termination */
417 quitquit(net, pty);
418 return(1);
419 }
420 }
421
422 /*
423 * Something to read from the pty...
424 */
425 if (ibits & (1 << pty)) {
426 if (frametimeout > 0) sleep(frametimeout);
427 pcc = read(pty, ptyibuf, sizeof(ptyibuf));
428 #ifdef DEBUG
429 BUGOUT("read from pty %d",pcc);
430 #endif
431 if (pcc < 0 && errno == EWOULDBLOCK)
432 pcc = 0;
433 else if (pcc <= 0) {
434 #ifdef DEBUG
435 BUGOUT("short read from pty. Calling cleanup");
436 #endif
437 cleanup();
438 return(1); /* ?? abnormal termination */
439 }
440 ptyip = ptyibuf;
441 }
442
443 while (pcc > 0) {
444 if ((&netobuf[sizeof(netobuf)] - nfrontp) < 2)
445 break;
446 *nfrontp++ = *ptyip++ & 0377; pcc--;
447 }
448 if ((obits & (1 << net)) && (nfrontp - nbackp) > 0)
449 netflush();
450 while (ncc > 0) {
451 if ((&ptyobuf[sizeof(ptyobuf)] - pfrontp) < 2) break;
452 *pfrontp++ = *netip++ & 0377;
453 ncc--;
454 }
455 if ((obits & (1 << pty)) && (pfrontp - pbackp) > 0)
456 ptyflush();
457 }
458 /* we should never get to here */
459 #ifdef DEBUG
460 BUGOUT("broke out of for(;;) somehow. calling cleanup");
461 #endif
462 cleanup();
463 return(0);
464 }
465
466 /*
467 * Send out of band data to other end of network
468 */
sendoobdata(value)469 sendoobdata(value)
470 u_char value;
471 {
472 struct {
473 struct sphdr hdr;
474 char val;
475 } oob;
476 oob.hdr = our_sphdr;
477 oob.val = value;
478 #ifdef DEBUG
479 BUGOUT("sendoobdata 0%o",value);
480 #endif
481 send(net, &oob, sizeof(oob), MSG_OOB);
482 }
483
484 /*
485 * Send interrupt to process on other side of pty.
486 * If it is in raw mode, just write NULL;
487 * otherwise, write intr char.
488 */
interrupt()489 interrupt()
490 {
491 struct sgttyb b;
492 struct tchars tchars;
493
494 ptyflush(); /* half-hearted */
495 ioctl(pty, TIOCGETP, &b);
496 if (b.sg_flags & RAW) {
497 *pfrontp++ = '\0';
498 return;
499 }
500 *pfrontp++ = ioctl(pty, TIOCGETC, &tchars) < 0 ?
501 '\177' : tchars.t_intrc;
502 }
503
ptyflush()504 ptyflush()
505 {
506 register int n;
507
508 if ((n = pfrontp - pbackp) > 0)
509 n = write(pty, pbackp, n);
510 #ifdef DEBUG
511 BUGOUT("ptyflush wrote %d",n);
512 #endif
513 if (n < 0)
514 return;
515 pbackp += n;
516 if (pbackp >= pfrontp) /* actually, > is an error */
517 pbackp = pfrontp = ptyobuf;
518 }
519
netflush()520 netflush()
521 {
522 register int n;
523
524 if ((n = nfrontp - nbackp) > 0) {
525 our_iovec[1].iov_len = ((n > SPPMAXDATA) ? SPPMAXDATA : n);
526 our_iovec[1].iov_base = nbackp;
527 n = writev(net, our_iovec, 2) - sizeof(struct sphdr);
528 }
529 #ifdef DEBUG
530 BUGOUT("netflush wrote %d",n);
531 if (our_iovec[0].iov_base != (char*)&our_sphdr)
532 BUGOUT("Oops: our_iovec clobbered");
533 BUGOUT("header: %d %d, %d %d %d %d %d %d",
534 our_sphdr.sp_cc, our_sphdr.sp_dt,
535 our_sphdr.sp_sid, our_sphdr.sp_did, our_sphdr.sp_seq,
536 our_sphdr.sp_ack, our_sphdr.sp_alo);
537 #endif
538 if (n < 0) {
539 if (errno == EWOULDBLOCK)
540 return;
541 /* should blow this guy away... */
542 return;
543 }
544 nbackp += n;
545 if (nbackp >= nfrontp) /* actually , > is an error */
546 nbackp = nfrontp = netobuf;
547 }
548
549 /*
550 * handle receipt of an SPPSST_END packet
551 * This is currently an error, since client didn't send "cleanup" first
552 */
quitquit()553 quitquit()
554 {
555 changeSPPopts(net, SPPSST_ENDREPLY, 1);
556 write(net, &our_sphdr, sizeof(our_sphdr));
557 sleep(3);
558
559 rmut();
560 vhangup(); /* XXX */
561 shutdown(net, 1);
562 close(net);
563 }
564
565 /*
566 * shut down the data connection for one reason or another
567 */
cleanup()568 cleanup()
569 {
570 int fdmask;
571 struct timeval timeout;
572 struct sphdr *si = (struct sphdr *)buf;
573 int off = 0;
574
575 signal(SIGCHLD, SIG_IGN);
576 sendoobdata(GAPCTLcleanup);
577 changeSPPopts(net, SPPSST_END, 1);
578 if (write(net, &our_sphdr, sizeof(our_sphdr)) < 0) {
579 fdmask = 1<<net;
580 timeout.tv_sec = 10;
581 while (select(net+1,&fdmask,(int*)0, (int*)0, &timeout) > 0 &&
582 read(net,buf,sizeof(buf)) >= sizeof(struct sphdr)) {
583 #ifdef DEBUG
584 BUGOUT("cleanup -- got packet");
585 #endif
586 if ((si->sp_cc & SP_OB)
587 && si->sp_dt == SPPSST_ENDREPLY) {
588 changeSPPopts(net, SPPSST_ENDREPLY, 1);
589 write(net, &our_sphdr, sizeof(our_sphdr));
590 #ifdef DEBUG
591 BUGOUT("cleanup -- wrote ENDREPLY");
592 #endif
593 sleep(1);
594 changeSPPopts(net,0,0);
595 ioctl(net, FIONBIO, &off);
596 rmut();
597 vhangup(); /* XXX */
598 return;
599 }
600 /* loop: ignore everything except ENDREPLY */
601 fdmask = 1<<net;
602 timeout.tv_sec = 10;
603 }
604 /* timed out or read failed */
605 changeSPPopts(net, SPPSST_ENDREPLY, 1);
606 write(net, &our_sphdr, sizeof(our_sphdr));
607 sleep(1);
608 }
609 shutdown(net, 1);
610 close(net);
611 rmut();
612 vhangup(); /* XXX */
613 }
614
615 /*
616 * SIGCHLD interrupt handler
617 */
childdied()618 childdied()
619 {
620 #ifdef DEBUG
621 BUGOUT("child died");
622 #endif
623 cleanup();
624 longjmp(childdiedbuf, -1);
625 }
626
changeSPPopts(s,stream,eom)627 changeSPPopts(s, stream, eom)
628 int s; /* SPP socket */
629 u_char stream; /* datastream type */
630 char eom; /* Boolean EOM */
631 {
632 our_sphdr.sp_dt = stream;
633 our_sphdr.sp_cc = (eom ? SP_EM : 0);
634 }
635
636
637 #include <utmp.h>
638
639 struct utmp wtmp;
640 char wtmpf[] = "/usr/adm/wtmp";
641 char utmp[] = "/etc/utmp";
642 #define SCPYN(a, b) strncpy(a, b, sizeof (a))
643 #define SCMPN(a, b) strncmp(a, b, sizeof (a))
644
rmut()645 rmut()
646 {
647 register f;
648 int found = 0;
649
650 f = open(utmp, 2);
651 if (f >= 0) {
652 while(read(f, (char *)&wtmp, sizeof (wtmp)) == sizeof (wtmp)) {
653 if (SCMPN(wtmp.ut_line, line+5) || wtmp.ut_name[0]==0)
654 continue;
655 lseek(f, -(long)sizeof (wtmp), 1);
656 SCPYN(wtmp.ut_name, "");
657 SCPYN(wtmp.ut_host, "");
658 time(&wtmp.ut_time);
659 write(f, (char *)&wtmp, sizeof (wtmp));
660 found++;
661 }
662 close(f);
663 }
664 if (found) {
665 f = open(wtmpf, 1);
666 if (f >= 0) {
667 SCPYN(wtmp.ut_line, line+5);
668 SCPYN(wtmp.ut_name, "");
669 SCPYN(wtmp.ut_host, "");
670 time(&wtmp.ut_time);
671 lseek(f, (long)0, 2);
672 write(f, (char *)&wtmp, sizeof (wtmp));
673 close(f);
674 }
675 }
676 chmod(line, 0666);
677 chown(line, 0, 0);
678 line[strlen("/dev/")] = 'p';
679 chmod(line, 0666);
680 chown(line, 0, 0);
681 }
682
683 /*
684 * Convert network-format xns address
685 * to ascii
686 * --Replace this with a clearinghouse name lookup someday.
687 */
688 char *
wsname(addr)689 wsname(addr)
690 struct ns_addr addr;
691 {
692 static char b[50];
693 char temp[10];
694 int i;
695
696 /* net */
697 sprintf(b, "%D.", ntohl(ns_netof(addr)));
698 /* skip leading zeros */
699 for(i=0; (addr.x_host.c_host[i] == (char) 0); i++) ;
700 /* print the rest */
701 for(; i < 6; i++) {
702 sprintf(temp,"%x", addr.x_host.c_host[i]);
703 strcat(b, temp);
704 if(i != 5) strcat(b, ":");
705 }
706 return (b);
707 }
708
709 /*
710 * generate an xns address that "DE" can parse.
711 * This goes in the environment. Should be the same as above
712 */
713 char *
xntoa(addr)714 xntoa(addr)
715 struct ns_addr addr;
716 {
717 static char b[50];
718 char temp[10];
719 int i;
720
721 /* net */
722 sprintf(b, "%X#", ntohl(ns_netof(addr)));
723 /* print the rest */
724 for(i=0; i < 6; i++) {
725 sprintf(temp,"%x", addr.x_host.c_host[i]);
726 strcat(b, temp);
727 if(i != 5) strcat(b, ".");
728 }
729 return (b);
730 }
731
732 #ifdef DEBUG
BUGOUT(str,a,b,c,d,e,f,g,h)733 BUGOUT(str,a,b,c,d,e,f,g,h)
734 char *str;
735 {
736 FILE *fd;
737 fd = fopen("/tmp/GAP2d.log","a");
738 fprintf(fd,str,a,b,c,d,e,f,g,h);
739 putc('\n',fd);
740 fclose(fd);
741 }
742 #endif
743