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