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