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