1 /* 2 * Copyright (c) 1983 Eric P. Allman 3 * Copyright (c) 1988 Regents of the University of California. 4 * All rights reserved. 5 * 6 * %sccs.include.redist.c% 7 */ 8 9 #ifndef lint 10 static char sccsid[] = "@(#)headers.c 5.25 (Berkeley) 11/14/92"; 11 #endif /* not lint */ 12 13 # include <sys/param.h> 14 # include <errno.h> 15 # include "sendmail.h" 16 17 /* 18 ** CHOMPHEADER -- process and save a header line. 19 ** 20 ** Called by collect and by readcf to deal with header lines. 21 ** 22 ** Parameters: 23 ** line -- header as a text line. 24 ** def -- if set, this is a default value. 25 ** e -- the envelope including this header. 26 ** 27 ** Returns: 28 ** flags for this header. 29 ** 30 ** Side Effects: 31 ** The header is saved on the header list. 32 ** Contents of 'line' are destroyed. 33 */ 34 35 chompheader(line, def, e) 36 char *line; 37 bool def; 38 register ENVELOPE *e; 39 { 40 register char *p; 41 register HDR *h; 42 HDR **hp; 43 char *fname; 44 char *fvalue; 45 struct hdrinfo *hi; 46 bool cond = FALSE; 47 BITMAP mopts; 48 49 if (tTd(31, 6)) 50 printf("chompheader: %s\n", line); 51 52 /* strip off options */ 53 clrbitmap(mopts); 54 p = line; 55 if (def && *p == '?') 56 { 57 /* have some */ 58 register char *q = strchr(p + 1, *p); 59 60 if (q != NULL) 61 { 62 *q++ = '\0'; 63 while (*++p != '\0') 64 setbitn(*p, mopts); 65 p = q; 66 } 67 else 68 usrerr("chompheader: syntax error, line \"%s\"", line); 69 cond = TRUE; 70 } 71 72 /* find canonical name */ 73 fname = p; 74 p = strchr(p, ':'); 75 if (p == NULL) 76 { 77 syserr("chompheader: syntax error, line \"%s\"", line); 78 return (0); 79 } 80 fvalue = &p[1]; 81 while (isspace(*--p)) 82 continue; 83 *++p = '\0'; 84 makelower(fname); 85 86 /* strip field value on front */ 87 if (*fvalue == ' ') 88 fvalue++; 89 90 /* see if it is a known type */ 91 for (hi = HdrInfo; hi->hi_field != NULL; hi++) 92 { 93 if (strcmp(hi->hi_field, fname) == 0) 94 break; 95 } 96 97 /* see if this is a resent message */ 98 if (!def && bitset(H_RESENT, hi->hi_flags)) 99 e->e_flags |= EF_RESENT; 100 101 /* if this means "end of header" quit now */ 102 if (bitset(H_EOH, hi->hi_flags)) 103 return (hi->hi_flags); 104 105 /* drop explicit From: if same as what we would generate -- for MH */ 106 p = "resent-from"; 107 if (!bitset(EF_RESENT, e->e_flags)) 108 p += 7; 109 if (!def && !QueueRun && strcmp(fname, p) == 0) 110 { 111 if (e->e_from.q_paddr != NULL && 112 strcmp(fvalue, e->e_from.q_paddr) == 0) 113 return (hi->hi_flags); 114 } 115 116 /* delete default value for this header */ 117 for (hp = &e->e_header; (h = *hp) != NULL; hp = &h->h_link) 118 { 119 if (strcmp(fname, h->h_field) == 0 && 120 bitset(H_DEFAULT, h->h_flags) && 121 !bitset(H_FORCE, h->h_flags)) 122 h->h_value = NULL; 123 } 124 125 /* create a new node */ 126 h = (HDR *) xalloc(sizeof *h); 127 h->h_field = newstr(fname); 128 h->h_value = NULL; 129 h->h_link = NULL; 130 bcopy((char *) mopts, (char *) h->h_mflags, sizeof mopts); 131 *hp = h; 132 h->h_flags = hi->hi_flags; 133 if (def) 134 h->h_flags |= H_DEFAULT; 135 if (cond) 136 h->h_flags |= H_CHECK; 137 if (h->h_value != NULL) 138 free((char *) h->h_value); 139 h->h_value = newstr(fvalue); 140 141 /* hack to see if this is a new format message */ 142 if (!def && bitset(H_RCPT|H_FROM, h->h_flags) && 143 (strchr(fvalue, ',') != NULL || strchr(fvalue, '(') != NULL || 144 strchr(fvalue, '<') != NULL || strchr(fvalue, ';') != NULL)) 145 { 146 e->e_flags &= ~EF_OLDSTYLE; 147 } 148 149 return (h->h_flags); 150 } 151 /* 152 ** ADDHEADER -- add a header entry to the end of the queue. 153 ** 154 ** This bypasses the special checking of chompheader. 155 ** 156 ** Parameters: 157 ** field -- the name of the header field. 158 ** value -- the value of the field. It must be lower-cased. 159 ** e -- the envelope to add them to. 160 ** 161 ** Returns: 162 ** none. 163 ** 164 ** Side Effects: 165 ** adds the field on the list of headers for this envelope. 166 */ 167 168 addheader(field, value, e) 169 char *field; 170 char *value; 171 ENVELOPE *e; 172 { 173 register HDR *h; 174 register struct hdrinfo *hi; 175 HDR **hp; 176 177 /* find info struct */ 178 for (hi = HdrInfo; hi->hi_field != NULL; hi++) 179 { 180 if (strcmp(field, hi->hi_field) == 0) 181 break; 182 } 183 184 /* find current place in list -- keep back pointer? */ 185 for (hp = &e->e_header; (h = *hp) != NULL; hp = &h->h_link) 186 { 187 if (strcmp(field, h->h_field) == 0) 188 break; 189 } 190 191 /* allocate space for new header */ 192 h = (HDR *) xalloc(sizeof *h); 193 h->h_field = field; 194 h->h_value = newstr(value); 195 h->h_link = *hp; 196 h->h_flags = hi->hi_flags | H_DEFAULT; 197 clrbitmap(h->h_mflags); 198 *hp = h; 199 } 200 /* 201 ** HVALUE -- return value of a header. 202 ** 203 ** Only "real" fields (i.e., ones that have not been supplied 204 ** as a default) are used. 205 ** 206 ** Parameters: 207 ** field -- the field name. 208 ** e -- the envelope containing the header. 209 ** 210 ** Returns: 211 ** pointer to the value part. 212 ** NULL if not found. 213 ** 214 ** Side Effects: 215 ** none. 216 */ 217 218 char * 219 hvalue(field, e) 220 char *field; 221 register ENVELOPE *e; 222 { 223 register HDR *h; 224 225 for (h = e->e_header; h != NULL; h = h->h_link) 226 { 227 if (!bitset(H_DEFAULT, h->h_flags) && strcmp(h->h_field, field) == 0) 228 return (h->h_value); 229 } 230 return (NULL); 231 } 232 /* 233 ** ISHEADER -- predicate telling if argument is a header. 234 ** 235 ** A line is a header if it has a single word followed by 236 ** optional white space followed by a colon. 237 ** 238 ** Parameters: 239 ** s -- string to check for possible headerness. 240 ** 241 ** Returns: 242 ** TRUE if s is a header. 243 ** FALSE otherwise. 244 ** 245 ** Side Effects: 246 ** none. 247 */ 248 249 bool 250 isheader(s) 251 register char *s; 252 { 253 while (*s > ' ' && *s != ':' && *s != '\0') 254 s++; 255 256 /* following technically violates RFC822 */ 257 while (isspace(*s)) 258 s++; 259 260 return (*s == ':'); 261 } 262 /* 263 ** EATHEADER -- run through the stored header and extract info. 264 ** 265 ** Parameters: 266 ** e -- the envelope to process. 267 ** 268 ** Returns: 269 ** none. 270 ** 271 ** Side Effects: 272 ** Sets a bunch of global variables from information 273 ** in the collected header. 274 ** Aborts the message if the hop count is exceeded. 275 */ 276 277 eatheader(e) 278 register ENVELOPE *e; 279 { 280 register HDR *h; 281 register char *p; 282 int hopcnt = 0; 283 284 if (tTd(32, 1)) 285 printf("----- collected header -----\n"); 286 for (h = e->e_header; h != NULL; h = h->h_link) 287 { 288 extern char *capitalize(); 289 290 if (tTd(32, 1)) 291 printf("%s: %s\n", capitalize(h->h_field), h->h_value); 292 /* count the number of times it has been processed */ 293 if (bitset(H_TRACE, h->h_flags)) 294 hopcnt++; 295 296 /* send to this person if we so desire */ 297 if (GrabTo && bitset(H_RCPT, h->h_flags) && 298 !bitset(H_DEFAULT, h->h_flags) && 299 (!bitset(EF_RESENT, e->e_flags) || bitset(H_RESENT, h->h_flags))) 300 { 301 sendtolist(h->h_value, (ADDRESS *) NULL, 302 &e->e_sendqueue, e); 303 } 304 305 /* log the message-id */ 306 #ifdef LOG 307 if (!QueueRun && LogLevel > 8 && h->h_value != NULL && 308 strcmp(h->h_field, "message-id") == 0) 309 { 310 char buf[MAXNAME]; 311 312 p = h->h_value; 313 if (bitset(H_DEFAULT, h->h_flags)) 314 { 315 expand(p, buf, &buf[sizeof buf], e); 316 p = buf; 317 } 318 syslog(LOG_INFO, "%s: message-id=%s", e->e_id, p); 319 } 320 #endif /* LOG */ 321 } 322 if (tTd(32, 1)) 323 printf("----------------------------\n"); 324 325 /* store hop count */ 326 if (hopcnt > e->e_hopcount) 327 e->e_hopcount = hopcnt; 328 329 /* message priority */ 330 p = hvalue("precedence", e); 331 if (p != NULL) 332 e->e_class = priencode(p); 333 if (!QueueRun) 334 e->e_msgpriority = e->e_msgsize 335 - e->e_class * WkClassFact 336 + e->e_nrcpts * WkRecipFact; 337 338 /* return receipt to */ 339 p = hvalue("return-receipt-to", e); 340 if (p != NULL) 341 e->e_receiptto = p; 342 343 /* errors to */ 344 p = hvalue("errors-to", e); 345 if (p != NULL) 346 sendtolist(p, (ADDRESS *) NULL, &e->e_errorqueue, e); 347 348 /* full name of from person */ 349 p = hvalue("full-name", e); 350 if (p != NULL) 351 define('x', p, e); 352 353 /* date message originated */ 354 p = hvalue("posted-date", e); 355 if (p == NULL) 356 p = hvalue("date", e); 357 if (p != NULL) 358 { 359 define('a', p, e); 360 /* we don't have a good way to do canonical conversion .... 361 define('d', newstr(arpatounix(p)), e); 362 .... so we will ignore the problem for the time being */ 363 } 364 365 /* 366 ** Log collection information. 367 */ 368 369 # ifdef LOG 370 if (!QueueRun && LogLevel > 1) 371 { 372 char hbuf[100]; 373 char *name = hbuf; 374 extern char *inet_ntoa(); 375 376 if (RealHostName == NULL) 377 name = "local"; 378 else if (RealHostName[0] == '[') 379 name = RealHostName; 380 else 381 (void)sprintf(hbuf, "%.80s (%s)", 382 RealHostName, inet_ntoa(RealHostAddr.sin_addr)); 383 syslog(LOG_INFO, 384 "%s: from=%s, size=%ld, class=%d, received from %s\n", 385 e->e_id, e->e_from.q_paddr, e->e_msgsize, 386 e->e_class, name); 387 } 388 # endif /* LOG */ 389 } 390 /* 391 ** PRIENCODE -- encode external priority names into internal values. 392 ** 393 ** Parameters: 394 ** p -- priority in ascii. 395 ** 396 ** Returns: 397 ** priority as a numeric level. 398 ** 399 ** Side Effects: 400 ** none. 401 */ 402 403 priencode(p) 404 char *p; 405 { 406 register int i; 407 408 for (i = 0; i < NumPriorities; i++) 409 { 410 if (!strcasecmp(p, Priorities[i].pri_name)) 411 return (Priorities[i].pri_val); 412 } 413 414 /* unknown priority */ 415 return (0); 416 } 417 /* 418 ** CRACKADDR -- parse an address and turn it into a macro 419 ** 420 ** This doesn't actually parse the address -- it just extracts 421 ** it and replaces it with "$g". The parse is totally ad hoc 422 ** and isn't even guaranteed to leave something syntactically 423 ** identical to what it started with. However, it does leave 424 ** something semantically identical. 425 ** 426 ** This algorithm has been cleaned up to handle a wider range 427 ** of cases -- notably quoted and backslash escaped strings. 428 ** This modification makes it substantially better at preserving 429 ** the original syntax. 430 ** 431 ** Parameters: 432 ** addr -- the address to be cracked. 433 ** 434 ** Returns: 435 ** a pointer to the new version. 436 ** 437 ** Side Effects: 438 ** none. 439 ** 440 ** Warning: 441 ** The return value is saved in local storage and should 442 ** be copied if it is to be reused. 443 */ 444 445 char * 446 crackaddr(addr) 447 register char *addr; 448 { 449 register char *p; 450 register char c; 451 int cmtlev; 452 int realcmtlev; 453 int anglelev, realanglelev; 454 int copylev; 455 bool qmode; 456 bool realqmode; 457 bool skipping; 458 bool putgmac = FALSE; 459 bool quoteit = FALSE; 460 register char *bp; 461 char *buflim; 462 static char buf[MAXNAME]; 463 464 if (tTd(33, 1)) 465 printf("crackaddr(%s)\n", addr); 466 467 /* strip leading spaces */ 468 while (*addr != '\0' && isspace(*addr)) 469 addr++; 470 471 /* 472 ** Start by assuming we have no angle brackets. This will be 473 ** adjusted later if we find them. 474 */ 475 476 bp = buf; 477 buflim = &buf[sizeof buf - 5]; 478 p = addr; 479 copylev = anglelev = realanglelev = cmtlev = realcmtlev = 0; 480 qmode = realqmode = FALSE; 481 482 while ((c = *p++) != '\0') 483 { 484 /* 485 ** If the buffer is overful, go into a special "skipping" 486 ** mode that tries to keep legal syntax but doesn't actually 487 ** output things. 488 */ 489 490 skipping = bp >= buflim; 491 492 if (copylev > 0 && !skipping) 493 *bp++ = c; 494 495 /* check for backslash escapes */ 496 if (c == '\\') 497 { 498 if ((c = *p++) == '\0') 499 { 500 /* too far */ 501 p--; 502 goto putg; 503 } 504 if (copylev > 0 && !skipping) 505 *bp++ = c; 506 goto putg; 507 } 508 509 /* check for quoted strings */ 510 if (c == '"') 511 { 512 qmode = !qmode; 513 if (copylev > 0 && !skipping) 514 realqmode = !realqmode; 515 continue; 516 } 517 if (qmode) 518 goto putg; 519 520 /* check for comments */ 521 if (c == '(') 522 { 523 cmtlev++; 524 525 /* allow space for closing paren */ 526 if (!skipping) 527 { 528 buflim--; 529 realcmtlev++; 530 if (copylev++ <= 0) 531 { 532 *bp++ = ' '; 533 *bp++ = c; 534 } 535 } 536 } 537 if (cmtlev > 0) 538 { 539 if (c == ')') 540 { 541 cmtlev--; 542 copylev--; 543 if (!skipping) 544 { 545 realcmtlev--; 546 buflim++; 547 } 548 } 549 continue; 550 } 551 else if (c == ')') 552 { 553 /* syntax error: unmatched ) */ 554 if (!skipping) 555 bp--; 556 } 557 558 559 /* check for characters that may have to be quoted */ 560 if (strchr(".'@,;:[]", c) != NULL) 561 { 562 /* 563 ** If these occur as the phrase part of a <> 564 ** construct, but are not inside of () or already 565 ** quoted, they will have to be quoted. Note that 566 ** now (but don't actually do the quoting). 567 */ 568 569 if (cmtlev <= 0 && !qmode) 570 quoteit = TRUE; 571 } 572 573 /* check for angle brackets */ 574 if (c == '<') 575 { 576 register char *q; 577 578 /* oops -- have to change our mind */ 579 anglelev++; 580 if (!skipping) 581 realanglelev++; 582 583 bp = buf; 584 if (quoteit) 585 { 586 *bp++ = '"'; 587 588 /* back up over the '<' and any spaces */ 589 --p; 590 while (isspace(*--p)) 591 continue; 592 p++; 593 } 594 for (q = addr; q < p; ) 595 { 596 c = *q++; 597 if (bp < buflim) 598 { 599 if (quoteit && c == '"') 600 *bp++ = '\\'; 601 *bp++ = c; 602 } 603 } 604 if (quoteit) 605 { 606 *bp++ = '"'; 607 while ((c = *p++) != '<') 608 { 609 if (bp < buflim) 610 *bp++ = c; 611 } 612 *bp++ = c; 613 } 614 copylev = 0; 615 putgmac = quoteit = FALSE; 616 continue; 617 } 618 619 if (c == '>') 620 { 621 if (anglelev > 0) 622 { 623 anglelev--; 624 if (!skipping) 625 { 626 realanglelev--; 627 buflim++; 628 } 629 } 630 else if (!skipping) 631 { 632 /* syntax error: unmatched > */ 633 if (copylev > 0) 634 bp--; 635 continue; 636 } 637 if (copylev++ <= 0) 638 *bp++ = c; 639 continue; 640 } 641 642 /* must be a real address character */ 643 putg: 644 if (copylev <= 0 && !putgmac) 645 { 646 *bp++ = '\001'; 647 *bp++ = 'g'; 648 putgmac = TRUE; 649 } 650 } 651 652 /* repair any syntactic damage */ 653 if (realqmode) 654 *bp++ = '"'; 655 while (realcmtlev-- > 0) 656 *bp++ = ')'; 657 while (realanglelev-- > 0) 658 *bp++ = '>'; 659 *bp++ = '\0'; 660 661 if (tTd(33, 1)) 662 printf("crackaddr=>`%s'\n", buf); 663 664 return (buf); 665 } 666 /* 667 ** PUTHEADER -- put the header part of a message from the in-core copy 668 ** 669 ** Parameters: 670 ** fp -- file to put it on. 671 ** m -- mailer to use. 672 ** e -- envelope to use. 673 ** 674 ** Returns: 675 ** none. 676 ** 677 ** Side Effects: 678 ** none. 679 */ 680 681 putheader(fp, m, e) 682 register FILE *fp; 683 register MAILER *m; 684 register ENVELOPE *e; 685 { 686 char buf[MAX(MAXFIELD,BUFSIZ)]; 687 register HDR *h; 688 extern char *arpadate(); 689 extern char *capitalize(); 690 char obuf[MAX(MAXFIELD,MAXLINE)]; 691 692 for (h = e->e_header; h != NULL; h = h->h_link) 693 { 694 register char *p; 695 extern bool bitintersect(); 696 697 if (bitset(H_CHECK|H_ACHECK, h->h_flags) && 698 !bitintersect(h->h_mflags, m->m_flags)) 699 continue; 700 701 /* handle Resent-... headers specially */ 702 if (bitset(H_RESENT, h->h_flags) && !bitset(EF_RESENT, e->e_flags)) 703 continue; 704 705 p = h->h_value; 706 if (bitset(H_DEFAULT, h->h_flags)) 707 { 708 /* macro expand value if generated internally */ 709 expand(p, buf, &buf[sizeof buf], e); 710 p = buf; 711 if (p == NULL || *p == '\0') 712 continue; 713 } 714 715 if (bitset(H_FROM|H_RCPT, h->h_flags)) 716 { 717 /* address field */ 718 bool oldstyle = bitset(EF_OLDSTYLE, e->e_flags); 719 720 if (bitset(H_FROM, h->h_flags)) 721 oldstyle = FALSE; 722 commaize(h, p, fp, oldstyle, m, e); 723 } 724 else 725 { 726 /* vanilla header line */ 727 register char *nlp; 728 729 (void) sprintf(obuf, "%s: ", capitalize(h->h_field)); 730 while ((nlp = strchr(p, '\n')) != NULL) 731 { 732 *nlp = '\0'; 733 (void) strcat(obuf, p); 734 *nlp = '\n'; 735 putline(obuf, fp, m); 736 p = ++nlp; 737 obuf[0] = '\0'; 738 } 739 (void) strcat(obuf, p); 740 putline(obuf, fp, m); 741 } 742 } 743 } 744 /* 745 ** COMMAIZE -- output a header field, making a comma-translated list. 746 ** 747 ** Parameters: 748 ** h -- the header field to output. 749 ** p -- the value to put in it. 750 ** fp -- file to put it to. 751 ** oldstyle -- TRUE if this is an old style header. 752 ** m -- a pointer to the mailer descriptor. If NULL, 753 ** don't transform the name at all. 754 ** e -- the envelope containing the message. 755 ** 756 ** Returns: 757 ** none. 758 ** 759 ** Side Effects: 760 ** outputs "p" to file "fp". 761 */ 762 763 commaize(h, p, fp, oldstyle, m, e) 764 register HDR *h; 765 register char *p; 766 FILE *fp; 767 bool oldstyle; 768 register MAILER *m; 769 register ENVELOPE *e; 770 { 771 register char *obp; 772 int opos; 773 bool firstone = TRUE; 774 char obuf[MAXLINE + 3]; 775 776 /* 777 ** Output the address list translated by the 778 ** mailer and with commas. 779 */ 780 781 if (tTd(14, 2)) 782 printf("commaize(%s: %s)\n", h->h_field, p); 783 784 obp = obuf; 785 (void) sprintf(obp, "%s: ", capitalize(h->h_field)); 786 opos = strlen(h->h_field) + 2; 787 obp += opos; 788 789 /* 790 ** Run through the list of values. 791 */ 792 793 while (*p != '\0') 794 { 795 register char *name; 796 register int c; 797 char savechar; 798 extern char *remotename(); 799 extern char *DelimChar; /* defined in prescan */ 800 801 /* 802 ** Find the end of the name. New style names 803 ** end with a comma, old style names end with 804 ** a space character. However, spaces do not 805 ** necessarily delimit an old-style name -- at 806 ** signs mean keep going. 807 */ 808 809 /* find end of name */ 810 while (isspace(*p) || *p == ',') 811 p++; 812 name = p; 813 for (;;) 814 { 815 char *oldp; 816 char pvpbuf[PSBUFSIZE]; 817 extern bool isatword(); 818 extern char **prescan(); 819 820 (void) prescan(p, oldstyle ? ' ' : ',', pvpbuf); 821 p = DelimChar; 822 823 /* look to see if we have an at sign */ 824 oldp = p; 825 while (*p != '\0' && isspace(*p)) 826 p++; 827 828 if (*p != '@' && !isatword(p)) 829 { 830 p = oldp; 831 break; 832 } 833 p += *p == '@' ? 1 : 2; 834 while (*p != '\0' && isspace(*p)) 835 p++; 836 } 837 /* at the end of one complete name */ 838 839 /* strip off trailing white space */ 840 while (p >= name && (isspace(*p) || *p == ',' || *p == '\0')) 841 p--; 842 if (++p == name) 843 continue; 844 savechar = *p; 845 *p = '\0'; 846 847 /* translate the name to be relative */ 848 name = remotename(name, m, bitset(H_FROM, h->h_flags), FALSE, e); 849 if (*name == '\0') 850 { 851 *p = savechar; 852 continue; 853 } 854 855 /* output the name with nice formatting */ 856 opos += strlen(name); 857 if (!firstone) 858 opos += 2; 859 if (opos > 78 && !firstone) 860 { 861 (void) strcpy(obp, ",\n"); 862 putline(obuf, fp, m); 863 obp = obuf; 864 (void) sprintf(obp, " "); 865 opos = strlen(obp); 866 obp += opos; 867 opos += strlen(name); 868 } 869 else if (!firstone) 870 { 871 (void) sprintf(obp, ", "); 872 obp += 2; 873 } 874 875 /* strip off quote bits as we output */ 876 while ((c = *name++) != '\0' && obp < &obuf[MAXLINE]) 877 { 878 if (bitnset(M_7BITS, m->m_flags)) 879 c &= 0177; 880 *obp++ = c; 881 } 882 firstone = FALSE; 883 *p = savechar; 884 } 885 (void) strcpy(obp, "\n"); 886 putline(obuf, fp, m); 887 } 888 /* 889 ** ISATWORD -- tell if the word we are pointing to is "at". 890 ** 891 ** Parameters: 892 ** p -- word to check. 893 ** 894 ** Returns: 895 ** TRUE -- if p is the word at. 896 ** FALSE -- otherwise. 897 ** 898 ** Side Effects: 899 ** none. 900 */ 901 902 bool 903 isatword(p) 904 register char *p; 905 { 906 extern char lower(); 907 908 if (lower(p[0]) == 'a' && lower(p[1]) == 't' && 909 p[2] != '\0' && isspace(p[2])) 910 return (TRUE); 911 return (FALSE); 912 } 913