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(char **arglist) 269 { 270 FILE *fi; 271 char *cp; 272 273 if ((cp = expand(*arglist)) == NULL) 274 return (1); 275 if ((fi = Fopen(cp, "r")) == NULL) { 276 warn("%s", cp); 277 return (1); 278 } 279 if (ssp >= SSTACK_SIZE - 1) { 280 printf("Too much \"sourcing\" going on.\n"); 281 (void)Fclose(fi); 282 return (1); 283 } 284 sstack[ssp].s_file = input; 285 sstack[ssp].s_cond = cond; 286 sstack[ssp].s_loading = loading; 287 ssp++; 288 loading = 0; 289 cond = CANY; 290 input = fi; 291 sourcing++; 292 return (0); 293 } 294 295 /* 296 * Pop the current input back to the previous level. 297 * Update the "sourcing" flag as appropriate. 298 */ 299 int 300 unstack(void) 301 { 302 if (ssp <= 0) { 303 printf("\"Source\" stack over-pop.\n"); 304 sourcing = 0; 305 return (1); 306 } 307 (void)Fclose(input); 308 if (cond != CANY) 309 printf("Unmatched \"if\"\n"); 310 ssp--; 311 cond = sstack[ssp].s_cond; 312 loading = sstack[ssp].s_loading; 313 input = sstack[ssp].s_file; 314 if (ssp == 0) 315 sourcing = loading; 316 return (0); 317 } 318 319 /* 320 * Touch the indicated file. 321 * This is nifty for the shell. 322 */ 323 void 324 alter(char *name) 325 { 326 struct timespec ts[2]; 327 328 (void)clock_gettime(CLOCK_REALTIME, &ts[0]); 329 ts[0].tv_sec++; 330 ts[1].tv_sec = 0; 331 ts[1].tv_nsec = UTIME_OMIT; 332 (void)utimensat(AT_FDCWD, name, ts, 0); 333 } 334 335 /* 336 * Get sender's name from this message. If the message has 337 * a bunch of arpanet stuff in it, we may have to skin the name 338 * before returning it. 339 */ 340 char * 341 nameof(struct message *mp, int reptype) 342 { 343 char *cp, *cp2; 344 345 cp = skin(name1(mp, reptype)); 346 if (reptype != 0 || charcount(cp, '!') < 2) 347 return (cp); 348 cp2 = strrchr(cp, '!'); 349 cp2--; 350 while (cp2 > cp && *cp2 != '!') 351 cp2--; 352 if (*cp2 == '!') 353 return (cp2 + 1); 354 return (cp); 355 } 356 357 /* 358 * Start of a "comment". 359 * Ignore it. 360 */ 361 char * 362 skip_comment(char *cp) 363 { 364 int nesting = 1; 365 366 for (; nesting > 0 && *cp; cp++) { 367 switch (*cp) { 368 case '\\': 369 if (cp[1]) 370 cp++; 371 break; 372 case '(': 373 nesting++; 374 break; 375 case ')': 376 nesting--; 377 break; 378 } 379 } 380 return (cp); 381 } 382 383 /* 384 * Skin an arpa net address according to the RFC 822 interpretation 385 * of "host-phrase." 386 */ 387 char * 388 skin(char *name) 389 { 390 char *nbuf, *bufend, *cp, *cp2; 391 int c, gotlt, lastsp; 392 393 if (name == NULL) 394 return (NULL); 395 if (strchr(name, '(') == NULL && strchr(name, '<') == NULL 396 && strchr(name, ' ') == NULL) 397 return (name); 398 399 /* We assume that length(input) <= length(output) */ 400 if ((nbuf = malloc(strlen(name) + 1)) == NULL) 401 err(1, "Out of memory"); 402 gotlt = 0; 403 lastsp = 0; 404 bufend = nbuf; 405 for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; ) { 406 switch (c) { 407 case '(': 408 cp = skip_comment(cp); 409 lastsp = 0; 410 break; 411 412 case '"': 413 /* 414 * Start of a "quoted-string". 415 * Copy it in its entirety. 416 */ 417 while ((c = *cp) != '\0') { 418 cp++; 419 if (c == '"') 420 break; 421 if (c != '\\') 422 *cp2++ = c; 423 else if ((c = *cp) != '\0') { 424 *cp2++ = c; 425 cp++; 426 } 427 } 428 lastsp = 0; 429 break; 430 431 case ' ': 432 if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ') 433 cp += 3, *cp2++ = '@'; 434 else 435 if (cp[0] == '@' && cp[1] == ' ') 436 cp += 2, *cp2++ = '@'; 437 else 438 lastsp = 1; 439 break; 440 441 case '<': 442 cp2 = bufend; 443 gotlt++; 444 lastsp = 0; 445 break; 446 447 case '>': 448 if (gotlt) { 449 gotlt = 0; 450 while ((c = *cp) != '\0' && c != ',') { 451 cp++; 452 if (c == '(') 453 cp = skip_comment(cp); 454 else if (c == '"') 455 while ((c = *cp) != '\0') { 456 cp++; 457 if (c == '"') 458 break; 459 if (c == '\\' && *cp != '\0') 460 cp++; 461 } 462 } 463 lastsp = 0; 464 break; 465 } 466 /* FALLTHROUGH */ 467 468 default: 469 if (lastsp) { 470 lastsp = 0; 471 *cp2++ = ' '; 472 } 473 *cp2++ = c; 474 if (c == ',' && !gotlt && 475 (*cp == ' ' || *cp == '"' || *cp == '<')) { 476 *cp2++ = ' '; 477 while (*cp == ' ') 478 cp++; 479 lastsp = 0; 480 bufend = cp2; 481 } 482 } 483 } 484 *cp2 = '\0'; 485 486 if ((cp = realloc(nbuf, strlen(nbuf) + 1)) != NULL) 487 nbuf = cp; 488 return (nbuf); 489 } 490 491 /* 492 * Fetch the sender's name from the passed message. 493 * Reptype can be 494 * 0 -- get sender's name for display purposes 495 * 1 -- get sender's name for reply 496 * 2 -- get sender's name for Reply 497 */ 498 char * 499 name1(struct message *mp, int reptype) 500 { 501 char namebuf[LINESIZE]; 502 char linebuf[LINESIZE]; 503 char *cp, *cp2; 504 FILE *ibuf; 505 int first = 1; 506 507 if ((cp = hfield("from", mp)) != NULL) 508 return (cp); 509 if (reptype == 0 && (cp = hfield("sender", mp)) != NULL) 510 return (cp); 511 ibuf = setinput(mp); 512 namebuf[0] = '\0'; 513 if (readline(ibuf, linebuf, LINESIZE) < 0) 514 return (savestr(namebuf)); 515 newname: 516 for (cp = linebuf; *cp != '\0' && *cp != ' '; cp++) 517 ; 518 for (; *cp == ' ' || *cp == '\t'; cp++) 519 ; 520 for (cp2 = &namebuf[strlen(namebuf)]; 521 *cp != '\0' && *cp != ' ' && *cp != '\t' && 522 cp2 < namebuf + LINESIZE - 1;) 523 *cp2++ = *cp++; 524 *cp2 = '\0'; 525 if (readline(ibuf, linebuf, LINESIZE) < 0) 526 return (savestr(namebuf)); 527 if ((cp = strchr(linebuf, 'F')) == NULL) 528 return (savestr(namebuf)); 529 if (strncmp(cp, "From", 4) != 0) 530 return (savestr(namebuf)); 531 while ((cp = strchr(cp, 'r')) != NULL) { 532 if (strncmp(cp, "remote", 6) == 0) { 533 if ((cp = strchr(cp, 'f')) == NULL) 534 break; 535 if (strncmp(cp, "from", 4) != 0) 536 break; 537 if ((cp = strchr(cp, ' ')) == NULL) 538 break; 539 cp++; 540 if (first) { 541 cp2 = namebuf; 542 first = 0; 543 } else 544 cp2 = strrchr(namebuf, '!') + 1; 545 strlcpy(cp2, cp, sizeof(namebuf) - (cp2 - namebuf) - 1); 546 strcat(namebuf, "!"); 547 goto newname; 548 } 549 cp++; 550 } 551 return (savestr(namebuf)); 552 } 553 554 /* 555 * Count the occurrences of c in str 556 */ 557 int 558 charcount(char *str, int c) 559 { 560 char *cp; 561 int i; 562 563 for (i = 0, cp = str; *cp != '\0'; cp++) 564 if (*cp == c) 565 i++; 566 return (i); 567 } 568 569 /* 570 * See if the given header field is supposed to be ignored. 571 */ 572 int 573 isign(const char *field, struct ignoretab ignore[2]) 574 { 575 char realfld[LINESIZE]; 576 577 if (ignore == ignoreall) 578 return (1); 579 /* 580 * Lower-case the string, so that "Status" and "status" 581 * will hash to the same place. 582 */ 583 istrncpy(realfld, field, sizeof(realfld)); 584 if (ignore[1].i_count > 0) 585 return (!member(realfld, ignore + 1)); 586 else 587 return (member(realfld, ignore)); 588 } 589 590 int 591 member(char *realfield, struct ignoretab *table) 592 { 593 struct ignore *igp; 594 595 for (igp = table->i_head[hash(realfield)]; igp != NULL; igp = igp->i_link) 596 if (*igp->i_field == *realfield && 597 equal(igp->i_field, realfield)) 598 return (1); 599 return (0); 600 } 601