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 */ 45int 46shell(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*/ 68int 69dosh(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 */ 86int 87bangexp(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)) { 100overf: 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 140int 141help(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 */ 159int 160schdir(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 179int 180respond(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 */ 194int 195dorespond(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 */ 259char * 260reedit(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 */ 279int 280preserve(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 */ 303int 304unread(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 */ 320int 321messize(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 */ 339int 340rexit(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 */ 352int 353set(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 */ 399int 400unset(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 */ 439int 440group(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 */ 497void 498sort(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 */ 513int 514diction(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*/ 524int 525null(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 */ 534int 535file(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 */ 552int 553echo(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 570int 571Respond(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 */ 584int 585doRespond(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 */ 621int 622ifcmd(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 */ 653int 654elsecmd(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 */ 681int 682endifcmd(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 */ 696int 697alternates(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