1 /* $NetBSD: ssh.c,v 1.1.1.1 1995/10/08 23:08:46 gwr Exp $ */ 2 3 /* 4 * Copyright (c) 1995 Gordon W. Ross 5 * 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. The name of the author may not be used to endorse or promote products 16 * derived from this software without specific prior written permission. 17 * 4. All advertising materials mentioning features or use of this software 18 * must display the following acknowledgement: 19 * This product includes software developed by Gordon W. Ross 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 /* 34 * Small Shell - Nothing fancy. Just runs programs. 35 * The RAMDISK root uses this to save space. 36 */ 37 38 #include <errno.h> 39 #include <fcntl.h> 40 #include <setjmp.h> 41 #include <signal.h> 42 #include <stdio.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <unistd.h> 46 47 #include <sys/param.h> 48 #include <sys/wait.h> 49 50 /* XXX - SunOS hacks... */ 51 #ifndef WCOREDUMP 52 #define WCOREDUMP(x) ((x) & 0200) 53 #endif 54 55 #ifndef __P 56 #ifdef __STDC__ 57 #define __P(x) x 58 #else /* STDC */ 59 #define __P(x) () 60 #endif /* STDC */ 61 #endif /* __P */ 62 63 extern char *optarg; 64 extern int optind, opterr; 65 66 #define MAXLINE 256 67 #define MAXARGS 32 68 69 #define MAXPATH 256 70 char cur_path[MAXPATH] = "PATH=/bin:/usr/bin"; 71 72 char rc_name[] = ".sshrc"; 73 char *prompt = "ssh: "; 74 75 int eflag; /* exit on cmd failure */ 76 int iflag; /* interactive mode (catch interrupts) */ 77 int sflag; /* read from stdin (ignore file arg) */ 78 int xflag; /* execution trace */ 79 80 /* Command file: name, line number, arg count, arg vector */ 81 char *cf_name; 82 int cf_line; 83 int cf_argc; 84 char **cf_argv; 85 86 int def_omode = 0666; 87 int run_bg_pid; 88 89 jmp_buf next_cmd; 90 91 void catchsig __P((int sig)); 92 void child_newfd __P((int setfd, char *file, int otype)); 93 int find_in_path __P((char *cmd, char *filebuf)); 94 void print_termsig __P((FILE *fp, int cstat)); 95 int runfile __P((FILE *fp)); 96 97 98 main(argc, argv) 99 int argc; 100 char **argv; 101 { 102 struct sigaction sa; 103 FILE *cfp; /* command file ptr */ 104 int c, sig; 105 int error = 0; 106 107 while ((c = getopt(argc, argv, "eisx")) != -1) { 108 switch (c) { 109 case 'e': 110 eflag++; 111 break; 112 case 'i': 113 eflag++; 114 break; 115 case 's': 116 sflag++; 117 break; 118 case 'x': 119 xflag++; 120 break; 121 case '?': 122 error++; 123 break; 124 } 125 } 126 if (error) { 127 fprintf(stderr, "usage: ssh [-eisx] [cmd_file [...]]\n"); 128 exit(1); 129 } 130 cf_argc = argc - optind; 131 cf_argv = &argv[optind]; 132 133 /* If this is a login shell, run the rc file. */ 134 if (argv[0] && argv[0][0] == '-') { 135 cf_line = 0; 136 cf_name = rc_name; 137 if ((cfp = fopen(cf_name, "r")) != NULL) { 138 error = runfile(cfp); 139 fclose(cfp); 140 } 141 } 142 143 /* If no file names, read commands from stdin. */ 144 if (cf_argc == 0) 145 sflag++; 146 /* If stdin is a tty, be interactive. */ 147 if (sflag && isatty(fileno(stdin))) 148 iflag++; 149 150 /* Maybe run a command file... */ 151 if (!sflag && cf_argc) { 152 cf_line = 0; 153 cf_name = cf_argv[0]; 154 cfp = fopen(cf_name, "r"); 155 if (cfp == NULL) { 156 perror(cf_name); 157 exit(1); 158 } 159 error = runfile(cfp); 160 fclose(cfp); 161 exit(error); 162 } 163 164 /* Read commands from stdin. */ 165 cf_line = 0; 166 cf_name = "(stdin)"; 167 if (iflag) { 168 eflag = 0; /* don't kill shell on error. */ 169 sig = setjmp(next_cmd); 170 if (sig == 0) { 171 /* Initialization... */ 172 sa.sa_handler = catchsig; 173 sa.sa_flags = 0; 174 sigemptyset(&sa.sa_mask); 175 sigaction(SIGINT, &sa, NULL); 176 sigaction(SIGQUIT, &sa, NULL); 177 sigaction(SIGTERM, &sa, NULL); 178 } else { 179 /* Got here via longjmp. */ 180 fprintf(stderr, " signal %d\n", sig); 181 sigemptyset(&sa.sa_mask); 182 sigprocmask(SIG_SETMASK, &sa.sa_mask, NULL); 183 } 184 } 185 error = runfile(stdin); 186 exit (error); 187 } 188 189 void 190 catchsig(sig) 191 int sig; 192 { 193 longjmp(next_cmd, sig); 194 } 195 196 /* 197 * Run command from the passed stdio file pointer. 198 * Returns exit status. 199 */ 200 int 201 runfile(cfp) 202 FILE *cfp; 203 { 204 char ibuf[MAXLINE]; 205 char *argv[MAXARGS]; 206 char *p; 207 int i, argc, exitcode, cpid, cstat; 208 209 /* The command loop. */ 210 exitcode = 0; 211 for (;;) { 212 if (iflag) { 213 fprintf(stderr, prompt); 214 fflush(stderr); 215 } 216 217 if ((fgets(ibuf, sizeof(ibuf), cfp)) == NULL) 218 break; 219 cf_line++; 220 221 argc = 0; 222 p = ibuf; 223 224 while (argc < MAXARGS-1) { 225 /* skip blanks or tabs */ 226 while ((*p == ' ') || (*p == '\t')) { 227 next_token: 228 *p++ = '\0'; 229 } 230 /* end of line? */ 231 if ((*p == '\n') || (*p == '#')) { 232 *p = '\0'; 233 break; /* to eol */ 234 } 235 if (*p == '\0') 236 break; 237 /* save start of token */ 238 argv[argc++] = p; 239 /* find end of token */ 240 while (*p) { 241 if ((*p == '\n') || (*p == '#')) { 242 *p = '\0'; 243 goto eol; 244 } 245 if ((*p == ' ') || (*p == '\t')) 246 goto next_token; 247 p++; 248 } 249 } 250 eol: 251 252 if (argc > 0) { 253 if (xflag) { 254 fprintf(stderr, "x"); 255 for (i = 0; i < argc; i++) { 256 fprintf(stderr, " %s", argv[i]); 257 } 258 fprintf(stderr, "\n"); 259 } 260 argv[argc] = NULL; 261 exitcode = cmd_eval(argc, argv); 262 } 263 264 /* Collect children. */ 265 while ((cpid = waitpid(0, &cstat, WNOHANG)) > 0) { 266 if (iflag) { 267 fprintf(stderr, "[%d] ", cpid); 268 if (WTERMSIG(cstat)) { 269 print_termsig(stderr, cstat); 270 } else { 271 fprintf(stderr, "Exited, status %d\n", 272 WEXITSTATUS(cstat)); 273 } 274 } 275 } 276 277 if (exitcode && eflag) 278 break; 279 } 280 /* return status of last command */ 281 return (exitcode); 282 } 283 284 285 /**************************************************************** 286 * Table of buildin commands 287 * for cmd_eval() to search... 288 ****************************************************************/ 289 290 struct cmd { 291 char *name; 292 int (*func)(); 293 char *help; 294 }; 295 struct cmd cmd_table[]; 296 297 /* 298 * Evaluate a command named as argv[0] 299 * with arguments argv[1],argv[2]... 300 * Returns exit status. 301 */ 302 int 303 cmd_eval(argc, argv) 304 int argc; 305 char **argv; 306 { 307 struct cmd *cp; 308 309 /* 310 * Do linear search for a builtin command. 311 * Performance does not matter here. 312 */ 313 for (cp = cmd_table; cp->name; cp++) { 314 if (!strcmp(cp->name, argv[0])) { 315 /* Pass only args to builtin. */ 316 --argc; argv++; 317 return (cp->func(argc, argv)); 318 } 319 } 320 321 /* 322 * If no matching builtin, let "run ..." 323 * have a chance to try an external. 324 */ 325 return (cmd_run(argc, argv)); 326 } 327 328 /***************************************************************** 329 * Here are the actual commands. For these, 330 * the command name has been skipped, so 331 * argv[0] is the first arg (if any args). 332 * All return an exit status. 333 ****************************************************************/ 334 335 char help_cd[] = "cd [dir]"; 336 337 int 338 cmd_cd(argc, argv) 339 int argc; 340 char **argv; 341 { 342 char *dir; 343 int err; 344 345 if (argc > 0) 346 dir = argv[0]; 347 else { 348 dir = getenv("HOME"); 349 if (dir == NULL) 350 dir = "/"; 351 } 352 if (chdir(dir)) { 353 perror(dir); 354 return (1); 355 } 356 return(0); 357 } 358 359 char help_exit[] = "exit [n]"; 360 361 int 362 cmd_exit(argc, argv) 363 int argc; 364 char **argv; 365 { 366 int val = 0; 367 368 if (argc > 0) 369 val = atoi(argv[0]); 370 exit(val); 371 } 372 373 char help_help[] = "help [command]"; 374 375 int 376 cmd_help(argc, argv) 377 int argc; 378 char **argv; 379 { 380 struct cmd *cp; 381 382 if (argc > 0) { 383 for (cp = cmd_table; cp->name; cp++) { 384 if (!strcmp(cp->name, argv[0])) { 385 printf("usage: %s\n", cp->help); 386 return (0); 387 } 388 } 389 printf("%s: no such command\n", argv[0]); 390 } 391 392 printf("Builtin commands: "); 393 for (cp = cmd_table; cp->name; cp++) { 394 printf(" %s", cp->name); 395 } 396 printf("\nFor specific usage: help [command]\n"); 397 return (0); 398 } 399 400 char help_path[] = "path [dir1:dir2:...]"; 401 402 int 403 cmd_path(argc, argv) 404 int argc; 405 char **argv; 406 { 407 int i; 408 409 if (argc <= 0) { 410 printf("%s\n", cur_path); 411 return(0); 412 } 413 414 strncpy(cur_path+5, argv[0], MAXPATH-6); 415 putenv(cur_path); 416 417 return (0); 418 } 419 420 /***************************************************************** 421 * The "run" command is the big one. 422 * Does fork/exec/wait, redirection... 423 * Returns exit status of child 424 * (or zero for a background job) 425 ****************************************************************/ 426 427 char help_run[] = "\ 428 run [-bg] [-i ifile] [-o ofile] [-e efile] program [args...]\n\ 429 or simply: program [args...]"; 430 431 int 432 cmd_run(argc, argv) 433 int argc; 434 char **argv; 435 { 436 struct sigaction sa; 437 int pid, err, cstat, fd; 438 char file[MAXPATHLEN]; 439 int background; 440 char *opt, *ifile, *ofile, *efile; 441 extern char **environ; 442 443 /* 444 * Parse options: 445 * -b : background 446 * -i : input file 447 * -o : output file 448 * -e : error file 449 */ 450 background = 0; 451 ifile = ofile = efile = NULL; 452 while ((argc > 0) && (argv[0][0] == '-')) { 453 opt = argv[0]; 454 --argc; argv++; 455 switch (opt[1]) { 456 case 'b': 457 background++; 458 break; 459 case 'i': 460 ifile = argv[0]; 461 goto shift; 462 case 'o': 463 ofile = argv[0]; 464 goto shift; 465 case 'e': 466 efile = argv[0]; 467 goto shift; 468 default: 469 fprintf(stderr, "run %s: bad option\n", opt); 470 return (1); 471 shift: 472 --argc; argv++; 473 } 474 } 475 476 if (argc <= 0) { 477 fprintf(stderr, "%s:%d run: missing command\n", 478 cf_name, cf_line); 479 return (1); 480 } 481 482 /* Commands containing '/' get no path search. */ 483 if (strchr(argv[0], '/')) { 484 strncpy(file, argv[0], sizeof(file)-1); 485 if (access(file, X_OK)) { 486 perror(file); 487 return (1); 488 } 489 } else { 490 if (find_in_path(argv[0], file)) { 491 fprintf(stderr, "%s: command not found\n", argv[0]); 492 return (1); 493 } 494 } 495 496 pid = fork(); 497 if (pid == 0) { 498 /* child runs this */ 499 /* handle redirection options... */ 500 if (ifile) 501 child_newfd(0, ifile, O_RDONLY); 502 if (ofile) 503 child_newfd(1, ofile, O_WRONLY|O_CREAT); 504 if (efile) 505 child_newfd(2, efile, O_WRONLY|O_CREAT); 506 if (background) { 507 /* Ignore SIGINT, SIGQUIT */ 508 sa.sa_handler = SIG_IGN; 509 sa.sa_flags = 0; 510 sigemptyset(&sa.sa_mask); 511 sigaction(SIGINT, &sa, NULL); 512 sigaction(SIGQUIT, &sa, NULL); 513 } 514 err = execve(file, argv, environ); 515 perror(argv[0]); 516 return (1); 517 } 518 /* parent */ 519 /* Handle background option... */ 520 if (background) { 521 fprintf(stderr, "[%d]\n", pid); 522 run_bg_pid = pid; 523 return (0); 524 } 525 if (waitpid(pid, &cstat, 0) < 0) { 526 perror("waitpid"); 527 return (1); 528 } 529 if (WTERMSIG(cstat)) { 530 print_termsig(stderr, cstat); 531 } 532 return (WEXITSTATUS(cstat)); 533 } 534 535 /***************************************************************** 536 * table of builtin commands 537 ****************************************************************/ 538 struct cmd cmd_table[] = { 539 { "cd", cmd_cd, help_cd }, 540 { "exit", cmd_exit, help_exit }, 541 { "help", cmd_help, help_help }, 542 { "path", cmd_path, help_path }, 543 { "run", cmd_run, help_run }, 544 { 0 }, 545 }; 546 547 /***************************************************************** 548 * helper functions for the "run" command 549 ****************************************************************/ 550 551 int 552 find_in_path(cmd, filebuf) 553 char *cmd; 554 char *filebuf; 555 { 556 char *dirp, *endp, *bufp; /* dir, end */ 557 558 dirp = cur_path + 5; 559 while (*dirp) { 560 endp = dirp; 561 bufp = filebuf; 562 while (*endp && (*endp != ':')) 563 *bufp++ = *endp++; 564 *bufp++ = '/'; 565 strcpy(bufp, cmd); 566 if (access(filebuf, X_OK) == 0) 567 return (0); 568 if (*endp == ':') 569 endp++; 570 dirp = endp; /* next dir */ 571 } 572 return (-1); 573 } 574 575 /* 576 * Set the file descriptor SETFD to FILE, 577 * which was opened with OTYPE and MODE. 578 */ 579 void 580 child_newfd(setfd, file, otype) 581 int setfd; /* what to set (i.e. 0,1,2) */ 582 char *file; 583 int otype; /* O_RDONLY, etc. */ 584 { 585 int newfd; 586 587 close(setfd); 588 if ((newfd = open(file, otype, def_omode)) < 0) { 589 perror(file); 590 exit(1); 591 } 592 if (newfd != setfd) { 593 dup2(newfd, setfd); 594 close(newfd); 595 } 596 } 597 598 void 599 print_termsig(fp, cstat) 600 FILE *fp; 601 int cstat; 602 { 603 fprintf(fp, "Terminated, signal %d", 604 WTERMSIG(cstat)); 605 if (WCOREDUMP(cstat)) 606 fprintf(fp, " (core dumped)"); 607 fprintf(fp, "\n"); 608 } 609