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