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(char *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(char *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) 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(int e __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(char **argv) 544 { 545 546 if (argv[0] == NULL) { 547 newfileinfo(0); 548 return (0); 549 } 550 if (setfile(*argv) < 0) 551 return (1); 552 announce(); 553 return (0); 554 } 555 556 /* 557 * Expand file names like echo 558 */ 559 int 560 echo(char **argv) 561 { 562 char **ap, *cp; 563 564 for (ap = argv; *ap != NULL; ap++) { 565 cp = *ap; 566 if ((cp = expand(cp)) != NULL) { 567 if (ap != argv) 568 printf(" "); 569 printf("%s", cp); 570 } 571 } 572 printf("\n"); 573 return (0); 574 } 575 576 int 577 Respond(int *msgvec) 578 { 579 if (value("Replyall") == NULL && value("flipr") == NULL) 580 return (doRespond(msgvec)); 581 else 582 return (dorespond(msgvec)); 583 } 584 585 /* 586 * Reply to a series of messages by simply mailing to the senders 587 * and not messing around with the To: and Cc: lists as in normal 588 * reply. 589 */ 590 int 591 doRespond(int msgvec[]) 592 { 593 struct header head; 594 struct message *mp; 595 int *ap; 596 char *cp, *mid; 597 598 head.h_to = NULL; 599 for (ap = msgvec; *ap != 0; ap++) { 600 mp = &message[*ap - 1]; 601 touch(mp); 602 dot = mp; 603 if ((cp = skin(hfield("from", mp))) == NULL) 604 cp = skin(nameof(mp, 2)); 605 head.h_to = cat(head.h_to, extract(cp, GTO)); 606 mid = skin(hfield("message-id", mp)); 607 } 608 if (head.h_to == NULL) 609 return (0); 610 mp = &message[msgvec[0] - 1]; 611 if ((head.h_subject = hfield("subject", mp)) == NULL) 612 head.h_subject = hfield("subj", mp); 613 head.h_subject = reedit(head.h_subject); 614 head.h_cc = NULL; 615 head.h_bcc = NULL; 616 head.h_smopts = NULL; 617 head.h_replyto = value("REPLYTO"); 618 head.h_inreplyto = mid; 619 mail1(&head, 1); 620 return (0); 621 } 622 623 /* 624 * Conditional commands. These allow one to parameterize one's 625 * .mailrc and do some things if sending, others if receiving. 626 */ 627 int 628 ifcmd(char **argv) 629 { 630 char *cp; 631 632 if (cond != CANY) { 633 printf("Illegal nested \"if\"\n"); 634 return (1); 635 } 636 cond = CANY; 637 cp = argv[0]; 638 switch (*cp) { 639 case 'r': case 'R': 640 cond = CRCV; 641 break; 642 643 case 's': case 'S': 644 cond = CSEND; 645 break; 646 647 default: 648 printf("Unrecognized if-keyword: \"%s\"\n", cp); 649 return (1); 650 } 651 return (0); 652 } 653 654 /* 655 * Implement 'else'. This is pretty simple -- we just 656 * flip over the conditional flag. 657 */ 658 int 659 elsecmd(void) 660 { 661 662 switch (cond) { 663 case CANY: 664 printf("\"Else\" without matching \"if\"\n"); 665 return (1); 666 667 case CSEND: 668 cond = CRCV; 669 break; 670 671 case CRCV: 672 cond = CSEND; 673 break; 674 675 default: 676 printf("Mail's idea of conditions is screwed up\n"); 677 cond = CANY; 678 break; 679 } 680 return (0); 681 } 682 683 /* 684 * End of if statement. Just set cond back to anything. 685 */ 686 int 687 endifcmd(void) 688 { 689 690 if (cond == CANY) { 691 printf("\"Endif\" without matching \"if\"\n"); 692 return (1); 693 } 694 cond = CANY; 695 return (0); 696 } 697 698 /* 699 * Set the list of alternate names. 700 */ 701 int 702 alternates(char **namelist) 703 { 704 int c; 705 char **ap, **ap2, *cp; 706 707 c = argcount(namelist) + 1; 708 if (c == 1) { 709 if (altnames == 0) 710 return (0); 711 for (ap = altnames; *ap != NULL; ap++) 712 printf("%s ", *ap); 713 printf("\n"); 714 return (0); 715 } 716 if (altnames != 0) 717 (void)free(altnames); 718 if ((altnames = calloc((unsigned)c, sizeof(char *))) == NULL) 719 err(1, "Out of memory"); 720 for (ap = namelist, ap2 = altnames; *ap != NULL; ap++, ap2++) { 721 cp = calloc((unsigned)strlen(*ap) + 1, sizeof(char)); 722 strcpy(cp, *ap); 723 *ap2 = cp; 724 } 725 *ap2 = 0; 726 return (0); 727 } 728