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