ssh.c revision 1.2 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