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