1 /* $NetBSD: cmd2.c,v 1.26 2017/11/09 20:27:50 christos Exp $ */ 2 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/cdefs.h> 33 #ifndef lint 34 #if 0 35 static char sccsid[] = "@(#)cmd2.c 8.1 (Berkeley) 6/6/93"; 36 #else 37 __RCSID("$NetBSD: cmd2.c,v 1.26 2017/11/09 20:27:50 christos Exp $"); 38 #endif 39 #endif /* not lint */ 40 41 #include "rcv.h" 42 #include <util.h> 43 #include "extern.h" 44 #ifdef MIME_SUPPORT 45 #include "mime.h" 46 #endif 47 #include "thread.h" 48 49 /* 50 * Mail -- a mail program 51 * 52 * More user commands. 53 */ 54 55 /* 56 * If any arguments were given, go to the next applicable argument 57 * following dot, otherwise, go to the next applicable message. 58 * If given as first command with no arguments, print first message. 59 */ 60 PUBLIC int 61 next(void *v) 62 { 63 int *msgvec; 64 struct message *mp; 65 int *ip, *ip2; 66 int list[2], mdot; 67 68 msgvec = v; 69 if (*msgvec != 0) { 70 71 /* 72 * If some messages were supplied, find the 73 * first applicable one following dot using 74 * wrap around. 75 */ 76 mdot = get_msgnum(dot); 77 78 /* 79 * Find the first message in the supplied 80 * message list which follows dot. 81 */ 82 83 for (ip = msgvec; *ip != 0; ip++) 84 if (*ip > mdot) 85 break; 86 if (*ip == 0) 87 ip = msgvec; 88 ip2 = ip; 89 do { 90 mp = get_message(*ip2); 91 if ((mp->m_flag & MDELETED) == 0) { 92 dot = mp; 93 goto hitit; 94 } 95 if (*ip2 != 0) 96 ip2++; 97 if (*ip2 == 0) 98 ip2 = msgvec; 99 } while (ip2 != ip); 100 (void)printf("No messages applicable\n"); 101 return 1; 102 } 103 104 /* 105 * If this is the first command, select message 1. 106 * Note that this must exist for us to get here at all. 107 */ 108 109 if (!sawcom) 110 goto hitit; 111 112 /* 113 * Just find the next good message after dot, no 114 * wraparound. 115 */ 116 117 for (mp = next_message(dot); mp; mp = next_message(mp)) 118 if ((mp->m_flag & (MDELETED|MSAVED)) == 0) 119 break; 120 121 if (mp == NULL) { 122 (void)printf("At EOF\n"); 123 return 0; 124 } 125 dot = mp; 126 hitit: 127 /* 128 * Print dot. 129 */ 130 131 list[0] = get_msgnum(dot); 132 list[1] = 0; 133 return type(list); 134 } 135 136 /* 137 * Snarf the file from the end of the command line and 138 * return a pointer to it. If there is no file attached, 139 * just return NULL. Put a null in front of the file 140 * name so that the message list processing won't see it, 141 * unless the file name is the only thing on the line, in 142 * which case, return 0 in the reference flag variable. 143 */ 144 static char * 145 snarf(char linebuf[], int *flag, const char *string) 146 { 147 char *cp; 148 149 *flag = 1; 150 cp = strlen(linebuf) + linebuf - 1; 151 152 /* 153 * Strip away trailing blanks. 154 */ 155 while (cp >= linebuf && isspace((unsigned char)*cp)) 156 cp--; 157 *++cp = '\0'; 158 159 /* 160 * Now search for the beginning of the file name. 161 */ 162 while (cp > linebuf && !isspace((unsigned char)*cp)) 163 cp--; 164 if (*cp == '\0') { 165 (void)printf("No %s specified.\n", string); 166 return NULL; 167 } 168 if (isspace((unsigned char)*cp)) 169 *cp++ = '\0'; 170 else 171 *flag = 0; 172 return cp; 173 } 174 175 struct save1_core_args_s { 176 FILE *obuf; 177 struct ignoretab *igtab; 178 int markmsg; 179 }; 180 static int 181 save1_core(struct message *mp, void *v) 182 { 183 struct save1_core_args_s *args; 184 185 args = v; 186 touch(mp); 187 188 if (sendmessage(mp, args->obuf, args->igtab, NULL, NULL) < 0) 189 return -1; 190 191 if (args->markmsg) 192 mp->m_flag |= MSAVED; 193 194 return 0; 195 } 196 197 /* 198 * Save/copy the indicated messages at the end of the passed file name. 199 * If markmsg is true, mark the message "saved." 200 */ 201 static int 202 save1(char str[], int markmsg, const char *cmd, struct ignoretab *igtab) 203 { 204 int *ip; 205 const char *fn; 206 const char *disp; 207 int f, *msgvec; 208 int msgCount; 209 FILE *obuf; 210 211 msgCount = get_msgCount(); 212 msgvec = salloc((msgCount + 2) * sizeof(*msgvec)); 213 if ((fn = snarf(str, &f, "file")) == NULL) 214 return 1; 215 if (!f) { 216 *msgvec = first(0, MMNORM); 217 if (*msgvec == 0) { 218 (void)printf("No messages to %s.\n", cmd); 219 return 1; 220 } 221 msgvec[1] = 0; 222 } 223 if (f && getmsglist(str, msgvec, 0) < 0) 224 return 1; 225 if ((fn = expand(fn)) == NULL) 226 return 1; 227 (void)printf("\"%s\" ", fn); 228 (void)fflush(stdout); 229 if (access(fn, 0) >= 0) 230 disp = "[Appended]"; 231 else 232 disp = "[New file]"; 233 if ((obuf = Fopen(fn, "aef")) == NULL) { 234 warn(NULL); 235 return 1; 236 } 237 for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) { 238 struct save1_core_args_s args; 239 struct message *mp; 240 241 args.obuf = obuf; 242 args.igtab = igtab; 243 args.markmsg = markmsg; 244 mp = get_message(*ip); 245 if (thread_recursion(mp, save1_core, &args)) { 246 warn("%s", fn); 247 (void)Fclose(obuf); 248 return 1; 249 } 250 } 251 (void)fflush(obuf); 252 if (ferror(obuf)) 253 warn("%s", fn); 254 (void)Fclose(obuf); 255 (void)printf("%s\n", disp); 256 return 0; 257 } 258 259 /* 260 * Save a message in a file. Mark the message as saved 261 * so we can discard when the user quits. 262 */ 263 PUBLIC int 264 save(void *v) 265 { 266 char *str; 267 268 str = v; 269 return save1(str, 1, "save", saveignore); 270 } 271 272 /* 273 * Save a message in a file. Mark the message as saved 274 * so we can discard when the user quits. Save all fields 275 * overriding saveignore and saveretain. 276 */ 277 PUBLIC int 278 Save(void *v) 279 { 280 char *str; 281 282 str = v; 283 return save1(str, 1, "Save", NULL); 284 } 285 286 /* 287 * Copy a message to a file without affected its saved-ness 288 */ 289 PUBLIC int 290 copycmd(void *v) 291 { 292 char *str; 293 294 str = v; 295 return save1(str, 0, "copy", saveignore); 296 } 297 298 /* 299 * Write the indicated messages at the end of the passed 300 * file name, minus header and trailing blank line. 301 */ 302 PUBLIC int 303 swrite(void *v) 304 { 305 char *str; 306 307 str = v; 308 return save1(str, 1, "write", ignoreall); 309 } 310 311 /* 312 * Delete the indicated messages. 313 * Set dot to some nice place afterwards. 314 * Internal interface. 315 */ 316 static int 317 delm(int *msgvec) 318 { 319 struct message *mp; 320 int *ip; 321 int last; 322 323 last = 0; 324 for (ip = msgvec; *ip != 0; ip++) { 325 mp = set_m_flag(*ip, 326 ~(MPRESERVE|MSAVED|MBOX|MDELETED|MTOUCH), MDELETED|MTOUCH); 327 touch(mp); 328 last = *ip; 329 } 330 if (last != 0) { 331 dot = get_message(last); 332 last = first(0, MDELETED); 333 if (last != 0) { 334 dot = get_message(last); 335 return 0; 336 } 337 else { 338 dot = get_message(1); 339 return -1; 340 } 341 } 342 343 /* 344 * Following can't happen -- it keeps lint happy 345 */ 346 return -1; 347 } 348 349 /* 350 * Delete messages. 351 */ 352 PUBLIC int 353 delete(void *v) 354 { 355 int *msgvec; 356 357 msgvec = v; 358 (void)delm(msgvec); 359 return 0; 360 } 361 362 /* 363 * Delete messages, then type the new dot. 364 */ 365 PUBLIC int 366 deltype(void *v) 367 { 368 int *msgvec; 369 int list[2]; 370 int lastdot; 371 372 msgvec = v; 373 lastdot = get_msgnum(dot); 374 if (delm(msgvec) >= 0) { 375 list[0] = get_msgnum(dot); 376 if (list[0] > lastdot) { 377 touch(dot); 378 list[1] = 0; 379 return type(list); 380 } 381 (void)printf("At EOF\n"); 382 } else 383 (void)printf("No more messages\n"); 384 return 0; 385 } 386 387 /* 388 * Undelete the indicated messages. 389 */ 390 PUBLIC int 391 undeletecmd(void *v) 392 { 393 int msgCount; 394 int *msgvec; 395 int *ip; 396 397 msgvec = v; 398 msgCount = get_msgCount(); 399 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) { 400 dot = set_m_flag(*ip, ~MDELETED, 0); 401 touch(dot); 402 dot->m_flag &= ~MDELETED; 403 } 404 return 0; 405 } 406 407 /*************************************************************************/ 408 409 /* 410 * Interactively dump core on "core" 411 */ 412 /*ARGSUSED*/ 413 PUBLIC int 414 core(void *v __unused) 415 { 416 int pid; 417 418 switch (pid = vfork()) { 419 case -1: 420 warn("fork"); 421 return 1; 422 case 0: 423 abort(); 424 _exit(1); 425 } 426 (void)printf("Okie dokie"); 427 (void)fflush(stdout); 428 (void)wait_child(pid); 429 if (WCOREDUMP(wait_status)) 430 (void)printf(" -- Core dumped.\n"); 431 else 432 (void)printf(" -- Can't dump core.\n"); 433 return 0; 434 } 435 436 /* 437 * Clobber the stack. 438 */ 439 static void 440 clob1(int n) 441 { 442 char buf[512]; 443 char *cp; 444 445 if (n <= 0) 446 return; 447 for (cp = buf; cp < &buf[512]; *cp++ = (char)0xFF) 448 continue; 449 clob1(n - 1); 450 } 451 452 /* 453 * Clobber as many bytes of stack as the user requests. 454 */ 455 PUBLIC int 456 clobber(void *v) 457 { 458 char **argv; 459 int times; 460 461 argv = v; 462 if (argv[0] == 0) 463 times = 1; 464 else 465 times = (atoi(argv[0]) + 511) / 512; 466 clob1(times); 467 return 0; 468 } 469 470 /* 471 * Compare two names for sorting ignored field list. 472 */ 473 static int 474 igcomp(const void *l, const void *r) 475 { 476 return strcmp(*(const char *const *)l, *(const char *const *)r); 477 } 478 479 /* 480 * Print out all currently retained fields. 481 */ 482 static int 483 igshow(struct ignoretab *tab, const char *which) 484 { 485 int h; 486 struct ignore *igp; 487 char **ap, **ring; 488 489 if (tab->i_count == 0) { 490 (void)printf("No fields currently being %s.\n", which); 491 return 0; 492 } 493 ring = salloc((tab->i_count + 1) * sizeof(char *)); 494 ap = ring; 495 for (h = 0; h < HSHSIZE; h++) 496 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link) 497 *ap++ = igp->i_field; 498 *ap = 0; 499 qsort(ring, tab->i_count, sizeof(char *), igcomp); 500 for (ap = ring; *ap != 0; ap++) 501 (void)printf("%s\n", *ap); 502 return 0; 503 } 504 505 /* 506 * core ignore routine. 507 */ 508 static int 509 ignore1(char *list[], struct ignoretab *tab, const char *which) 510 { 511 char **ap; 512 513 if (*list == NULL) 514 return igshow(tab, which); 515 516 for (ap = list; *ap != 0; ap++) 517 add_ignore(*ap, tab); 518 519 return 0; 520 } 521 522 /* 523 * Add the given header fields to the retained list. 524 * If no arguments, print the current list of retained fields. 525 */ 526 PUBLIC int 527 retfield(void *v) 528 { 529 char **list; 530 531 list = v; 532 return ignore1(list, ignore + 1, "retained"); 533 } 534 535 /* 536 * Add the given header fields to the ignored list. 537 * If no arguments, print the current list of ignored fields. 538 */ 539 PUBLIC int 540 igfield(void *v) 541 { 542 char **list; 543 544 list = v; 545 return ignore1(list, ignore, "ignored"); 546 } 547 548 /* 549 * Add the given header fields to the save retained list. 550 * If no arguments, print the current list of save retained fields. 551 */ 552 PUBLIC int 553 saveretfield(void *v) 554 { 555 char **list; 556 557 list = v; 558 return ignore1(list, saveignore + 1, "retained"); 559 } 560 561 /* 562 * Add the given header fields to the save ignored list. 563 * If no arguments, print the current list of save ignored fields. 564 */ 565 PUBLIC int 566 saveigfield(void *v) 567 { 568 char **list; 569 570 list = v; 571 return ignore1(list, saveignore, "ignored"); 572 } 573 574 #ifdef MIME_SUPPORT 575 576 static char* 577 check_dirname(char *filename) 578 { 579 struct stat sb; 580 char *fname; 581 char canon_buf[MAXPATHLEN]; 582 char *canon_name; 583 584 canon_name = canon_buf; 585 fname = filename; 586 if (fname[0] == '~' && fname[1] == '/') { 587 if (homedir && homedir[0] != '~') 588 (void)easprintf(&fname, "%s/%s", 589 homedir, fname + 2); 590 } 591 if (realpath(fname, canon_name) == NULL) { 592 warn("realpath: %s", filename); 593 canon_name = NULL; 594 goto done; 595 } 596 if (stat(canon_name, &sb) == -1) { 597 warn("stat: %s", canon_name); 598 canon_name = NULL; 599 goto done; 600 } 601 if (!S_ISDIR(sb.st_mode)) { 602 warnx("stat: %s is not a directory", canon_name); 603 canon_name = NULL; 604 goto done; 605 } 606 if (access(canon_name, W_OK|X_OK) == -1) { 607 warnx("access: %s is not writable", canon_name); 608 canon_name = NULL; 609 goto done; 610 } 611 done: 612 if (fname != filename) 613 free(fname); 614 615 return canon_name ? savestr(canon_name) : NULL; 616 } 617 618 struct detach1_core_args_s { 619 struct message *parent; 620 struct ignoretab *igtab; 621 const char *dstdir; 622 }; 623 static int 624 detach1_core(struct message *mp, void *v) 625 { 626 struct mime_info *mip; 627 struct detach1_core_args_s *args; 628 629 args = v; 630 touch(mp); 631 show_msgnum(stdout, mp, args->parent); 632 mip = mime_decode_open(mp); 633 mime_detach_msgnum(mip, sget_msgnum(mp, args->parent)); 634 (void)mime_sendmessage(mp, NULL, args->igtab, args->dstdir, mip); 635 mime_decode_close(mip); 636 return 0; 637 } 638 639 /* 640 * detach attachments. 641 */ 642 static int 643 detach1(void *v, int do_unnamed) 644 { 645 int recursive; 646 int f; 647 int msgCount; 648 int *msgvec; 649 int *ip; 650 char *str; 651 char *dstdir; 652 653 str = v; 654 655 /* 656 * Get the destination directory. 657 */ 658 if ((dstdir = snarf(str, &f, "directory")) == NULL && 659 (dstdir = value(ENAME_MIME_DETACH_DIR)) == NULL && 660 (dstdir = origdir) == NULL) 661 return 1; 662 663 if ((dstdir = check_dirname(dstdir)) == NULL) 664 return 1; 665 666 /* 667 * Setup the message list. 668 */ 669 msgCount = get_msgCount(); 670 msgvec = salloc((msgCount + 2) * sizeof(*msgvec)); 671 if (!f) { 672 *msgvec = first(0, MMNORM); 673 if (*msgvec == 0) { 674 (void)printf("No messages to detach.\n"); 675 return 1; 676 } 677 msgvec[1] = 0; 678 } 679 if (f && getmsglist(str, msgvec, 0) < 0) 680 return 1; 681 682 if (mime_detach_control() != 0) 683 return 1; 684 685 /* 686 * do 'dot' if nothing else was selected. 687 */ 688 if (msgvec[0] == 0 && dot != NULL) { 689 msgvec[0] = get_msgnum(dot); 690 msgvec[1] = 0; 691 } 692 recursive = do_recursion(); 693 for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) { 694 struct detach1_core_args_s args; 695 struct message *mp; 696 697 mp = get_message(*ip); 698 dot = mp; 699 args.parent = recursive ? mp : NULL; 700 args.igtab = do_unnamed ? detachall : ignoreall; 701 args.dstdir = dstdir; 702 (void)thread_recursion(mp, detach1_core, &args); 703 } 704 return 0; 705 } 706 707 /* 708 * detach named attachments. 709 */ 710 PUBLIC int 711 detach(void *v) 712 { 713 714 return detach1(v, 0); 715 } 716 717 /* 718 * detach all attachments. 719 */ 720 PUBLIC int 721 Detach(void *v) 722 { 723 724 return detach1(v, 1); 725 } 726 #endif /* MIME_SUPPORT */ 727