cmd3.c revision 1.30
1/* $NetBSD: cmd3.c,v 1.30 2006/09/19 20:31:49 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[] = "@(#)cmd3.c 8.2 (Berkeley) 4/20/95"; 36#else 37__RCSID("$NetBSD: cmd3.c,v 1.30 2006/09/19 20:31:49 christos Exp $"); 38#endif 39#endif /* not lint */ 40 41#include "rcv.h" 42#include "extern.h" 43 44/* 45 * Mail -- a mail program 46 * 47 * Still more user commands. 48 */ 49static int delgroup(const char *); 50static int diction(const void *, const void *); 51 52/* 53 * Process a shell escape by saving signals, ignoring signals, 54 * and forking a sh -c 55 */ 56int 57shell(void *v) 58{ 59 char *str = v; 60 sig_t sigint = signal(SIGINT, SIG_IGN); 61 const char *shellcmd; 62 char cmd[BUFSIZ]; 63 64 (void)strcpy(cmd, str); 65 if (bangexp(cmd) < 0) 66 return 1; 67 if ((shellcmd = value("SHELL")) == NULL) 68 shellcmd = _PATH_CSHELL; 69 (void)run_command(shellcmd, 0, 0, 1, "-c", cmd, NULL); 70 (void)signal(SIGINT, sigint); 71 (void)printf("!\n"); 72 return 0; 73} 74 75/* 76 * Fork an interactive shell. 77 */ 78/*ARGSUSED*/ 79int 80dosh(void *v) 81{ 82 sig_t sigint = signal(SIGINT, SIG_IGN); 83 const char *shellcmd; 84 85 if ((shellcmd = value("SHELL")) == NULL) 86 shellcmd = _PATH_CSHELL; 87 (void)run_command(shellcmd, 0, 0, 1, NULL); 88 (void)signal(SIGINT, sigint); 89 (void)putchar('\n'); 90 return 0; 91} 92 93/* 94 * Expand the shell escape by expanding unescaped !'s into the 95 * last issued command where possible. 96 */ 97 98char lastbang[128]; 99 100int 101bangexp(char *str) 102{ 103 char bangbuf[BUFSIZ]; 104 char *cp, *cp2; 105 int n; 106 int changed = 0; 107 108 cp = str; 109 cp2 = bangbuf; 110 n = BUFSIZ; 111 while (*cp) { 112 if (*cp == '!') { 113 if (n < strlen(lastbang)) { 114overf: 115 (void)printf("Command buffer overflow\n"); 116 return(-1); 117 } 118 changed++; 119 (void)strcpy(cp2, lastbang); 120 cp2 += strlen(lastbang); 121 n -= strlen(lastbang); 122 cp++; 123 continue; 124 } 125 if (*cp == '\\' && cp[1] == '!') { 126 if (--n <= 1) 127 goto overf; 128 *cp2++ = '!'; 129 cp += 2; 130 changed++; 131 } 132 if (--n <= 1) 133 goto overf; 134 *cp2++ = *cp++; 135 } 136 *cp2 = 0; 137 if (changed) { 138 (void)printf("!%s\n", bangbuf); 139 (void)fflush(stdout); 140 } 141 (void)strcpy(str, bangbuf); 142 (void)strncpy(lastbang, bangbuf, 128); 143 lastbang[127] = 0; 144 return(0); 145} 146 147/* 148 * Print out a nice help message from some file or another. 149 */ 150 151int 152/*ARGSUSED*/ 153help(void *v) 154{ 155 int c; 156 FILE *f; 157 158 if ((f = Fopen(_PATH_HELP, "r")) == NULL) { 159 warn(_PATH_HELP); 160 return(1); 161 } 162 while ((c = getc(f)) != EOF) 163 (void)putchar(c); 164 (void)Fclose(f); 165 return(0); 166} 167 168/* 169 * Change user's working directory. 170 */ 171int 172schdir(void *v) 173{ 174 char **arglist = v; 175 const char *cp; 176 177 if (*arglist == NULL) 178 cp = homedir; 179 else 180 if ((cp = expand(*arglist)) == NULL) 181 return(1); 182 if (chdir(cp) < 0) { 183 warn("%s", cp); 184 return(1); 185 } 186 return 0; 187} 188 189int 190respond(void *v) 191{ 192 int *msgvec = v; 193 if (value("Replyall") == NULL) 194 return (_respond(msgvec)); 195 else 196 return (_Respond(msgvec)); 197} 198 199static struct name * 200set_smopts(struct message *mp) 201{ 202 char *cp; 203 struct name *np = NULL; 204 char *reply_as_recipient = value("ReplyAsRecipient"); 205 206 if (reply_as_recipient && 207 (cp = skin(hfield("to", mp))) != NULL && 208 extract(cp, GTO)->n_flink == NULL) { /* check for one recipient */ 209 char *p, *q; 210 int len = strlen(cp); 211 for (p = q = reply_as_recipient ; *p ; p = q) { 212 while (*q != '\0' && *q != ',' && *q != ' ' && *q != '\t') 213 q++; 214 if (p + len == q && strncmp(cp, p, len) == 0) 215 return np; 216 while (*q == ',' || *q == ' ' || *q == '\t') 217 q++; 218 } 219 np = extract(__UNCONST("-f"), GSMOPTS); 220 np = cat(np, extract(cp, GSMOPTS)); 221 } 222 223 return np; 224} 225 226/* 227 * Reply to a list of messages. Extract each name from the 228 * message header and send them off to mail1() 229 */ 230int 231_respond(int *msgvec) 232{ 233 struct message *mp; 234 char *cp, *rcv, *replyto; 235 char **ap; 236 struct name *np; 237 struct header head; 238 239 if (msgvec[1] != 0) { 240 (void)printf("Sorry, can't reply to multiple messages at once\n"); 241 return(1); 242 } 243 mp = &message[msgvec[0] - 1]; 244 touch(mp); 245 dot = mp; 246 if ((rcv = skin(hfield("from", mp))) == NULL) 247 rcv = skin(nameof(mp, 1)); 248 if ((replyto = skin(hfield("reply-to", mp))) != NULL) 249 np = extract(replyto, GTO); 250 else if ((cp = skin(hfield("to", mp))) != NULL) 251 np = extract(cp, GTO); 252 else 253 np = NULL; 254 np = elide(np); 255 /* 256 * Delete my name from the reply list, 257 * and with it, all my alternate names. 258 */ 259 np = delname(np, myname); 260 if (altnames) 261 for (ap = altnames; *ap; ap++) 262 np = delname(np, *ap); 263 if (np != NULL && replyto == NULL) 264 np = cat(np, extract(rcv, GTO)); 265 else if (np == NULL) { 266 if (replyto != NULL) 267 (void)printf("Empty reply-to field -- replying to author\n"); 268 np = extract(rcv, GTO); 269 } 270 head.h_to = np; 271 if ((head.h_subject = hfield("subject", mp)) == NULL) 272 head.h_subject = hfield("subj", mp); 273 head.h_subject = reedit(head.h_subject); 274 if (replyto == NULL && (cp = skin(hfield("cc", mp))) != NULL) { 275 np = elide(extract(cp, GCC)); 276 np = delname(np, myname); 277 if (altnames != 0) 278 for (ap = altnames; *ap; ap++) 279 np = delname(np, *ap); 280 head.h_cc = np; 281 } else 282 head.h_cc = NULL; 283 head.h_bcc = NULL; 284 head.h_smopts = set_smopts(mp); 285 mail1(&head, 1); 286 return(0); 287} 288 289/* 290 * Modify the subject we are replying to to begin with Re: if 291 * it does not already. 292 */ 293char * 294reedit(char *subj) 295{ 296 char *newsubj; 297 298 if (subj == NULL) 299 return NULL; 300 if ((subj[0] == 'r' || subj[0] == 'R') && 301 (subj[1] == 'e' || subj[1] == 'E') && 302 subj[2] == ':') 303 return subj; 304 newsubj = salloc(strlen(subj) + 5); 305 (void)strcpy(newsubj, "Re: "); 306 (void)strcpy(newsubj + 4, subj); 307 return newsubj; 308} 309 310/* 311 * Preserve the named messages, so that they will be sent 312 * back to the system mailbox. 313 */ 314int 315preserve(void *v) 316{ 317 int *msgvec = v; 318 struct message *mp; 319 int *ip, mesg; 320 321 if (edit) { 322 (void)printf("Cannot \"preserve\" in edit mode\n"); 323 return(1); 324 } 325 for (ip = msgvec; *ip != 0; ip++) { 326 mesg = *ip; 327 mp = &message[mesg-1]; 328 mp->m_flag |= MPRESERVE; 329 mp->m_flag &= ~MBOX; 330 dot = mp; 331 } 332 return(0); 333} 334 335/* 336 * Mark all given messages as unread. 337 */ 338int 339unread(void *v) 340{ 341 int *msgvec = v; 342 int *ip; 343 344 for (ip = msgvec; *ip != 0; ip++) { 345 dot = &message[*ip-1]; 346 dot->m_flag &= ~(MREAD|MTOUCH); 347 dot->m_flag |= MSTATUS; 348 } 349 return(0); 350} 351 352/* 353 * Print the size of each message. 354 */ 355int 356messize(void *v) 357{ 358 int *msgvec = v; 359 struct message *mp; 360 int *ip, mesg; 361 362 for (ip = msgvec; *ip != 0; ip++) { 363 mesg = *ip; 364 mp = &message[mesg-1]; 365 (void)printf("%d: %ld/%llu\n", mesg, mp->m_blines, 366 /*LINTED*/ 367 (unsigned long long)mp->m_size); 368 } 369 return(0); 370} 371 372/* 373 * Quit quickly. If we are sourcing, just pop the input level 374 * by returning an error. 375 */ 376int 377/*ARGSUSED*/ 378rexit(void *v) 379{ 380 if (sourcing) 381 return(1); 382 exit(0); 383 /*NOTREACHED*/ 384} 385 386/* 387 * Set or display a variable value. Syntax is similar to that 388 * of csh. 389 */ 390int 391set(void *v) 392{ 393 char **arglist = v; 394 struct var *vp; 395 const char *cp; 396 char varbuf[BUFSIZ], **ap, **p; 397 int errs, h, s; 398 size_t l; 399 400 if (*arglist == NULL) { 401 for (h = 0, s = 1; h < HSHSIZE; h++) 402 for (vp = variables[h]; vp != NULL; vp = vp->v_link) 403 s++; 404 ap = salloc(s * sizeof *ap); 405 for (h = 0, p = ap; h < HSHSIZE; h++) 406 for (vp = variables[h]; vp != NULL; vp = vp->v_link) 407 *p++ = vp->v_name; 408 *p = NULL; 409 sort(ap); 410 for (p = ap; *p != NULL; p++) 411 (void)printf("%s\t%s\n", *p, value(*p)); 412 return(0); 413 } 414 errs = 0; 415 for (ap = arglist; *ap != NULL; ap++) { 416 cp = *ap; 417 while (*cp != '=' && *cp != '\0') 418 ++cp; 419 l = cp - *ap; 420 if (l >= sizeof varbuf) 421 l = sizeof varbuf - 1; 422 (void)strncpy(varbuf, *ap, l); 423 varbuf[l] = '\0'; 424 if (*cp == '\0') 425 cp = ""; 426 else 427 cp++; 428 if (equal(varbuf, "")) { 429 (void)printf("Non-null variable name required\n"); 430 errs++; 431 continue; 432 } 433 assign(varbuf, cp); 434 } 435 return(errs); 436} 437 438/* 439 * Unset a bunch of variable values. 440 */ 441int 442unset(void *v) 443{ 444 char **arglist = v; 445 struct var *vp, *vp2; 446 int errs, h; 447 char **ap; 448 449 errs = 0; 450 for (ap = arglist; *ap != NULL; ap++) { 451 if ((vp2 = lookup(*ap)) == NULL) { 452 if (getenv(*ap)) { 453 (void)unsetenv(*ap); 454 } else if (!sourcing) { 455 (void)printf("\"%s\": undefined variable\n", *ap); 456 errs++; 457 } 458 continue; 459 } 460 h = hash(*ap); 461 if (vp2 == variables[h]) { 462 variables[h] = variables[h]->v_link; 463 v_free(vp2->v_name); 464 v_free(vp2->v_value); 465 free(vp2); 466 continue; 467 } 468 for (vp = variables[h]; vp->v_link != vp2; vp = vp->v_link) 469 ; 470 vp->v_link = vp2->v_link; 471 v_free(vp2->v_name); 472 v_free(vp2->v_value); 473 free(vp2); 474 } 475 return(errs); 476} 477 478/* 479 * Show a variable value. 480 */ 481int 482show(void *v) 483{ 484 char **arglist = v; 485 struct var *vp; 486 char **ap, **p; 487 int h, s; 488 489 if (*arglist == NULL) { 490 for (h = 0, s = 1; h < HSHSIZE; h++) 491 for (vp = variables[h]; vp != NULL; vp = vp->v_link) 492 s++; 493 ap = salloc(s * sizeof *ap); 494 for (h = 0, p = ap; h < HSHSIZE; h++) 495 for (vp = variables[h]; vp != NULL; vp = vp->v_link) 496 *p++ = vp->v_name; 497 *p = NULL; 498 sort(ap); 499 for (p = ap; *p != NULL; p++) 500 (void)printf("%s=%s\n", *p, value(*p)); 501 return(0); 502 } 503 504 for (ap = arglist; *ap != NULL; ap++) { 505 char *val = value(*ap); 506 (void)printf("%s=%s\n", *ap, val ? val : "<null>"); 507 } 508 return 0; 509} 510 511/* 512 * Put add users to a group. 513 */ 514int 515group(void *v) 516{ 517 char **argv = v; 518 struct grouphead *gh; 519 struct group *gp; 520 int h; 521 int s; 522 char **ap, *gname, **p; 523 524 if (*argv == NULL) { 525 for (h = 0, s = 1; h < HSHSIZE; h++) 526 for (gh = groups[h]; gh != NULL; gh = gh->g_link) 527 s++; 528 ap = salloc(s * sizeof *ap); 529 for (h = 0, p = ap; h < HSHSIZE; h++) 530 for (gh = groups[h]; gh != NULL; gh = gh->g_link) 531 *p++ = gh->g_name; 532 *p = NULL; 533 sort(ap); 534 for (p = ap; *p != NULL; p++) 535 printgroup(*p); 536 return(0); 537 } 538 if (argv[1] == NULL) { 539 printgroup(*argv); 540 return(0); 541 } 542 gname = *argv; 543 h = hash(gname); 544 if ((gh = findgroup(gname)) == NULL) { 545 gh = (struct grouphead *) calloc(1, sizeof *gh); 546 gh->g_name = vcopy(gname); 547 gh->g_list = NULL; 548 gh->g_link = groups[h]; 549 groups[h] = gh; 550 } 551 552 /* 553 * Insert names from the command list into the group. 554 * Who cares if there are duplicates? They get tossed 555 * later anyway. 556 */ 557 558 for (ap = argv+1; *ap != NULL; ap++) { 559 gp = (struct group *) calloc(1, sizeof *gp); 560 gp->ge_name = vcopy(*ap); 561 gp->ge_link = gh->g_list; 562 gh->g_list = gp; 563 } 564 return(0); 565} 566 567/* 568 * The unalias command takes a list of alises 569 * and discards the remembered groups of users. 570 */ 571int 572unalias(void *v) 573{ 574 char **ap; 575 576 for (ap = v; *ap != NULL; ap++) 577 (void)delgroup(*ap); 578 return 0; 579} 580 581/* 582 * Delete the named group alias. Return zero if the group was 583 * successfully deleted, or -1 if there was no such group. 584 */ 585static int 586delgroup(const char *name) 587{ 588 struct grouphead *gh, *p; 589 struct group *g; 590 int h; 591 592 h = hash(name); 593 for (gh = groups[h], p = NULL; gh != NULL; p = gh, gh = gh->g_link) 594 if (strcmp(gh->g_name, name) == 0) { 595 if (p == NULL) 596 groups[h] = gh->g_link; 597 else 598 p->g_link = gh->g_link; 599 while (gh->g_list != NULL) { 600 g = gh->g_list; 601 gh->g_list = g->ge_link; 602 free(g->ge_name); 603 free(g); 604 } 605 free(gh->g_name); 606 free(gh); 607 return 0; 608 } 609 return -1; 610} 611 612/* 613 * Sort the passed string vecotor into ascending dictionary 614 * order. 615 */ 616void 617sort(char **list) 618{ 619 char **ap; 620 621 for (ap = list; *ap != NULL; ap++) 622 ; 623 if (ap-list < 2) 624 return; 625 qsort(list, (size_t)(ap-list), sizeof(*list), diction); 626} 627 628/* 629 * Do a dictionary order comparison of the arguments from 630 * qsort. 631 */ 632static int 633diction(const void *a, const void *b) 634{ 635 return(strcmp(*(const char *const *)a, *(const char *const *)b)); 636} 637 638/* 639 * The do nothing command for comments. 640 */ 641 642/*ARGSUSED*/ 643int 644null(void *v) 645{ 646 return 0; 647} 648 649/* 650 * Change to another file. With no argument, print information about 651 * the current file. 652 */ 653int 654file(void *v) 655{ 656 char **argv = v; 657 658 if (argv[0] == NULL) { 659 (void)newfileinfo(0); 660 return 0; 661 } 662 if (setfile(*argv) < 0) 663 return 1; 664 announce(); 665 return 0; 666} 667 668/* 669 * Expand file names like echo 670 */ 671int 672echo(void *v) 673{ 674 char **argv = v; 675 char **ap; 676 const char *cp; 677 678 for (ap = argv; *ap != NULL; ap++) { 679 cp = *ap; 680 if ((cp = expand(cp)) != NULL) { 681 if (ap != argv) 682 (void)putchar(' '); 683 (void)printf("%s", cp); 684 } 685 } 686 (void)putchar('\n'); 687 return 0; 688} 689 690int 691Respond(void *v) 692{ 693 int *msgvec = v; 694 if (value("Replyall") == NULL) 695 return (_Respond(msgvec)); 696 else 697 return (_respond(msgvec)); 698} 699 700/* 701 * Reply to a series of messages by simply mailing to the senders 702 * and not messing around with the To: and Cc: lists as in normal 703 * reply. 704 */ 705int 706_Respond(int msgvec[]) 707{ 708 struct header head; 709 struct message *mp; 710 int *ap; 711 char *cp; 712 713 head.h_to = NULL; 714 for (ap = msgvec; *ap != 0; ap++) { 715 mp = &message[*ap - 1]; 716 touch(mp); 717 dot = mp; 718 if ((cp = skin(hfield("from", mp))) == NULL) 719 cp = skin(nameof(mp, 2)); 720 head.h_to = cat(head.h_to, extract(cp, GTO)); 721 } 722 if (head.h_to == NULL) 723 return 0; 724 mp = &message[msgvec[0] - 1]; 725 if ((head.h_subject = hfield("subject", mp)) == NULL) 726 head.h_subject = hfield("subj", mp); 727 head.h_subject = reedit(head.h_subject); 728 head.h_cc = NULL; 729 head.h_bcc = NULL; 730 head.h_smopts = set_smopts(mp); 731 mail1(&head, 1); 732 return 0; 733} 734 735/* 736 * Conditional commands. These allow one to parameterize one's 737 * .mailrc and do some things if sending, others if receiving. 738 */ 739int 740ifcmd(void *v) 741{ 742 char **argv = v; 743 char *cp; 744 745 if (cond != CANY) { 746 (void)printf("Illegal nested \"if\"\n"); 747 return(1); 748 } 749 cond = CANY; 750 cp = argv[0]; 751 switch (*cp) { 752 case 'r': case 'R': 753 cond = CRCV; 754 break; 755 756 case 's': case 'S': 757 cond = CSEND; 758 break; 759 760 default: 761 (void)printf("Unrecognized if-keyword: \"%s\"\n", cp); 762 return(1); 763 } 764 return(0); 765} 766 767/* 768 * Implement 'else'. This is pretty simple -- we just 769 * flip over the conditional flag. 770 */ 771int 772/*ARGSUSED*/ 773elsecmd(void *v) 774{ 775 776 switch (cond) { 777 case CANY: 778 (void)printf("\"Else\" without matching \"if\"\n"); 779 return(1); 780 781 case CSEND: 782 cond = CRCV; 783 break; 784 785 case CRCV: 786 cond = CSEND; 787 break; 788 789 default: 790 (void)printf("Mail's idea of conditions is screwed up\n"); 791 cond = CANY; 792 break; 793 } 794 return(0); 795} 796 797/* 798 * End of if statement. Just set cond back to anything. 799 */ 800int 801/*ARGSUSED*/ 802endifcmd(void *v) 803{ 804 805 if (cond == CANY) { 806 (void)printf("\"Endif\" without matching \"if\"\n"); 807 return(1); 808 } 809 cond = CANY; 810 return(0); 811} 812 813/* 814 * Set the list of alternate names. 815 */ 816int 817alternates(void *v) 818{ 819 char **namelist = v; 820 int c; 821 char **ap, **ap2, *cp; 822 823 c = argcount(namelist) + 1; 824 if (c == 1) { 825 if (altnames == 0) 826 return(0); 827 for (ap = altnames; *ap; ap++) 828 (void)printf("%s ", *ap); 829 (void)printf("\n"); 830 return(0); 831 } 832 if (altnames != 0) 833 free(altnames); 834 altnames = (char **) calloc((unsigned) c, sizeof (char *)); 835 for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) { 836 cp = calloc((unsigned) strlen(*ap) + 1, sizeof (char)); 837 (void)strcpy(cp, *ap); 838 *ap2 = cp; 839 } 840 *ap2 = 0; 841 return(0); 842} 843