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