cmd3.c revision 331722
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 32static char sccsid[] = "@(#)cmd3.c 8.2 (Berkeley) 4/20/95"; 33#endif 34#endif /* not lint */ 35#include <sys/cdefs.h> 36__FBSDID("$FreeBSD: stable/11/usr.bin/mail/cmd3.c 331722 2018-03-29 02:50:57Z eadler $"); 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 */ 51int 52shell(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*/ 74int 75dosh(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 */ 92int 93bangexp(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)) { 106overf: 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 146int 147help(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 */ 165int 166schdir(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 184int 185respond(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 */ 197int 198dorespond(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 */ 262char * 263reedit(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 */ 282int 283preserve(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 */ 305int 306unread(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 */ 321int 322messize(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 */ 339int 340rexit(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 */ 352int 353set(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 */ 398int 399unset(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 */ 437int 438group(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 if ((gh = calloc(1, sizeof(*gh))) == NULL) 467 err(1, "Out of memory"); 468 gh->g_name = vcopy(gname); 469 gh->g_list = NULL; 470 gh->g_link = groups[h]; 471 groups[h] = gh; 472 } 473 474 /* 475 * Insert names from the command list into the group. 476 * Who cares if there are duplicates? They get tossed 477 * later anyway. 478 */ 479 480 for (ap = argv+1; *ap != NULL; ap++) { 481 if ((gp = calloc(1, sizeof(*gp))) == NULL) 482 err(1, "Out of memory"); 483 gp->ge_name = vcopy(*ap); 484 gp->ge_link = gh->g_list; 485 gh->g_list = gp; 486 } 487 return (0); 488} 489 490/* 491 * Sort the passed string vecotor into ascending dictionary 492 * order. 493 */ 494void 495sort(char **list) 496{ 497 char **ap; 498 499 for (ap = list; *ap != NULL; ap++) 500 ; 501 if (ap-list < 2) 502 return; 503 qsort(list, ap-list, sizeof(*list), diction); 504} 505 506/* 507 * Do a dictionary order comparison of the arguments from 508 * qsort. 509 */ 510int 511diction(const void *a, const void *b) 512{ 513 return (strcmp(*(const char **)a, *(const char **)b)); 514} 515 516/* 517 * The do nothing command for comments. 518 */ 519 520/*ARGSUSED*/ 521int 522null(int e __unused) 523{ 524 return (0); 525} 526 527/* 528 * Change to another file. With no argument, print information about 529 * the current file. 530 */ 531int 532file(char **argv) 533{ 534 535 if (argv[0] == NULL) { 536 newfileinfo(0); 537 return (0); 538 } 539 if (setfile(*argv) < 0) 540 return (1); 541 announce(); 542 return (0); 543} 544 545/* 546 * Expand file names like echo 547 */ 548int 549echo(char **argv) 550{ 551 char **ap, *cp; 552 553 for (ap = argv; *ap != NULL; ap++) { 554 cp = *ap; 555 if ((cp = expand(cp)) != NULL) { 556 if (ap != argv) 557 printf(" "); 558 printf("%s", cp); 559 } 560 } 561 printf("\n"); 562 return (0); 563} 564 565int 566Respond(int *msgvec) 567{ 568 if (value("Replyall") == NULL && value("flipr") == NULL) 569 return (doRespond(msgvec)); 570 else 571 return (dorespond(msgvec)); 572} 573 574/* 575 * Reply to a series of messages by simply mailing to the senders 576 * and not messing around with the To: and Cc: lists as in normal 577 * reply. 578 */ 579int 580doRespond(int msgvec[]) 581{ 582 struct header head; 583 struct message *mp; 584 int *ap; 585 char *cp, *mid; 586 587 head.h_to = NULL; 588 for (ap = msgvec; *ap != 0; ap++) { 589 mp = &message[*ap - 1]; 590 touch(mp); 591 dot = mp; 592 if ((cp = skin(hfield("from", mp))) == NULL) 593 cp = skin(nameof(mp, 2)); 594 head.h_to = cat(head.h_to, extract(cp, GTO)); 595 mid = skin(hfield("message-id", mp)); 596 } 597 if (head.h_to == NULL) 598 return (0); 599 mp = &message[msgvec[0] - 1]; 600 if ((head.h_subject = hfield("subject", mp)) == NULL) 601 head.h_subject = hfield("subj", mp); 602 head.h_subject = reedit(head.h_subject); 603 head.h_cc = NULL; 604 head.h_bcc = NULL; 605 head.h_smopts = NULL; 606 head.h_replyto = value("REPLYTO"); 607 head.h_inreplyto = mid; 608 mail1(&head, 1); 609 return (0); 610} 611 612/* 613 * Conditional commands. These allow one to parameterize one's 614 * .mailrc and do some things if sending, others if receiving. 615 */ 616int 617ifcmd(char **argv) 618{ 619 char *cp; 620 621 if (cond != CANY) { 622 printf("Illegal nested \"if\"\n"); 623 return (1); 624 } 625 cond = CANY; 626 cp = argv[0]; 627 switch (*cp) { 628 case 'r': case 'R': 629 cond = CRCV; 630 break; 631 632 case 's': case 'S': 633 cond = CSEND; 634 break; 635 636 default: 637 printf("Unrecognized if-keyword: \"%s\"\n", cp); 638 return (1); 639 } 640 return (0); 641} 642 643/* 644 * Implement 'else'. This is pretty simple -- we just 645 * flip over the conditional flag. 646 */ 647int 648elsecmd(void) 649{ 650 651 switch (cond) { 652 case CANY: 653 printf("\"Else\" without matching \"if\"\n"); 654 return (1); 655 656 case CSEND: 657 cond = CRCV; 658 break; 659 660 case CRCV: 661 cond = CSEND; 662 break; 663 664 default: 665 printf("Mail's idea of conditions is screwed up\n"); 666 cond = CANY; 667 break; 668 } 669 return (0); 670} 671 672/* 673 * End of if statement. Just set cond back to anything. 674 */ 675int 676endifcmd(void) 677{ 678 679 if (cond == CANY) { 680 printf("\"Endif\" without matching \"if\"\n"); 681 return (1); 682 } 683 cond = CANY; 684 return (0); 685} 686 687/* 688 * Set the list of alternate names. 689 */ 690int 691alternates(char **namelist) 692{ 693 int c; 694 char **ap, **ap2, *cp; 695 696 c = argcount(namelist) + 1; 697 if (c == 1) { 698 if (altnames == 0) 699 return (0); 700 for (ap = altnames; *ap != NULL; ap++) 701 printf("%s ", *ap); 702 printf("\n"); 703 return (0); 704 } 705 if (altnames != 0) 706 (void)free(altnames); 707 if ((altnames = calloc((unsigned)c, sizeof(char *))) == NULL) 708 err(1, "Out of memory"); 709 for (ap = namelist, ap2 = altnames; *ap != NULL; ap++, ap2++) { 710 cp = calloc((unsigned)strlen(*ap) + 1, sizeof(char)); 711 strcpy(cp, *ap); 712 *ap2 = cp; 713 } 714 *ap2 = 0; 715 return (0); 716} 717