1/* $NetBSD: cmd2.c,v 1.23 2007/10/27 15:14: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 35static char sccsid[] = "@(#)cmd2.c 8.1 (Berkeley) 6/6/93"; 36#else 37__RCSID("$NetBSD: cmd2.c,v 1.23 2007/10/27 15:14: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 */ 60PUBLIC int 61next(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; 126hitit: 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 */ 144static char * 145snarf(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 175struct save1_core_args_s { 176 FILE *obuf; 177 struct ignoretab *igtab; 178 int markmsg; 179}; 180static int 181save1_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 */ 201static int 202save1(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, "a")) == 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 */ 263PUBLIC int 264save(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 */ 277PUBLIC int 278Save(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 */ 289PUBLIC int 290copycmd(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 */ 302PUBLIC int 303swrite(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 */ 316static int 317delm(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 */ 352PUBLIC int 353delete(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 */ 365PUBLIC int 366deltype(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 */ 390PUBLIC int 391undeletecmd(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*/ 413PUBLIC int 414core(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 */ 439static void 440clob1(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 */ 455PUBLIC int 456clobber(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 */ 473static int 474igcomp(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 */ 482static int 483igshow(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 */ 508static int 509ignore1(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 */ 526PUBLIC int 527retfield(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 */ 539PUBLIC int 540igfield(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 */ 552PUBLIC int 553saveretfield(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 */ 565PUBLIC int 566saveigfield(void *v) 567{ 568 char **list; 569 570 list = v; 571 return ignore1(list, saveignore, "ignored"); 572} 573 574#ifdef MIME_SUPPORT 575 576static char* 577check_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 618struct detach1_core_args_s { 619 struct message *parent; 620 struct ignoretab *igtab; 621 const char *dstdir; 622}; 623static int 624detach1_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 */ 642static int 643detach1(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 */ 710PUBLIC int 711detach(void *v) 712{ 713 714 return detach1(v, 0); 715} 716 717/* 718 * detach all attachments. 719 */ 720PUBLIC int 721Detach(void *v) 722{ 723 724 return detach1(v, 1); 725} 726#endif /* MIME_SUPPORT */ 727