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