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