1 # include "sendmail.h" 2 3 static char SccsId[] = "@(#)parseaddr.c 3.27 09/28/81"; 4 5 /* 6 ** PARSE -- Parse an address 7 ** 8 ** Parses an address and breaks it up into three parts: a 9 ** net to transmit the message on, the host to transmit it 10 ** to, and a user on that host. These are loaded into an 11 ** ADDRESS header with the values squirreled away if necessary. 12 ** The "user" part may not be a real user; the process may 13 ** just reoccur on that machine. For example, on a machine 14 ** with an arpanet connection, the address 15 ** csvax.bill@berkeley 16 ** will break up to a "user" of 'csvax.bill' and a host 17 ** of 'berkeley' -- to be transmitted over the arpanet. 18 ** 19 ** Parameters: 20 ** addr -- the address to parse. 21 ** a -- a pointer to the address descriptor buffer. 22 ** If NULL, a header will be created. 23 ** copyf -- determines what shall be copied: 24 ** -1 -- don't copy anything. The printname 25 ** (q_paddr) is just addr, and the 26 ** user & host are allocated internally 27 ** to parse. 28 ** 0 -- copy out the parsed user & host, but 29 ** don't copy the printname. 30 ** +1 -- copy everything. 31 ** 32 ** Returns: 33 ** A pointer to the address descriptor header (`a' if 34 ** `a' is non-NULL). 35 ** NULL on error. 36 ** 37 ** Side Effects: 38 ** none 39 */ 40 41 # define DELIMCHARS "$()<>,;\\\"\r\n" /* word delimiters */ 42 43 ADDRESS * 44 parse(addr, a, copyf) 45 char *addr; 46 register ADDRESS *a; 47 int copyf; 48 { 49 register char **pvp; 50 register struct mailer *m; 51 extern char **prescan(); 52 extern ADDRESS *buildaddr(); 53 54 /* 55 ** Initialize and prescan address. 56 */ 57 58 To = addr; 59 # ifdef DEBUG 60 if (Debug) 61 printf("\n--parse(%s)\n", addr); 62 # endif DEBUG 63 64 pvp = prescan(addr, '\0'); 65 if (pvp == NULL) 66 return (NULL); 67 68 /* 69 ** Apply rewriting rules. 70 */ 71 72 rewrite(pvp, 0); 73 74 /* 75 ** See if we resolved to a real mailer. 76 */ 77 78 if (pvp[0][0] != CANONNET) 79 { 80 setstat(EX_USAGE); 81 usrerr("cannot resolve name"); 82 return (NULL); 83 } 84 85 /* 86 ** Build canonical address from pvp. 87 */ 88 89 a = buildaddr(pvp, a); 90 if (a == NULL) 91 return (NULL); 92 m = Mailer[a->q_mailer]; 93 94 /* 95 ** Make local copies of the host & user and then 96 ** transport them out. 97 */ 98 99 if (copyf > 0) 100 a->q_paddr = newstr(addr); 101 else 102 a->q_paddr = addr; 103 104 if (copyf >= 0) 105 { 106 if (a->q_host != NULL) 107 a->q_host = newstr(a->q_host); 108 else 109 a->q_host = ""; 110 if (a->q_user != a->q_paddr) 111 a->q_user = newstr(a->q_user); 112 } 113 114 /* 115 ** Do UPPER->lower case mapping unless inhibited. 116 */ 117 118 if (!bitset(M_HST_UPPER, m->m_flags)) 119 makelower(a->q_host); 120 if (!bitset(M_USR_UPPER, m->m_flags)) 121 makelower(a->q_user); 122 123 /* 124 ** Compute return value. 125 */ 126 127 # ifdef DEBUG 128 if (Debug) 129 { 130 printf("parse-->"); 131 printaddr(a, FALSE); 132 } 133 # endif DEBUG 134 135 return (a); 136 } 137 /* 138 ** PRESCAN -- Prescan name and make it canonical 139 ** 140 ** Scans a name and turns it into canonical form. This involves 141 ** deleting blanks, comments (in parentheses), and turning the 142 ** word "at" into an at-sign ("@"). The name is copied as this 143 ** is done; it is legal to copy a name onto itself, since this 144 ** process can only make things smaller. 145 ** 146 ** This routine knows about quoted strings and angle brackets. 147 ** 148 ** There are certain subtleties to this routine. The one that 149 ** comes to mind now is that backslashes on the ends of names 150 ** are silently stripped off; this is intentional. The problem 151 ** is that some versions of sndmsg (like at LBL) set the kill 152 ** character to something other than @ when reading addresses; 153 ** so people type "csvax.eric\@berkeley" -- which screws up the 154 ** berknet mailer. 155 ** 156 ** Parameters: 157 ** addr -- the name to chomp. 158 ** delim -- the delimiter for the address, normally 159 ** '\0' or ','; \0 is accepted in any case. 160 ** are moving in place; set buflim to high core. 161 ** 162 ** Returns: 163 ** A pointer to a vector of tokens. 164 ** NULL on error. 165 ** 166 ** Side Effects: 167 ** none. 168 */ 169 170 # define OPER 1 171 # define ATOM 2 172 # define EOTOK 3 173 # define QSTRING 4 174 # define SPACE 5 175 # define DOLLAR 6 176 # define GETONE 7 177 # define MACRO 8 178 179 char ** 180 prescan(addr, delim) 181 char *addr; 182 char delim; 183 { 184 register char *p; 185 static char buf[MAXNAME+MAXATOM]; 186 static char *av[MAXATOM+1]; 187 char **avp; 188 bool bslashmode; 189 int cmntcnt; 190 int brccnt; 191 register char c; 192 char *tok; 193 register char *q; 194 register int state; 195 int nstate; 196 extern char lower(); 197 198 q = buf; 199 bslashmode = FALSE; 200 cmntcnt = brccnt = 0; 201 avp = av; 202 state = OPER; 203 for (p = addr; *p != '\0' && *p != delim; ) 204 { 205 /* read a token */ 206 tok = q; 207 while ((c = *p++) != '\0' && c != delim) 208 { 209 /* chew up special characters */ 210 c &= ~0200; 211 *q = '\0'; 212 if (bslashmode) 213 { 214 c |= 0200; 215 bslashmode = FALSE; 216 } 217 else if (c == '\\') 218 { 219 bslashmode = TRUE; 220 continue; 221 } 222 else if (c == '"') 223 { 224 if (state == QSTRING) 225 state = OPER; 226 else 227 state = QSTRING; 228 break; 229 } 230 231 if (c == '$' && delim == '\t') 232 nstate = DOLLAR; 233 else 234 nstate = toktype(c); 235 switch (state) 236 { 237 case QSTRING: /* in quoted string */ 238 break; 239 240 case ATOM: /* regular atom */ 241 if (nstate != ATOM) 242 { 243 state = EOTOK; 244 p--; 245 } 246 break; 247 248 case GETONE: /* grab one character */ 249 state = OPER; 250 break; 251 252 case EOTOK: /* after atom or q-string */ 253 state = nstate; 254 if (state == SPACE) 255 continue; 256 break; 257 258 case SPACE: /* linear white space */ 259 state = nstate; 260 break; 261 262 case OPER: /* operator */ 263 if (nstate == SPACE) 264 continue; 265 state = nstate; 266 break; 267 268 case DOLLAR: /* $- etc. */ 269 state = OPER; 270 switch (c) 271 { 272 case '$': /* literal $ */ 273 break; 274 275 case '+': /* match anything */ 276 c = MATCHANY; 277 state = GETONE; 278 break; 279 280 case '-': /* match one token */ 281 c = MATCHONE; 282 state = GETONE; 283 break; 284 285 case '=': /* match one token of class */ 286 c = MATCHCLASS; 287 state = GETONE; 288 break; 289 290 case '#': /* canonical net name */ 291 c = CANONNET; 292 break; 293 294 case '@': /* canonical host name */ 295 c = CANONHOST; 296 break; 297 298 case ':': /* canonical user name */ 299 c = CANONUSER; 300 break; 301 302 default: 303 state = MACRO; 304 break; 305 } 306 break; 307 308 default: 309 syserr("prescan: unknown state %d", state); 310 } 311 312 if (state == EOTOK || state == SPACE) 313 break; 314 if (state == DOLLAR) 315 continue; 316 317 /* squirrel it away */ 318 if (q >= &buf[sizeof buf - 5]) 319 { 320 usrerr("Address too long"); 321 return (NULL); 322 } 323 if (state == MACRO) 324 { 325 char mbuf[3]; 326 327 mbuf[0] = '$'; 328 mbuf[1] = c; 329 mbuf[2] = '\0'; 330 (void) expand(mbuf, q, &buf[sizeof buf - 5]); 331 q += strlen(q); 332 state = EOTOK; 333 break; 334 } 335 *q++ = c; 336 337 /* decide whether this represents end of token */ 338 if (state == OPER) 339 break; 340 } 341 if (c == '\0' || c == delim) 342 p--; 343 344 /* new token */ 345 if (tok == q) 346 continue; 347 *q++ = '\0'; 348 349 c = tok[0]; 350 if (c == '(') 351 { 352 cmntcnt++; 353 continue; 354 } 355 else if (c == ')') 356 { 357 if (cmntcnt <= 0) 358 { 359 usrerr("Unbalanced ')'"); 360 return (NULL); 361 } 362 else 363 { 364 cmntcnt--; 365 continue; 366 } 367 } 368 else if (cmntcnt > 0) 369 continue; 370 371 if (avp >= &av[MAXATOM]) 372 { 373 syserr("prescan: too many tokens"); 374 return (NULL); 375 } 376 *avp++ = tok; 377 378 /* we prefer <> specs */ 379 if (c == '<') 380 { 381 if (brccnt < 0) 382 { 383 usrerr("multiple < spec"); 384 return (NULL); 385 } 386 brccnt++; 387 if (brccnt == 1) 388 { 389 /* we prefer using machine readable name */ 390 q = buf; 391 *q = '\0'; 392 avp = av; 393 continue; 394 } 395 } 396 else if (c == '>') 397 { 398 if (brccnt <= 0) 399 { 400 usrerr("Unbalanced `>'"); 401 return (NULL); 402 } 403 else 404 brccnt--; 405 if (brccnt <= 0) 406 { 407 brccnt = -1; 408 continue; 409 } 410 } 411 } 412 *avp = NULL; 413 if (cmntcnt > 0) 414 usrerr("Unbalanced '('"); 415 else if (brccnt > 0) 416 usrerr("Unbalanced '<'"); 417 else if (state == QSTRING) 418 usrerr("Unbalanced '\"'"); 419 else if (av[0] != NULL) 420 return (av); 421 return (NULL); 422 } 423 /* 424 ** TOKTYPE -- return token type 425 ** 426 ** Parameters: 427 ** c -- the character in question. 428 ** 429 ** Returns: 430 ** Its type. 431 ** 432 ** Side Effects: 433 ** none. 434 */ 435 436 toktype(c) 437 register char c; 438 { 439 static char buf[50]; 440 static bool firstime = TRUE; 441 442 if (firstime) 443 { 444 firstime = FALSE; 445 (void) expand("$o", buf, &buf[sizeof buf - 1]); 446 strcat(buf, DELIMCHARS); 447 } 448 if (!isascii(c)) 449 return (ATOM); 450 if (isspace(c)) 451 return (SPACE); 452 if (iscntrl(c) || index(buf, c) != NULL) 453 return (OPER); 454 return (ATOM); 455 } 456 /* 457 ** REWRITE -- apply rewrite rules to token vector. 458 ** 459 ** Parameters: 460 ** pvp -- pointer to token vector. 461 ** 462 ** Returns: 463 ** none. 464 ** 465 ** Side Effects: 466 ** pvp is modified. 467 */ 468 469 struct match 470 { 471 char **firsttok; /* first token matched */ 472 char **lasttok; /* last token matched */ 473 char name; /* name of parameter */ 474 }; 475 476 # define MAXMATCH 8 /* max params per rewrite */ 477 478 479 rewrite(pvp, ruleset) 480 char **pvp; 481 int ruleset; 482 { 483 register char *ap; /* address pointer */ 484 register char *rp; /* rewrite pointer */ 485 register char **avp; /* address vector pointer */ 486 register char **rvp; /* rewrite vector pointer */ 487 struct rewrite *rwr; 488 struct match mlist[MAXMATCH]; 489 char *npvp[MAXATOM+1]; /* temporary space for rebuild */ 490 extern bool sameword(); 491 492 # ifdef DEBUG 493 if (Debug > 9) 494 { 495 printf("rewrite: original pvp:\n"); 496 printav(pvp); 497 } 498 # endif DEBUG 499 500 /* 501 ** Run through the list of rewrite rules, applying 502 ** any that match. 503 */ 504 505 for (rwr = RewriteRules[ruleset]; rwr != NULL; ) 506 { 507 # ifdef DEBUG 508 if (Debug > 10) 509 { 510 printf("-----trying rule:\n"); 511 printav(rwr->r_lhs); 512 } 513 # endif DEBUG 514 515 /* try to match on this rule */ 516 clrmatch(mlist); 517 for (rvp = rwr->r_lhs, avp = pvp; *avp != NULL; ) 518 { 519 ap = *avp; 520 rp = *rvp; 521 522 if (rp == NULL) 523 { 524 /* end-of-pattern before end-of-address */ 525 goto fail; 526 } 527 528 switch (*rp) 529 { 530 register STAB *s; 531 register int class; 532 533 case MATCHONE: 534 /* match exactly one token */ 535 setmatch(mlist, rp[1], avp, avp); 536 break; 537 538 case MATCHANY: 539 /* match any number of tokens */ 540 setmatch(mlist, rp[1], (char **) NULL, avp); 541 break; 542 543 case MATCHCLASS: 544 /* match any token in a class */ 545 class = rp[1]; 546 if (!isalpha(class)) 547 goto fail; 548 if (isupper(class)) 549 class -= 'A'; 550 else 551 class -= 'a'; 552 s = stab(ap, ST_CLASS, ST_FIND); 553 if (s == NULL || (s->s_class & (1 << class)) == 0) 554 goto fail; 555 break; 556 557 default: 558 /* must have exact match */ 559 if (!sameword(rp, ap)) 560 goto fail; 561 break; 562 } 563 564 /* successful match on this token */ 565 avp++; 566 rvp++; 567 continue; 568 569 fail: 570 /* match failed -- back up */ 571 while (--rvp >= rwr->r_lhs) 572 { 573 rp = *rvp; 574 if (*rp == MATCHANY) 575 break; 576 577 /* can't extend match: back up everything */ 578 avp--; 579 580 if (*rp == MATCHONE) 581 { 582 /* undo binding */ 583 setmatch(mlist, rp[1], (char **) NULL, (char **) NULL); 584 } 585 } 586 587 if (rvp < rwr->r_lhs) 588 { 589 /* total failure to match */ 590 break; 591 } 592 } 593 594 /* 595 ** See if we successfully matched 596 */ 597 598 if (rvp >= rwr->r_lhs && *rvp == NULL) 599 { 600 # ifdef DEBUG 601 if (Debug > 10) 602 { 603 printf("-----rule matches:\n"); 604 printav(rwr->r_rhs); 605 } 606 # endif DEBUG 607 608 /* substitute */ 609 for (rvp = rwr->r_rhs, avp = npvp; *rvp != NULL; rvp++) 610 { 611 rp = *rvp; 612 if (*rp == MATCHANY) 613 { 614 register struct match *m; 615 register char **pp; 616 extern struct match *findmatch(); 617 618 m = findmatch(mlist, rp[1]); 619 if (m != NULL) 620 { 621 pp = m->firsttok; 622 do 623 { 624 if (avp >= &npvp[MAXATOM]) 625 { 626 syserr("rewrite: expansion too long"); 627 return; 628 } 629 *avp++ = *pp; 630 } while (pp++ != m->lasttok); 631 } 632 } 633 else 634 { 635 if (avp >= &npvp[MAXATOM]) 636 { 637 syserr("rewrite: expansion too long"); 638 return; 639 } 640 *avp++ = rp; 641 } 642 } 643 *avp++ = NULL; 644 bmove((char *) npvp, (char *) pvp, (avp - npvp) * sizeof *avp); 645 # ifdef DEBUG 646 if (Debug > 3) 647 { 648 char **vp; 649 650 printf("rewritten as `"); 651 for (vp = pvp; *vp != NULL; vp++) 652 { 653 if (vp != pvp) 654 printf("_"); 655 xputs(*vp); 656 } 657 printf("'\n"); 658 } 659 # endif DEBUG 660 if (pvp[0][0] == CANONNET) 661 break; 662 } 663 else 664 { 665 # ifdef DEBUG 666 if (Debug > 10) 667 printf("----- rule fails\n"); 668 # endif DEBUG 669 rwr = rwr->r_next; 670 } 671 } 672 } 673 /* 674 ** SETMATCH -- set parameter value in match vector 675 ** 676 ** Parameters: 677 ** mlist -- list of match values. 678 ** name -- the character name of this parameter. 679 ** first -- the first location of the replacement. 680 ** last -- the last location of the replacement. 681 ** 682 ** If last == NULL, delete this entry. 683 ** If first == NULL, extend this entry (or add it if 684 ** it does not exist). 685 ** 686 ** Returns: 687 ** nothing. 688 ** 689 ** Side Effects: 690 ** munges with mlist. 691 */ 692 693 setmatch(mlist, name, first, last) 694 struct match *mlist; 695 char name; 696 char **first; 697 char **last; 698 { 699 register struct match *m; 700 struct match *nullm = NULL; 701 702 for (m = mlist; m < &mlist[MAXMATCH]; m++) 703 { 704 if (m->name == name) 705 break; 706 if (m->name == '\0') 707 nullm = m; 708 } 709 710 if (m >= &mlist[MAXMATCH]) 711 m = nullm; 712 713 if (last == NULL) 714 { 715 m->name = '\0'; 716 return; 717 } 718 719 if (m->name == '\0') 720 { 721 if (first == NULL) 722 m->firsttok = last; 723 else 724 m->firsttok = first; 725 } 726 m->name = name; 727 m->lasttok = last; 728 } 729 /* 730 ** FINDMATCH -- find match in mlist 731 ** 732 ** Parameters: 733 ** mlist -- list to search. 734 ** name -- name to find. 735 ** 736 ** Returns: 737 ** pointer to match structure. 738 ** NULL if no match. 739 ** 740 ** Side Effects: 741 ** none. 742 */ 743 744 struct match * 745 findmatch(mlist, name) 746 struct match *mlist; 747 char name; 748 { 749 register struct match *m; 750 751 for (m = mlist; m < &mlist[MAXMATCH]; m++) 752 { 753 if (m->name == name) 754 return (m); 755 } 756 757 return (NULL); 758 } 759 /* 760 ** CLRMATCH -- clear match list 761 ** 762 ** Parameters: 763 ** mlist -- list to clear. 764 ** 765 ** Returns: 766 ** none. 767 ** 768 ** Side Effects: 769 ** mlist is cleared. 770 */ 771 772 clrmatch(mlist) 773 struct match *mlist; 774 { 775 register struct match *m; 776 777 for (m = mlist; m < &mlist[MAXMATCH]; m++) 778 m->name = '\0'; 779 } 780 /* 781 ** BUILDADDR -- build address from token vector. 782 ** 783 ** Parameters: 784 ** tv -- token vector. 785 ** a -- pointer to address descriptor to fill. 786 ** If NULL, one will be allocated. 787 ** 788 ** Returns: 789 ** NULL if there was an error. 790 ** 'a' otherwise. 791 ** 792 ** Side Effects: 793 ** fills in 'a' 794 */ 795 796 ADDRESS * 797 buildaddr(tv, a) 798 register char **tv; 799 register ADDRESS *a; 800 { 801 register int i; 802 static char buf[MAXNAME]; 803 struct mailer **mp; 804 register struct mailer *m; 805 806 if (a == NULL) 807 a = (ADDRESS *) xalloc(sizeof *a); 808 a->q_flags = 0; 809 a->q_home = NULL; 810 811 /* figure out what net/mailer to use */ 812 if (**tv != CANONNET) 813 { 814 syserr("buildaddr: no net"); 815 return (NULL); 816 } 817 tv++; 818 if (strcmp(*tv, "error") == 0) 819 { 820 if (**++tv != CANONUSER) 821 syserr("buildaddr: error: no user"); 822 buf[0] = '\0'; 823 while (*++tv != NULL) 824 { 825 if (buf[0] != '\0') 826 strcat(buf, " "); 827 strcat(buf, *tv); 828 } 829 usrerr(buf); 830 return (NULL); 831 } 832 for (mp = Mailer, i = 0; (m = *mp++) != NULL; i++) 833 { 834 if (strcmp(m->m_name, *tv) == 0) 835 break; 836 } 837 if (m == NULL) 838 { 839 syserr("buildaddr: unknown net %s", *tv); 840 return (NULL); 841 } 842 a->q_mailer = i; 843 844 /* figure out what host (if any) */ 845 tv++; 846 if (!bitset(M_LOCAL, m->m_flags)) 847 { 848 if (**tv != CANONHOST) 849 { 850 syserr("buildaddr: no host"); 851 return (NULL); 852 } 853 tv++; 854 a->q_host = *tv; 855 tv++; 856 } 857 else 858 a->q_host = NULL; 859 860 /* figure out the user */ 861 if (**tv != CANONUSER) 862 { 863 syserr("buildaddr: no user"); 864 return (NULL); 865 } 866 cataddr(++tv, buf, sizeof buf); 867 a->q_user = buf; 868 869 return (a); 870 } 871 /* 872 ** CATADDR -- concatenate pieces of addresses (putting in <LWSP> subs) 873 ** 874 ** Parameters: 875 ** pvp -- parameter vector to rebuild. 876 ** buf -- buffer to build the string into. 877 ** sz -- size of buf. 878 ** 879 ** Returns: 880 ** none. 881 ** 882 ** Side Effects: 883 ** Destroys buf. 884 */ 885 886 cataddr(pvp, buf, sz) 887 char **pvp; 888 char *buf; 889 register int sz; 890 { 891 bool oatomtok = FALSE; 892 bool natomtok = FALSE; 893 register int i; 894 register char *p; 895 896 p = buf; 897 sz--; 898 while (*pvp != NULL && (i = strlen(*pvp)) < sz) 899 { 900 natomtok = (toktype(**pvp) == ATOM); 901 if (oatomtok && natomtok) 902 *p++ = SPACESUB; 903 (void) strcpy(p, *pvp); 904 oatomtok = natomtok; 905 p += i; 906 sz -= i; 907 pvp++; 908 } 909 *p = '\0'; 910 } 911 /* 912 ** SAMEADDR -- Determine if two addresses are the same 913 ** 914 ** This is not just a straight comparison -- if the mailer doesn't 915 ** care about the host we just ignore it, etc. 916 ** 917 ** Parameters: 918 ** a, b -- pointers to the internal forms to compare. 919 ** wildflg -- if TRUE, 'a' may have no user specified, 920 ** in which case it is to match anything. 921 ** 922 ** Returns: 923 ** TRUE -- they represent the same mailbox. 924 ** FALSE -- they don't. 925 ** 926 ** Side Effects: 927 ** none. 928 */ 929 930 bool 931 sameaddr(a, b, wildflg) 932 register ADDRESS *a; 933 register ADDRESS *b; 934 bool wildflg; 935 { 936 /* if they don't have the same mailer, forget it */ 937 if (a->q_mailer != b->q_mailer) 938 return (FALSE); 939 940 /* if the user isn't the same, we can drop out */ 941 if ((!wildflg || a->q_user[0] != '\0') && strcmp(a->q_user, b->q_user) != 0) 942 return (FALSE); 943 944 /* if the mailer ignores hosts, we have succeeded! */ 945 if (bitset(M_LOCAL, Mailer[a->q_mailer]->m_flags)) 946 return (TRUE); 947 948 /* otherwise compare hosts (but be careful for NULL ptrs) */ 949 if (a->q_host == NULL || b->q_host == NULL) 950 return (FALSE); 951 if (strcmp(a->q_host, b->q_host) != 0) 952 return (FALSE); 953 954 return (TRUE); 955 } 956 /* 957 ** PRINTADDR -- print address (for debugging) 958 ** 959 ** Parameters: 960 ** a -- the address to print 961 ** follow -- follow the q_next chain. 962 ** 963 ** Returns: 964 ** none. 965 ** 966 ** Side Effects: 967 ** none. 968 */ 969 970 # ifdef DEBUG 971 972 printaddr(a, follow) 973 register ADDRESS *a; 974 bool follow; 975 { 976 while (a != NULL) 977 { 978 printf("%x=", a); 979 (void) fflush(stdout); 980 printf("%s: mailer %d (%s), host `%s', user `%s'\n", a->q_paddr, 981 a->q_mailer, Mailer[a->q_mailer]->m_name, a->q_host, a->q_user); 982 printf("\tnext=%x, flags=%o, rmailer %d\n", a->q_next, 983 a->q_flags, a->q_rmailer); 984 985 if (!follow) 986 return; 987 a = a->q_next; 988 } 989 if (!follow) 990 printf("[NULL]\n"); 991 } 992 993 # endif DEBUG 994