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