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 */ 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 147 GAP2_Delete(session) 148 GAP2_SessionHandle session; 149 { 150 } 151 152 void 153 GAP2_Reset() 154 { 155 } 156 157 GAP2_CreateResults 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 */ 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 */ 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 */ 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 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 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 */ 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 */ 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 */ 618 childdied() 619 { 620 #ifdef DEBUG 621 BUGOUT("child died"); 622 #endif 623 cleanup(); 624 longjmp(childdiedbuf, -1); 625 } 626 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 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 * 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 * 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 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