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