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