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