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
main(int argc,char * argv[])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
catchsig(int sig)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
runfile(FILE * cfp)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
cmd_eval(int argc,char * argv[])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
cmd_cd(int argc,char * argv[])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
cmd_exit(int argc,char ** argv)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
cmd_help(int argc,char * argv[])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
cmd_path(int argc,char * argv[])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
cmd_run(int argc,char * argv[])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
find_in_path(char * cmd,char * filebuf)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
child_newfd(int setfd,char * file,int otype)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
print_termsig(FILE * fp,int cstat)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