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