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