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