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