1 /* $NetBSD: cmd2.c,v 1.27 2025/07/09 16:59:54 rillig 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.27 2025/07/09 16:59:54 rillig 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 } 425 (void)printf("Okie dokie"); 426 (void)fflush(stdout); 427 (void)wait_child(pid); 428 if (WCOREDUMP(wait_status)) 429 (void)printf(" -- Core dumped.\n"); 430 else 431 (void)printf(" -- Can't dump core.\n"); 432 return 0; 433 } 434 435 /* 436 * Clobber the stack. 437 */ 438 static void 439 clob1(int n) 440 { 441 char buf[512]; 442 char *cp; 443 444 if (n <= 0) 445 return; 446 for (cp = buf; cp < &buf[512]; *cp++ = (char)0xFF) 447 continue; 448 clob1(n - 1); 449 } 450 451 /* 452 * Clobber as many bytes of stack as the user requests. 453 */ 454 PUBLIC int 455 clobber(void *v) 456 { 457 char **argv; 458 int times; 459 460 argv = v; 461 if (argv[0] == 0) 462 times = 1; 463 else 464 times = (atoi(argv[0]) + 511) / 512; 465 clob1(times); 466 return 0; 467 } 468 469 /* 470 * Compare two names for sorting ignored field list. 471 */ 472 static int 473 igcomp(const void *l, const void *r) 474 { 475 return strcmp(*(const char *const *)l, *(const char *const *)r); 476 } 477 478 /* 479 * Print out all currently retained fields. 480 */ 481 static int 482 igshow(struct ignoretab *tab, const char *which) 483 { 484 int h; 485 struct ignore *igp; 486 char **ap, **ring; 487 488 if (tab->i_count == 0) { 489 (void)printf("No fields currently being %s.\n", which); 490 return 0; 491 } 492 ring = salloc((tab->i_count + 1) * sizeof(char *)); 493 ap = ring; 494 for (h = 0; h < HSHSIZE; h++) 495 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link) 496 *ap++ = igp->i_field; 497 *ap = 0; 498 qsort(ring, tab->i_count, sizeof(char *), igcomp); 499 for (ap = ring; *ap != 0; ap++) 500 (void)printf("%s\n", *ap); 501 return 0; 502 } 503 504 /* 505 * core ignore routine. 506 */ 507 static int 508 ignore1(char *list[], struct ignoretab *tab, const char *which) 509 { 510 char **ap; 511 512 if (*list == NULL) 513 return igshow(tab, which); 514 515 for (ap = list; *ap != 0; ap++) 516 add_ignore(*ap, tab); 517 518 return 0; 519 } 520 521 /* 522 * Add the given header fields to the retained list. 523 * If no arguments, print the current list of retained fields. 524 */ 525 PUBLIC int 526 retfield(void *v) 527 { 528 char **list; 529 530 list = v; 531 return ignore1(list, ignore + 1, "retained"); 532 } 533 534 /* 535 * Add the given header fields to the ignored list. 536 * If no arguments, print the current list of ignored fields. 537 */ 538 PUBLIC int 539 igfield(void *v) 540 { 541 char **list; 542 543 list = v; 544 return ignore1(list, ignore, "ignored"); 545 } 546 547 /* 548 * Add the given header fields to the save retained list. 549 * If no arguments, print the current list of save retained fields. 550 */ 551 PUBLIC int 552 saveretfield(void *v) 553 { 554 char **list; 555 556 list = v; 557 return ignore1(list, saveignore + 1, "retained"); 558 } 559 560 /* 561 * Add the given header fields to the save ignored list. 562 * If no arguments, print the current list of save ignored fields. 563 */ 564 PUBLIC int 565 saveigfield(void *v) 566 { 567 char **list; 568 569 list = v; 570 return ignore1(list, saveignore, "ignored"); 571 } 572 573 #ifdef MIME_SUPPORT 574 575 static char* 576 check_dirname(char *filename) 577 { 578 struct stat sb; 579 char *fname; 580 char canon_buf[MAXPATHLEN]; 581 char *canon_name; 582 583 canon_name = canon_buf; 584 fname = filename; 585 if (fname[0] == '~' && fname[1] == '/') { 586 if (homedir && homedir[0] != '~') 587 (void)easprintf(&fname, "%s/%s", 588 homedir, fname + 2); 589 } 590 if (realpath(fname, canon_name) == NULL) { 591 warn("realpath: %s", filename); 592 canon_name = NULL; 593 goto done; 594 } 595 if (stat(canon_name, &sb) == -1) { 596 warn("stat: %s", canon_name); 597 canon_name = NULL; 598 goto done; 599 } 600 if (!S_ISDIR(sb.st_mode)) { 601 warnx("stat: %s is not a directory", canon_name); 602 canon_name = NULL; 603 goto done; 604 } 605 if (access(canon_name, W_OK|X_OK) == -1) { 606 warnx("access: %s is not writable", canon_name); 607 canon_name = NULL; 608 goto done; 609 } 610 done: 611 if (fname != filename) 612 free(fname); 613 614 return canon_name ? savestr(canon_name) : NULL; 615 } 616 617 struct detach1_core_args_s { 618 struct message *parent; 619 struct ignoretab *igtab; 620 const char *dstdir; 621 }; 622 static int 623 detach1_core(struct message *mp, void *v) 624 { 625 struct mime_info *mip; 626 struct detach1_core_args_s *args; 627 628 args = v; 629 touch(mp); 630 show_msgnum(stdout, mp, args->parent); 631 mip = mime_decode_open(mp); 632 mime_detach_msgnum(mip, sget_msgnum(mp, args->parent)); 633 (void)mime_sendmessage(mp, NULL, args->igtab, args->dstdir, mip); 634 mime_decode_close(mip); 635 return 0; 636 } 637 638 /* 639 * detach attachments. 640 */ 641 static int 642 detach1(void *v, int do_unnamed) 643 { 644 int recursive; 645 int f; 646 int msgCount; 647 int *msgvec; 648 int *ip; 649 char *str; 650 char *dstdir; 651 652 str = v; 653 654 /* 655 * Get the destination directory. 656 */ 657 if ((dstdir = snarf(str, &f, "directory")) == NULL && 658 (dstdir = value(ENAME_MIME_DETACH_DIR)) == NULL && 659 (dstdir = origdir) == NULL) 660 return 1; 661 662 if ((dstdir = check_dirname(dstdir)) == NULL) 663 return 1; 664 665 /* 666 * Setup the message list. 667 */ 668 msgCount = get_msgCount(); 669 msgvec = salloc((msgCount + 2) * sizeof(*msgvec)); 670 if (!f) { 671 *msgvec = first(0, MMNORM); 672 if (*msgvec == 0) { 673 (void)printf("No messages to detach.\n"); 674 return 1; 675 } 676 msgvec[1] = 0; 677 } 678 if (f && getmsglist(str, msgvec, 0) < 0) 679 return 1; 680 681 if (mime_detach_control() != 0) 682 return 1; 683 684 /* 685 * do 'dot' if nothing else was selected. 686 */ 687 if (msgvec[0] == 0 && dot != NULL) { 688 msgvec[0] = get_msgnum(dot); 689 msgvec[1] = 0; 690 } 691 recursive = do_recursion(); 692 for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) { 693 struct detach1_core_args_s args; 694 struct message *mp; 695 696 mp = get_message(*ip); 697 dot = mp; 698 args.parent = recursive ? mp : NULL; 699 args.igtab = do_unnamed ? detachall : ignoreall; 700 args.dstdir = dstdir; 701 (void)thread_recursion(mp, detach1_core, &args); 702 } 703 return 0; 704 } 705 706 /* 707 * detach named attachments. 708 */ 709 PUBLIC int 710 detach(void *v) 711 { 712 713 return detach1(v, 0); 714 } 715 716 /* 717 * detach all attachments. 718 */ 719 PUBLIC int 720 Detach(void *v) 721 { 722 723 return detach1(v, 1); 724 } 725 #endif /* MIME_SUPPORT */ 726