1 /* 2 * Copyright (c) 1980, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 4. Neither the name of the University nor the names of its contributors 14 * may be used to endorse or promote products derived from this software 15 * without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #ifndef lint 31 #if 0 32 static char sccsid[] = "@(#)aux.c 8.1 (Berkeley) 6/6/93"; 33 #endif 34 #endif /* not lint */ 35 #include <sys/cdefs.h> 36 __FBSDID("$FreeBSD$"); 37 38 #include <sys/time.h> 39 40 #include "rcv.h" 41 #include "extern.h" 42 43 /* 44 * Mail -- a mail program 45 * 46 * Auxiliary functions. 47 */ 48 49 static char *save2str(char *, char *); 50 51 /* 52 * Return a pointer to a dynamic copy of the argument. 53 */ 54 char * 55 savestr(char *str) 56 { 57 char *new; 58 int size = strlen(str) + 1; 59 60 if ((new = salloc(size)) != NULL) 61 bcopy(str, new, size); 62 return (new); 63 } 64 65 /* 66 * Make a copy of new argument incorporating old one. 67 */ 68 static char * 69 save2str(char *str, char *old) 70 { 71 char *new; 72 int newsize = strlen(str) + 1; 73 int oldsize = old ? strlen(old) + 1 : 0; 74 75 if ((new = salloc(newsize + oldsize)) != NULL) { 76 if (oldsize) { 77 bcopy(old, new, oldsize); 78 new[oldsize - 1] = ' '; 79 } 80 bcopy(str, new + oldsize, newsize); 81 } 82 return (new); 83 } 84 85 /* 86 * Touch the named message by setting its MTOUCH flag. 87 * Touched messages have the effect of not being sent 88 * back to the system mailbox on exit. 89 */ 90 void 91 touch(struct message *mp) 92 { 93 94 mp->m_flag |= MTOUCH; 95 if ((mp->m_flag & MREAD) == 0) 96 mp->m_flag |= MREAD|MSTATUS; 97 } 98 99 /* 100 * Test to see if the passed file name is a directory. 101 * Return true if it is. 102 */ 103 int 104 isdir(char name[]) 105 { 106 struct stat sbuf; 107 108 if (stat(name, &sbuf) < 0) 109 return (0); 110 return (S_ISDIR(sbuf.st_mode)); 111 } 112 113 /* 114 * Count the number of arguments in the given string raw list. 115 */ 116 int 117 argcount(char **argv) 118 { 119 char **ap; 120 121 for (ap = argv; *ap++ != NULL;) 122 ; 123 return (ap - argv - 1); 124 } 125 126 /* 127 * Return the desired header line from the passed message 128 * pointer (or NULL if the desired header field is not available). 129 */ 130 char * 131 hfield(const char *field, struct message *mp) 132 { 133 FILE *ibuf; 134 char linebuf[LINESIZE]; 135 int lc; 136 char *hfield; 137 char *colon, *oldhfield = NULL; 138 139 ibuf = setinput(mp); 140 if ((lc = mp->m_lines - 1) < 0) 141 return (NULL); 142 if (readline(ibuf, linebuf, LINESIZE) < 0) 143 return (NULL); 144 while (lc > 0) { 145 if ((lc = gethfield(ibuf, linebuf, lc, &colon)) < 0) 146 return (oldhfield); 147 if ((hfield = ishfield(linebuf, colon, field)) != NULL) 148 oldhfield = save2str(hfield, oldhfield); 149 } 150 return (oldhfield); 151 } 152 153 /* 154 * Return the next header field found in the given message. 155 * Return >= 0 if something found, < 0 elsewise. 156 * "colon" is set to point to the colon in the header. 157 * Must deal with \ continuations & other such fraud. 158 */ 159 int 160 gethfield(FILE *f, char linebuf[], int rem, char **colon) 161 { 162 char line2[LINESIZE]; 163 char *cp, *cp2; 164 int c; 165 166 for (;;) { 167 if (--rem < 0) 168 return (-1); 169 if ((c = readline(f, linebuf, LINESIZE)) <= 0) 170 return (-1); 171 for (cp = linebuf; isprint((unsigned char)*cp) && *cp != ' ' && *cp != ':'; 172 cp++) 173 ; 174 if (*cp != ':' || cp == linebuf) 175 continue; 176 /* 177 * I guess we got a headline. 178 * Handle wraparounding 179 */ 180 *colon = cp; 181 cp = linebuf + c; 182 for (;;) { 183 while (--cp >= linebuf && (*cp == ' ' || *cp == '\t')) 184 ; 185 cp++; 186 if (rem <= 0) 187 break; 188 ungetc(c = getc(f), f); 189 if (c != ' ' && c != '\t') 190 break; 191 if ((c = readline(f, line2, LINESIZE)) < 0) 192 break; 193 rem--; 194 for (cp2 = line2; *cp2 == ' ' || *cp2 == '\t'; cp2++) 195 ; 196 c -= cp2 - line2; 197 if (cp + c >= linebuf + LINESIZE - 2) 198 break; 199 *cp++ = ' '; 200 bcopy(cp2, cp, c); 201 cp += c; 202 } 203 *cp = 0; 204 return (rem); 205 } 206 /* NOTREACHED */ 207 } 208 209 /* 210 * Check whether the passed line is a header line of 211 * the desired breed. Return the field body, or 0. 212 */ 213 214 char* 215 ishfield(char linebuf[], char *colon, const char *field) 216 { 217 char *cp = colon; 218 219 *cp = 0; 220 if (strcasecmp(linebuf, field) != 0) { 221 *cp = ':'; 222 return (0); 223 } 224 *cp = ':'; 225 for (cp++; *cp == ' ' || *cp == '\t'; cp++) 226 ; 227 return (cp); 228 } 229 230 /* 231 * Copy a string and lowercase the result. 232 * dsize: space left in buffer (including space for NULL) 233 */ 234 void 235 istrncpy(char *dest, const char *src, size_t dsize) 236 { 237 238 strlcpy(dest, src, dsize); 239 for (; *dest; dest++) 240 *dest = tolower((unsigned char)*dest); 241 } 242 243 /* 244 * The following code deals with input stacking to do source 245 * commands. All but the current file pointer are saved on 246 * the stack. 247 */ 248 249 static int ssp; /* Top of file stack */ 250 struct sstack { 251 FILE *s_file; /* File we were in. */ 252 int s_cond; /* Saved state of conditionals */ 253 int s_loading; /* Loading .mailrc, etc. */ 254 }; 255 #define SSTACK_SIZE 64 /* XXX was NOFILE. */ 256 static struct sstack sstack[SSTACK_SIZE]; 257 258 /* 259 * Pushdown current input file and switch to a new one. 260 * Set the global flag "sourcing" so that others will realize 261 * that they are no longer reading from a tty (in all probability). 262 */ 263 int 264 source(char **arglist) 265 { 266 FILE *fi; 267 char *cp; 268 269 if ((cp = expand(*arglist)) == NULL) 270 return (1); 271 if ((fi = Fopen(cp, "r")) == NULL) { 272 warn("%s", cp); 273 return (1); 274 } 275 if (ssp >= SSTACK_SIZE - 1) { 276 printf("Too much \"sourcing\" going on.\n"); 277 (void)Fclose(fi); 278 return (1); 279 } 280 sstack[ssp].s_file = input; 281 sstack[ssp].s_cond = cond; 282 sstack[ssp].s_loading = loading; 283 ssp++; 284 loading = 0; 285 cond = CANY; 286 input = fi; 287 sourcing++; 288 return (0); 289 } 290 291 /* 292 * Pop the current input back to the previous level. 293 * Update the "sourcing" flag as appropriate. 294 */ 295 int 296 unstack(void) 297 { 298 if (ssp <= 0) { 299 printf("\"Source\" stack over-pop.\n"); 300 sourcing = 0; 301 return (1); 302 } 303 (void)Fclose(input); 304 if (cond != CANY) 305 printf("Unmatched \"if\"\n"); 306 ssp--; 307 cond = sstack[ssp].s_cond; 308 loading = sstack[ssp].s_loading; 309 input = sstack[ssp].s_file; 310 if (ssp == 0) 311 sourcing = loading; 312 return (0); 313 } 314 315 /* 316 * Touch the indicated file. 317 * This is nifty for the shell. 318 */ 319 void 320 alter(char *name) 321 { 322 struct stat sb; 323 struct timeval tv[2]; 324 325 if (stat(name, &sb)) 326 return; 327 (void)gettimeofday(&tv[0], NULL); 328 tv[0].tv_sec++; 329 TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtim); 330 (void)utimes(name, tv); 331 } 332 333 /* 334 * Get sender's name from this message. If the message has 335 * a bunch of arpanet stuff in it, we may have to skin the name 336 * before returning it. 337 */ 338 char * 339 nameof(struct message *mp, int reptype) 340 { 341 char *cp, *cp2; 342 343 cp = skin(name1(mp, reptype)); 344 if (reptype != 0 || charcount(cp, '!') < 2) 345 return (cp); 346 cp2 = strrchr(cp, '!'); 347 cp2--; 348 while (cp2 > cp && *cp2 != '!') 349 cp2--; 350 if (*cp2 == '!') 351 return (cp2 + 1); 352 return (cp); 353 } 354 355 /* 356 * Start of a "comment". 357 * Ignore it. 358 */ 359 char * 360 skip_comment(char *cp) 361 { 362 int nesting = 1; 363 364 for (; nesting > 0 && *cp; cp++) { 365 switch (*cp) { 366 case '\\': 367 if (cp[1]) 368 cp++; 369 break; 370 case '(': 371 nesting++; 372 break; 373 case ')': 374 nesting--; 375 break; 376 } 377 } 378 return (cp); 379 } 380 381 /* 382 * Skin an arpa net address according to the RFC 822 interpretation 383 * of "host-phrase." 384 */ 385 char * 386 skin(char *name) 387 { 388 char *nbuf, *bufend, *cp, *cp2; 389 int c, gotlt, lastsp; 390 391 if (name == NULL) 392 return (NULL); 393 if (strchr(name, '(') == NULL && strchr(name, '<') == NULL 394 && strchr(name, ' ') == NULL) 395 return (name); 396 397 /* We assume that length(input) <= length(output) */ 398 if ((nbuf = malloc(strlen(name) + 1)) == NULL) 399 err(1, "Out of memory"); 400 gotlt = 0; 401 lastsp = 0; 402 bufend = nbuf; 403 for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; ) { 404 switch (c) { 405 case '(': 406 cp = skip_comment(cp); 407 lastsp = 0; 408 break; 409 410 case '"': 411 /* 412 * Start of a "quoted-string". 413 * Copy it in its entirety. 414 */ 415 while ((c = *cp) != '\0') { 416 cp++; 417 if (c == '"') 418 break; 419 if (c != '\\') 420 *cp2++ = c; 421 else if ((c = *cp) != '\0') { 422 *cp2++ = c; 423 cp++; 424 } 425 } 426 lastsp = 0; 427 break; 428 429 case ' ': 430 if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ') 431 cp += 3, *cp2++ = '@'; 432 else 433 if (cp[0] == '@' && cp[1] == ' ') 434 cp += 2, *cp2++ = '@'; 435 else 436 lastsp = 1; 437 break; 438 439 case '<': 440 cp2 = bufend; 441 gotlt++; 442 lastsp = 0; 443 break; 444 445 case '>': 446 if (gotlt) { 447 gotlt = 0; 448 while ((c = *cp) != '\0' && c != ',') { 449 cp++; 450 if (c == '(') 451 cp = skip_comment(cp); 452 else if (c == '"') 453 while ((c = *cp) != '\0') { 454 cp++; 455 if (c == '"') 456 break; 457 if (c == '\\' && *cp != '\0') 458 cp++; 459 } 460 } 461 lastsp = 0; 462 break; 463 } 464 /* FALLTHROUGH */ 465 466 default: 467 if (lastsp) { 468 lastsp = 0; 469 *cp2++ = ' '; 470 } 471 *cp2++ = c; 472 if (c == ',' && !gotlt && 473 (*cp == ' ' || *cp == '"' || *cp == '<')) { 474 *cp2++ = ' '; 475 while (*cp == ' ') 476 cp++; 477 lastsp = 0; 478 bufend = cp2; 479 } 480 } 481 } 482 *cp2 = '\0'; 483 484 if ((cp = realloc(nbuf, strlen(nbuf) + 1)) != NULL) 485 nbuf = cp; 486 return (nbuf); 487 } 488 489 /* 490 * Fetch the sender's name from the passed message. 491 * Reptype can be 492 * 0 -- get sender's name for display purposes 493 * 1 -- get sender's name for reply 494 * 2 -- get sender's name for Reply 495 */ 496 char * 497 name1(struct message *mp, int reptype) 498 { 499 char namebuf[LINESIZE]; 500 char linebuf[LINESIZE]; 501 char *cp, *cp2; 502 FILE *ibuf; 503 int first = 1; 504 505 if ((cp = hfield("from", mp)) != NULL) 506 return (cp); 507 if (reptype == 0 && (cp = hfield("sender", mp)) != NULL) 508 return (cp); 509 ibuf = setinput(mp); 510 namebuf[0] = '\0'; 511 if (readline(ibuf, linebuf, LINESIZE) < 0) 512 return (savestr(namebuf)); 513 newname: 514 for (cp = linebuf; *cp != '\0' && *cp != ' '; cp++) 515 ; 516 for (; *cp == ' ' || *cp == '\t'; cp++) 517 ; 518 for (cp2 = &namebuf[strlen(namebuf)]; 519 *cp != '\0' && *cp != ' ' && *cp != '\t' && 520 cp2 < namebuf + LINESIZE - 1;) 521 *cp2++ = *cp++; 522 *cp2 = '\0'; 523 if (readline(ibuf, linebuf, LINESIZE) < 0) 524 return (savestr(namebuf)); 525 if ((cp = strchr(linebuf, 'F')) == NULL) 526 return (savestr(namebuf)); 527 if (strncmp(cp, "From", 4) != 0) 528 return (savestr(namebuf)); 529 while ((cp = strchr(cp, 'r')) != NULL) { 530 if (strncmp(cp, "remote", 6) == 0) { 531 if ((cp = strchr(cp, 'f')) == NULL) 532 break; 533 if (strncmp(cp, "from", 4) != 0) 534 break; 535 if ((cp = strchr(cp, ' ')) == NULL) 536 break; 537 cp++; 538 if (first) { 539 cp2 = namebuf; 540 first = 0; 541 } else 542 cp2 = strrchr(namebuf, '!') + 1; 543 strlcpy(cp2, cp, sizeof(namebuf) - (cp2 - namebuf) - 1); 544 strcat(namebuf, "!"); 545 goto newname; 546 } 547 cp++; 548 } 549 return (savestr(namebuf)); 550 } 551 552 /* 553 * Count the occurrences of c in str 554 */ 555 int 556 charcount(char *str, int c) 557 { 558 char *cp; 559 int i; 560 561 for (i = 0, cp = str; *cp != '\0'; cp++) 562 if (*cp == c) 563 i++; 564 return (i); 565 } 566 567 /* 568 * See if the given header field is supposed to be ignored. 569 */ 570 int 571 isign(const char *field, struct ignoretab ignore[2]) 572 { 573 char realfld[LINESIZE]; 574 575 if (ignore == ignoreall) 576 return (1); 577 /* 578 * Lower-case the string, so that "Status" and "status" 579 * will hash to the same place. 580 */ 581 istrncpy(realfld, field, sizeof(realfld)); 582 if (ignore[1].i_count > 0) 583 return (!member(realfld, ignore + 1)); 584 else 585 return (member(realfld, ignore)); 586 } 587 588 int 589 member(char *realfield, struct ignoretab *table) 590 { 591 struct ignore *igp; 592 593 for (igp = table->i_head[hash(realfield)]; igp != NULL; igp = igp->i_link) 594 if (*igp->i_field == *realfield && 595 equal(igp->i_field, realfield)) 596 return (1); 597 return (0); 598 } 599