1 /* $NetBSD: collect.c,v 1.42 2007/10/29 23:20:38 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1980, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #ifndef lint 34 #if 0 35 static char sccsid[] = "@(#)collect.c 8.2 (Berkeley) 4/19/94"; 36 #else 37 __RCSID("$NetBSD: collect.c,v 1.42 2007/10/29 23:20:38 christos Exp $"); 38 #endif 39 #endif /* not lint */ 40 41 /* 42 * Mail -- a mail program 43 * 44 * Collect input from standard input, handling 45 * ~ escapes. 46 */ 47 48 #include <assert.h> 49 #include <util.h> 50 51 #include "rcv.h" 52 #include "extern.h" 53 #include "format.h" 54 #ifdef MIME_SUPPORT 55 #include "mime.h" 56 #endif 57 #include "thread.h" 58 59 60 /* 61 * Read a message from standard input and return a read file to it 62 * or NULL on error. 63 */ 64 65 /* 66 * The following hokiness with global variables is so that on 67 * receipt of an interrupt signal, the partial message can be salted 68 * away on dead.letter. 69 */ 70 71 static sig_t saveint; /* Previous SIGINT value */ 72 static sig_t savehup; /* Previous SIGHUP value */ 73 static sig_t savetstp; /* Previous SIGTSTP value */ 74 static sig_t savettou; /* Previous SIGTTOU value */ 75 static sig_t savettin; /* Previous SIGTTIN value */ 76 static FILE *collf; /* File for saving away */ 77 static int hadintr; /* Have seen one SIGINT so far */ 78 79 static jmp_buf colljmp; /* To get back to work */ 80 static int colljmp_p; /* whether to long jump */ 81 static jmp_buf collabort; /* To end collection with error */ 82 83 84 /* 85 * Write a file, ex-like if f set. 86 */ 87 static int 88 exwrite(const char name[], FILE *fp, int f) 89 { 90 FILE *of; 91 int c; 92 long cc; 93 int lc; 94 struct stat junk; 95 96 if (f) { 97 (void)printf("\"%s\" ", name); 98 (void)fflush(stdout); 99 } 100 if (stat(name, &junk) >= 0 && S_ISREG(junk.st_mode)) { 101 if (!f) 102 (void)fprintf(stderr, "%s: ", name); 103 (void)fprintf(stderr, "File exists\n"); 104 return -1; 105 } 106 if ((of = Fopen(name, "w")) == NULL) { 107 warn("%s", name); 108 return -1; 109 } 110 lc = 0; 111 cc = 0; 112 while ((c = getc(fp)) != EOF) { 113 cc++; 114 if (c == '\n') 115 lc++; 116 (void)putc(c, of); 117 if (ferror(of)) { 118 warn("%s", name); 119 (void)Fclose(of); 120 return -1; 121 } 122 } 123 (void)Fclose(of); 124 (void)printf("%d/%ld\n", lc, cc); 125 (void)fflush(stdout); 126 return 0; 127 } 128 129 /* 130 * Edit the message being collected on fp. 131 * On return, make the edit file the new temp file. 132 */ 133 static void 134 mesedit(FILE *fp, int c) 135 { 136 sig_t sigint = signal(SIGINT, SIG_IGN); 137 FILE *nf = run_editor(fp, (off_t)-1, c, 0); 138 139 if (nf != NULL) { 140 (void)fseek(nf, 0L, 2); 141 collf = nf; 142 (void)Fclose(fp); 143 } 144 (void)signal(SIGINT, sigint); 145 } 146 147 /* 148 * Pipe the message through the command. 149 * Old message is on stdin of command; 150 * New message collected from stdout. 151 * Sh -c must return 0 to accept the new message. 152 */ 153 static void 154 mespipe(FILE *fp, char cmd[]) 155 { 156 FILE *nf; 157 sig_t sigint = signal(SIGINT, SIG_IGN); 158 const char *shellcmd; 159 int fd; 160 char tempname[PATHSIZE]; 161 162 (void)snprintf(tempname, sizeof(tempname), 163 "%s/mail.ReXXXXXXXXXX", tmpdir); 164 if ((fd = mkstemp(tempname)) == -1 || 165 (nf = Fdopen(fd, "w+")) == NULL) { 166 if (fd != -1) 167 (void)close(fd); 168 warn("%s", tempname); 169 goto out; 170 } 171 (void)unlink(tempname); 172 /* 173 * stdin = current message. 174 * stdout = new message. 175 */ 176 if ((shellcmd = value(ENAME_SHELL)) == NULL) 177 shellcmd = _PATH_CSHELL; 178 if (run_command(shellcmd, 179 0, fileno(fp), fileno(nf), "-c", cmd, NULL) < 0) { 180 (void)Fclose(nf); 181 goto out; 182 } 183 if (fsize(nf) == 0) { 184 (void)fprintf(stderr, "No bytes from \"%s\" !?\n", cmd); 185 (void)Fclose(nf); 186 goto out; 187 } 188 /* 189 * Take new files. 190 */ 191 (void)fseek(nf, 0L, 2); 192 collf = nf; 193 (void)Fclose(fp); 194 out: 195 (void)signal(SIGINT, sigint); 196 } 197 198 /* 199 * Interpolate the named messages into the current 200 * message, preceding each line with a tab. 201 * Return a count of the number of characters now in 202 * the message, or -1 if an error is encountered writing 203 * the message temporary. The flag argument is 'm' if we 204 * should shift over and 'f' if not. 205 */ 206 static int 207 interpolate(char ms[], FILE *fp, char *fn, int f) 208 { 209 int *msgvec; 210 struct ignoretab *ig; 211 const char *tabst; 212 #ifdef MIME_SUPPORT 213 struct mime_info *mip; 214 int retval; 215 #endif 216 msgvec = salloc((get_msgCount() + 1) * sizeof(*msgvec)); 217 if (msgvec == NULL) 218 return 0; 219 if (getmsglist(ms, msgvec, 0) < 0) 220 return 0; 221 if (*msgvec == 0) { 222 *msgvec = first(0, MMNORM); 223 if (*msgvec == 0) { 224 (void)printf("No appropriate messages\n"); 225 return 0; 226 } 227 msgvec[1] = 0; 228 } 229 if (f == 'f' || f == 'F') 230 tabst = NULL; 231 else if ((tabst = value(ENAME_INDENTPREFIX)) == NULL) 232 tabst = "\t"; 233 ig = isupper(f) ? NULL : ignore; 234 (void)printf("Interpolating:"); 235 for (/*EMPTY*/; *msgvec != 0; msgvec++) { 236 struct message *mp; 237 char *fmtstr; 238 239 mp = get_message(*msgvec); 240 touch(mp); 241 (void)printf(" %d", *msgvec); 242 (void)fflush(stdout); /* flush stdout and the above */ 243 244 if (tabst && (fmtstr = value(ENAME_INDENT_PREAMBLE)) != NULL) 245 fmsgprintf(collf, fmtstr, mp); 246 #ifdef MIME_SUPPORT 247 mip = NULL; 248 if (value(ENAME_MIME_DECODE_MSG)) { 249 if ((tabst == NULL && value(ENAME_MIME_DECODE_INSERT)) || 250 (tabst != NULL && value(ENAME_MIME_DECODE_QUOTE))) 251 mip = mime_decode_open(mp); 252 } 253 retval = mime_sendmessage(mp, fp, ig, tabst, mip); 254 mime_decode_close(mip); 255 if (retval < 0) 256 #else 257 if (sendmessage(mp, fp, ig, tabst, NULL) < 0) 258 #endif 259 { 260 warn("%s", fn); 261 return -1; 262 } 263 if (tabst && (fmtstr = value(ENAME_INDENT_POSTSCRIPT)) != NULL) 264 fmsgprintf(collf, fmtstr, mp); 265 } 266 (void)printf("\n"); 267 return 0; 268 } 269 270 /* 271 * Print (continue) when continued after ^Z. 272 */ 273 /*ARGSUSED*/ 274 static void 275 collstop(int s) 276 { 277 sig_t old_action = signal(s, SIG_DFL); 278 sigset_t nset; 279 280 (void)sigemptyset(&nset); 281 (void)sigaddset(&nset, s); 282 (void)sigprocmask(SIG_UNBLOCK, &nset, NULL); 283 (void)kill(0, s); 284 (void)sigprocmask(SIG_BLOCK, &nset, NULL); 285 (void)signal(s, old_action); 286 if (colljmp_p) { 287 colljmp_p = 0; 288 hadintr = 0; 289 longjmp(colljmp, 1); 290 } 291 } 292 293 /* 294 * Append the contents of the file to the end of the deadletter file. 295 */ 296 PUBLIC void 297 savedeadletter(FILE *fp) 298 { 299 FILE *dbuf; 300 mode_t m; 301 int c; 302 const char *cp; 303 304 if (fsize(fp) == 0) 305 return; 306 cp = getdeadletter(); 307 m = umask(077); 308 dbuf = Fopen(cp, "a"); 309 (void)umask(m); 310 if (dbuf == NULL) 311 return; 312 while ((c = getc(fp)) != EOF) 313 (void)putc(c, dbuf); 314 (void)Fclose(dbuf); 315 rewind(fp); 316 } 317 318 /* 319 * On interrupt, come here to save the partial message in ~/dead.letter. 320 * Then jump out of the collection loop. 321 */ 322 /*ARGSUSED*/ 323 static void 324 collint(int s __unused) 325 { 326 /* 327 * the control flow is subtle, because we can be called from ~q. 328 */ 329 if (!hadintr) { 330 if (value(ENAME_IGNORE) != NULL) { 331 (void)puts("@"); 332 (void)fflush(stdout); 333 clearerr(stdin); 334 return; 335 } 336 hadintr = 1; 337 longjmp(colljmp, 1); 338 } 339 rewind(collf); 340 if (value(ENAME_NOSAVE) == NULL) 341 savedeadletter(collf); 342 longjmp(collabort, 1); 343 } 344 345 /*ARGSUSED*/ 346 static void 347 collhup(int s __unused) 348 { 349 rewind(collf); 350 savedeadletter(collf); 351 /* 352 * Let's pretend nobody else wants to clean up, 353 * a true statement at this time. 354 */ 355 exit(1); 356 } 357 358 PUBLIC FILE * 359 collect(struct header *hp, int printheaders) 360 { 361 FILE *fbuf; 362 int lc, cc; 363 int c, fd, t; 364 char linebuf[LINESIZE]; 365 const char *cp; 366 char tempname[PATHSIZE]; 367 char mailtempname[PATHSIZE]; 368 int lastlong, rc; /* So we don't make 2 or more lines 369 out of a long input line. */ 370 int eofcount; 371 int longline; 372 sigset_t nset; 373 374 /* The following are declared volatile to avoid longjmp clobbering. */ 375 char volatile getsub; 376 int volatile escape; 377 378 (void)memset(mailtempname, 0, sizeof(mailtempname)); 379 collf = NULL; 380 /* 381 * Start catching signals from here, but we're still die on interrupts 382 * until we're in the main loop. 383 */ 384 (void)sigemptyset(&nset); 385 (void)sigaddset(&nset, SIGINT); 386 (void)sigaddset(&nset, SIGHUP); 387 (void)sigprocmask(SIG_BLOCK, &nset, NULL); 388 if ((saveint = signal(SIGINT, SIG_IGN)) != SIG_IGN) 389 (void)signal(SIGINT, collint); 390 if ((savehup = signal(SIGHUP, SIG_IGN)) != SIG_IGN) 391 (void)signal(SIGHUP, collhup); 392 savetstp = signal(SIGTSTP, collstop); 393 savettou = signal(SIGTTOU, collstop); 394 savettin = signal(SIGTTIN, collstop); 395 if (setjmp(collabort) || setjmp(colljmp)) { 396 (void)rm(mailtempname); 397 goto err; 398 } 399 (void)sigprocmask(SIG_UNBLOCK, &nset, NULL); 400 401 noreset++; 402 (void)snprintf(mailtempname, sizeof(mailtempname), 403 "%s/mail.RsXXXXXXXXXX", tmpdir); 404 if ((fd = mkstemp(mailtempname)) == -1 || 405 (collf = Fdopen(fd, "w+")) == NULL) { 406 if (fd != -1) 407 (void)close(fd); 408 warn("%s", mailtempname); 409 goto err; 410 } 411 (void)rm(mailtempname); 412 413 /* 414 * If we are going to prompt for a subject, 415 * refrain from printing a newline after 416 * the headers (since some people mind). 417 */ 418 t = GTO|GSUBJECT|GCC|GNL|GSMOPTS; 419 getsub = 0; 420 if (hp->h_subject == NULL && value(ENAME_INTERACTIVE) != NULL && 421 (value(ENAME_ASK) != NULL || value(ENAME_ASKSUB) != NULL)) 422 t &= ~GNL, getsub++; 423 if (printheaders) { 424 (void)puthead(hp, stdout, t); 425 (void)fflush(stdout); 426 } 427 if ((cp = value(ENAME_ESCAPE)) != NULL) 428 escape = *cp; 429 else 430 escape = ESCAPE; 431 hadintr = 0; /* static - no longjmp problem */ 432 if (!setjmp(colljmp)) { 433 if (getsub) 434 (void)grabh(hp, GSUBJECT); 435 } else { 436 /* 437 * Come here for printing the after-signal message. 438 * Duplicate messages won't be printed because 439 * the write is aborted if we get a SIGTTOU. 440 */ 441 cont: 442 if (hadintr) { 443 (void)fflush(stdout); 444 (void)fprintf(stderr, 445 "\n(Interrupt -- one more to kill letter)\n"); 446 } else { 447 (void)printf("(continue)\n"); 448 (void)fflush(stdout); 449 } 450 } 451 eofcount = 0; /* reset after possible longjmp */ 452 longline = 0; /* reset after possible longjmp */ 453 for (;;) { 454 colljmp_p = 1; 455 c = mail_readline(stdin, linebuf, LINESIZE); 456 colljmp_p = 0; 457 #ifdef USE_EDITLINE 458 if (c < 0) { 459 char *p; 460 if (value(ENAME_INTERACTIVE) != NULL && 461 (p = value(ENAME_IGNOREEOF)) != NULL && 462 ++eofcount < (*p == 0 ? 25 : atoi(p))) { 463 (void)printf("Use \".\" to terminate letter\n"); 464 continue; 465 } 466 break; 467 } 468 #else 469 if (c < 0) { 470 if (value(ENAME_INTERACTIVE) != NULL && 471 value(ENAME_IGNOREEOF) != NULL && ++eofcount < 25) { 472 (void)printf("Use \".\" to terminate letter\n"); 473 continue; 474 } 475 break; 476 } 477 #endif 478 lastlong = longline; 479 longline = c == LINESIZE-1; 480 eofcount = 0; 481 hadintr = 0; 482 if (linebuf[0] == '.' && linebuf[1] == '\0' && 483 value(ENAME_INTERACTIVE) != NULL && !lastlong && 484 (value(ENAME_DOT) != NULL || value(ENAME_IGNOREEOF) != NULL)) 485 break; 486 if (linebuf[0] != escape || value(ENAME_INTERACTIVE) == NULL || 487 lastlong) { 488 if (putline(collf, linebuf, !longline) < 0) 489 goto err; 490 continue; 491 } 492 c = linebuf[1]; 493 switch (c) { 494 default: 495 /* 496 * On double escape, just send the single one. 497 * Otherwise, it's an error. 498 */ 499 if (c == escape) { 500 if (putline(collf, &linebuf[1], !longline) < 0) 501 goto err; 502 else 503 break; 504 } 505 (void)printf("Unknown tilde escape.\n"); 506 break; 507 #ifdef MIME_SUPPORT 508 case '@': 509 hp->h_attach = mime_attach_files(hp->h_attach, &linebuf[2]); 510 break; 511 #endif 512 case 'C': 513 /* 514 * Dump core. 515 */ 516 (void)core(NULL); 517 break; 518 case '!': 519 /* 520 * Shell escape, send the balance of the 521 * line to sh -c. 522 */ 523 (void)shell(&linebuf[2]); 524 break; 525 case ':': 526 case '_': 527 /* 528 * Escape to command mode, but be nice! 529 */ 530 (void)execute(&linebuf[2], ec_composing); 531 goto cont; 532 case '.': 533 /* 534 * Simulate end of file on input. 535 */ 536 goto out; 537 case 'q': 538 /* 539 * Force a quit of sending mail. 540 * Act like an interrupt happened. 541 */ 542 hadintr++; 543 collint(SIGINT); 544 exit(1); 545 /*NOTREACHED*/ 546 547 case 'x': /* exit, do not save in dead.letter */ 548 goto err; 549 550 case 'h': 551 /* 552 * Grab a bunch of headers. 553 */ 554 (void)grabh(hp, GTO|GSUBJECT|GCC|GBCC|GSMOPTS); 555 goto cont; 556 case 't': 557 /* 558 * Add to the To list. 559 */ 560 hp->h_to = cat(hp->h_to, extract(&linebuf[2], GTO)); 561 break; 562 case 's': 563 /* 564 * Set the Subject list. 565 */ 566 cp = skip_WSP(&linebuf[2]); 567 hp->h_subject = savestr(cp); 568 break; 569 case 'c': 570 /* 571 * Add to the CC list. 572 */ 573 hp->h_cc = cat(hp->h_cc, extract(&linebuf[2], GCC)); 574 break; 575 case 'b': 576 /* 577 * Add stuff to blind carbon copies list. 578 */ 579 hp->h_bcc = cat(hp->h_bcc, extract(&linebuf[2], GBCC)); 580 break; 581 case 'i': 582 case 'A': 583 case 'a': 584 /* 585 * Insert named variable in message 586 */ 587 588 switch(c) { 589 case 'i': 590 cp = skip_WSP(&linebuf[2]); 591 break; 592 case 'a': 593 cp = "sign"; 594 break; 595 case 'A': 596 cp = "Sign"; 597 break; 598 default: 599 goto err; 600 } 601 602 if (*cp && (cp = value(cp)) != NULL) { 603 (void)printf("%s\n", cp); 604 if (putline(collf, cp, 1) < 0) 605 goto err; 606 } 607 608 break; 609 610 case 'd': 611 (void)strcpy(linebuf + 2, getdeadletter()); 612 /* FALLTHROUGH */ 613 case 'r': 614 case '<': 615 /* 616 * Invoke a file: 617 * Search for the file name, 618 * then open it and copy the contents to collf. 619 */ 620 cp = skip_WSP(&linebuf[2]); 621 if (*cp == '\0') { 622 (void)printf("Interpolate what file?\n"); 623 break; 624 } 625 626 cp = expand(cp); 627 if (cp == NULL) 628 break; 629 630 if (*cp == '!') { /* insert stdout of command */ 631 const char *shellcmd; 632 int nullfd; 633 int rc2; 634 635 if ((nullfd = open("/dev/null", O_RDONLY, 0)) == -1) { 636 warn("/dev/null"); 637 break; 638 } 639 640 (void)snprintf(tempname, sizeof(tempname), 641 "%s/mail.ReXXXXXXXXXX", tmpdir); 642 if ((fd = mkstemp(tempname)) == -1 || 643 (fbuf = Fdopen(fd, "w+")) == NULL) { 644 if (fd != -1) 645 (void)close(fd); 646 warn("%s", tempname); 647 break; 648 } 649 (void)unlink(tempname); 650 651 if ((shellcmd = value(ENAME_SHELL)) == NULL) 652 shellcmd = _PATH_CSHELL; 653 654 rc2 = run_command(shellcmd, 0, nullfd, fileno(fbuf), "-c", cp + 1, NULL); 655 656 (void)close(nullfd); 657 658 if (rc2 < 0) { 659 (void)Fclose(fbuf); 660 break; 661 } 662 663 if (fsize(fbuf) == 0) { 664 (void)fprintf(stderr, "No bytes from command \"%s\"\n", cp + 1); 665 (void)Fclose(fbuf); 666 break; 667 } 668 669 rewind(fbuf); 670 } 671 else if (isdir(cp)) { 672 (void)printf("%s: Directory\n", cp); 673 break; 674 } 675 else if ((fbuf = Fopen(cp, "r")) == NULL) { 676 warn("%s", cp); 677 break; 678 } 679 (void)printf("\"%s\" ", cp); 680 (void)fflush(stdout); 681 lc = 0; 682 cc = 0; 683 while ((rc = mail_readline(fbuf, linebuf, LINESIZE)) >= 0) { 684 if (rc != LINESIZE-1) lc++; 685 if ((t = putline(collf, linebuf, 686 rc != LINESIZE-1)) < 0) { 687 (void)Fclose(fbuf); 688 goto err; 689 } 690 cc += t; 691 } 692 (void)Fclose(fbuf); 693 (void)printf("%d/%d\n", lc, cc); 694 break; 695 case 'w': 696 /* 697 * Write the message on a file. 698 */ 699 cp = skip_WSP(&linebuf[2]); 700 if (*cp == '\0') { 701 (void)fprintf(stderr, "Write what file!?\n"); 702 break; 703 } 704 if ((cp = expand(cp)) == NULL) 705 break; 706 rewind(collf); 707 (void)exwrite(cp, collf, 1); 708 break; 709 case 'm': 710 case 'M': 711 case 'f': 712 case 'F': 713 /* 714 * Interpolate the named messages, if we 715 * are in receiving mail mode. Does the 716 * standard list processing garbage. 717 * If ~f is given, we don't shift over. 718 */ 719 if (interpolate(linebuf + 2, collf, mailtempname, c) < 0) 720 goto err; 721 goto cont; 722 case '?': 723 cathelp(_PATH_TILDE); 724 break; 725 case 'p': 726 /* 727 * Print out the current state of the 728 * message without altering anything. 729 */ 730 rewind(collf); 731 (void)printf("-------\nMessage contains:\n"); 732 (void)puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL); 733 while ((t = getc(collf)) != EOF) 734 (void)putchar(t); 735 goto cont; 736 case '|': 737 /* 738 * Pipe message through command. 739 * Collect output as new message. 740 */ 741 rewind(collf); 742 mespipe(collf, &linebuf[2]); 743 goto cont; 744 case 'v': 745 case 'e': 746 /* 747 * Edit the current message. 748 * 'e' means to use EDITOR 749 * 'v' means to use VISUAL 750 */ 751 rewind(collf); 752 mesedit(collf, c); 753 goto cont; 754 } 755 } 756 goto out; 757 err: 758 if (collf != NULL) { 759 (void)Fclose(collf); 760 collf = NULL; 761 } 762 out: 763 if (collf != NULL) 764 rewind(collf); 765 noreset--; 766 (void)sigprocmask(SIG_BLOCK, &nset, NULL); 767 (void)signal(SIGINT, saveint); 768 (void)signal(SIGHUP, savehup); 769 (void)signal(SIGTSTP, savetstp); 770 (void)signal(SIGTTOU, savettou); 771 (void)signal(SIGTTIN, savettin); 772 (void)sigprocmask(SIG_UNBLOCK, &nset, NULL); 773 return collf; 774 } 775