NetBSD/distrib/utils/ssh/ssh.c

605 lines
12 KiB
C

/* $NetBSD: ssh.c,v 1.2 2002/05/26 00:09:09 wiz Exp $ */
/*
* Copyright (c) 1995 Gordon W. Ross
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
* 4. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Gordon W. Ross
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Small Shell - Nothing fancy. Just runs programs.
* The RAMDISK root uses this to save space.
*/
#include <errno.h>
#include <fcntl.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/wait.h>
/* XXX - SunOS hacks... */
#ifndef WCOREDUMP
#define WCOREDUMP(x) ((x) & 0200)
#endif
#ifndef __P
#define __P(x) x
#endif /* __P */
extern char *optarg;
extern int optind, opterr;
#define MAXLINE 256
#define MAXARGS 32
#define MAXPATH 256
char cur_path[MAXPATH] = "PATH=/bin:/usr/bin";
char rc_name[] = ".sshrc";
char *prompt = "ssh: ";
int eflag; /* exit on cmd failure */
int iflag; /* interactive mode (catch interrupts) */
int sflag; /* read from stdin (ignore file arg) */
int xflag; /* execution trace */
/* Command file: name, line number, arg count, arg vector */
char *cf_name;
int cf_line;
int cf_argc;
char **cf_argv;
int def_omode = 0666;
int run_bg_pid;
jmp_buf next_cmd;
void catchsig __P((int sig));
void child_newfd __P((int setfd, char *file, int otype));
int find_in_path __P((char *cmd, char *filebuf));
void print_termsig __P((FILE *fp, int cstat));
int runfile __P((FILE *fp));
main(argc, argv)
int argc;
char **argv;
{
struct sigaction sa;
FILE *cfp; /* command file ptr */
int c, sig;
int error = 0;
while ((c = getopt(argc, argv, "eisx")) != -1) {
switch (c) {
case 'e':
eflag++;
break;
case 'i':
eflag++;
break;
case 's':
sflag++;
break;
case 'x':
xflag++;
break;
case '?':
error++;
break;
}
}
if (error) {
fprintf(stderr, "usage: ssh [-eisx] [cmd_file [...]]\n");
exit(1);
}
cf_argc = argc - optind;
cf_argv = &argv[optind];
/* If this is a login shell, run the rc file. */
if (argv[0] && argv[0][0] == '-') {
cf_line = 0;
cf_name = rc_name;
if ((cfp = fopen(cf_name, "r")) != NULL) {
error = runfile(cfp);
fclose(cfp);
}
}
/* If no file names, read commands from stdin. */
if (cf_argc == 0)
sflag++;
/* If stdin is a tty, be interactive. */
if (sflag && isatty(fileno(stdin)))
iflag++;
/* Maybe run a command file... */
if (!sflag && cf_argc) {
cf_line = 0;
cf_name = cf_argv[0];
cfp = fopen(cf_name, "r");
if (cfp == NULL) {
perror(cf_name);
exit(1);
}
error = runfile(cfp);
fclose(cfp);
exit(error);
}
/* Read commands from stdin. */
cf_line = 0;
cf_name = "(stdin)";
if (iflag) {
eflag = 0; /* don't kill shell on error. */
sig = setjmp(next_cmd);
if (sig == 0) {
/* Initialization... */
sa.sa_handler = catchsig;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
} else {
/* Got here via longjmp. */
fprintf(stderr, " signal %d\n", sig);
sigemptyset(&sa.sa_mask);
sigprocmask(SIG_SETMASK, &sa.sa_mask, NULL);
}
}
error = runfile(stdin);
exit (error);
}
void
catchsig(sig)
int sig;
{
longjmp(next_cmd, sig);
}
/*
* Run command from the passed stdio file pointer.
* Returns exit status.
*/
int
runfile(cfp)
FILE *cfp;
{
char ibuf[MAXLINE];
char *argv[MAXARGS];
char *p;
int i, argc, exitcode, cpid, cstat;
/* The command loop. */
exitcode = 0;
for (;;) {
if (iflag) {
fprintf(stderr, prompt);
fflush(stderr);
}
if ((fgets(ibuf, sizeof(ibuf), cfp)) == NULL)
break;
cf_line++;
argc = 0;
p = ibuf;
while (argc < MAXARGS-1) {
/* skip blanks or tabs */
while ((*p == ' ') || (*p == '\t')) {
next_token:
*p++ = '\0';
}
/* end of line? */
if ((*p == '\n') || (*p == '#')) {
*p = '\0';
break; /* to eol */
}
if (*p == '\0')
break;
/* save start of token */
argv[argc++] = p;
/* find end of token */
while (*p) {
if ((*p == '\n') || (*p == '#')) {
*p = '\0';
goto eol;
}
if ((*p == ' ') || (*p == '\t'))
goto next_token;
p++;
}
}
eol:
if (argc > 0) {
if (xflag) {
fprintf(stderr, "x");
for (i = 0; i < argc; i++) {
fprintf(stderr, " %s", argv[i]);
}
fprintf(stderr, "\n");
}
argv[argc] = NULL;
exitcode = cmd_eval(argc, argv);
}
/* Collect children. */
while ((cpid = waitpid(0, &cstat, WNOHANG)) > 0) {
if (iflag) {
fprintf(stderr, "[%d] ", cpid);
if (WTERMSIG(cstat)) {
print_termsig(stderr, cstat);
} else {
fprintf(stderr, "Exited, status %d\n",
WEXITSTATUS(cstat));
}
}
}
if (exitcode && eflag)
break;
}
/* return status of last command */
return (exitcode);
}
/****************************************************************
* Table of buildin commands
* for cmd_eval() to search...
****************************************************************/
struct cmd {
char *name;
int (*func)();
char *help;
};
struct cmd cmd_table[];
/*
* Evaluate a command named as argv[0]
* with arguments argv[1],argv[2]...
* Returns exit status.
*/
int
cmd_eval(argc, argv)
int argc;
char **argv;
{
struct cmd *cp;
/*
* Do linear search for a builtin command.
* Performance does not matter here.
*/
for (cp = cmd_table; cp->name; cp++) {
if (!strcmp(cp->name, argv[0])) {
/* Pass only args to builtin. */
--argc; argv++;
return (cp->func(argc, argv));
}
}
/*
* If no matching builtin, let "run ..."
* have a chance to try an external.
*/
return (cmd_run(argc, argv));
}
/*****************************************************************
* Here are the actual commands. For these,
* the command name has been skipped, so
* argv[0] is the first arg (if any args).
* All return an exit status.
****************************************************************/
char help_cd[] = "cd [dir]";
int
cmd_cd(argc, argv)
int argc;
char **argv;
{
char *dir;
int err;
if (argc > 0)
dir = argv[0];
else {
dir = getenv("HOME");
if (dir == NULL)
dir = "/";
}
if (chdir(dir)) {
perror(dir);
return (1);
}
return(0);
}
char help_exit[] = "exit [n]";
int
cmd_exit(argc, argv)
int argc;
char **argv;
{
int val = 0;
if (argc > 0)
val = atoi(argv[0]);
exit(val);
}
char help_help[] = "help [command]";
int
cmd_help(argc, argv)
int argc;
char **argv;
{
struct cmd *cp;
if (argc > 0) {
for (cp = cmd_table; cp->name; cp++) {
if (!strcmp(cp->name, argv[0])) {
printf("usage: %s\n", cp->help);
return (0);
}
}
printf("%s: no such command\n", argv[0]);
}
printf("Builtin commands: ");
for (cp = cmd_table; cp->name; cp++) {
printf(" %s", cp->name);
}
printf("\nFor specific usage: help [command]\n");
return (0);
}
char help_path[] = "path [dir1:dir2:...]";
int
cmd_path(argc, argv)
int argc;
char **argv;
{
int i;
if (argc <= 0) {
printf("%s\n", cur_path);
return(0);
}
strncpy(cur_path+5, argv[0], MAXPATH-6);
putenv(cur_path);
return (0);
}
/*****************************************************************
* The "run" command is the big one.
* Does fork/exec/wait, redirection...
* Returns exit status of child
* (or zero for a background job)
****************************************************************/
char help_run[] = "\
run [-bg] [-i ifile] [-o ofile] [-e efile] program [args...]\n\
or simply: program [args...]";
int
cmd_run(argc, argv)
int argc;
char **argv;
{
struct sigaction sa;
int pid, err, cstat, fd;
char file[MAXPATHLEN];
int background;
char *opt, *ifile, *ofile, *efile;
extern char **environ;
/*
* Parse options:
* -b : background
* -i : input file
* -o : output file
* -e : error file
*/
background = 0;
ifile = ofile = efile = NULL;
while ((argc > 0) && (argv[0][0] == '-')) {
opt = argv[0];
--argc; argv++;
switch (opt[1]) {
case 'b':
background++;
break;
case 'i':
ifile = argv[0];
goto shift;
case 'o':
ofile = argv[0];
goto shift;
case 'e':
efile = argv[0];
goto shift;
default:
fprintf(stderr, "run %s: bad option\n", opt);
return (1);
shift:
--argc; argv++;
}
}
if (argc <= 0) {
fprintf(stderr, "%s:%d run: missing command\n",
cf_name, cf_line);
return (1);
}
/* Commands containing '/' get no path search. */
if (strchr(argv[0], '/')) {
strncpy(file, argv[0], sizeof(file)-1);
if (access(file, X_OK)) {
perror(file);
return (1);
}
} else {
if (find_in_path(argv[0], file)) {
fprintf(stderr, "%s: command not found\n", argv[0]);
return (1);
}
}
pid = fork();
if (pid == 0) {
/* child runs this */
/* handle redirection options... */
if (ifile)
child_newfd(0, ifile, O_RDONLY);
if (ofile)
child_newfd(1, ofile, O_WRONLY|O_CREAT);
if (efile)
child_newfd(2, efile, O_WRONLY|O_CREAT);
if (background) {
/* Ignore SIGINT, SIGQUIT */
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
}
err = execve(file, argv, environ);
perror(argv[0]);
return (1);
}
/* parent */
/* Handle background option... */
if (background) {
fprintf(stderr, "[%d]\n", pid);
run_bg_pid = pid;
return (0);
}
if (waitpid(pid, &cstat, 0) < 0) {
perror("waitpid");
return (1);
}
if (WTERMSIG(cstat)) {
print_termsig(stderr, cstat);
}
return (WEXITSTATUS(cstat));
}
/*****************************************************************
* table of builtin commands
****************************************************************/
struct cmd cmd_table[] = {
{ "cd", cmd_cd, help_cd },
{ "exit", cmd_exit, help_exit },
{ "help", cmd_help, help_help },
{ "path", cmd_path, help_path },
{ "run", cmd_run, help_run },
{ 0 },
};
/*****************************************************************
* helper functions for the "run" command
****************************************************************/
int
find_in_path(cmd, filebuf)
char *cmd;
char *filebuf;
{
char *dirp, *endp, *bufp; /* dir, end */
dirp = cur_path + 5;
while (*dirp) {
endp = dirp;
bufp = filebuf;
while (*endp && (*endp != ':'))
*bufp++ = *endp++;
*bufp++ = '/';
strcpy(bufp, cmd);
if (access(filebuf, X_OK) == 0)
return (0);
if (*endp == ':')
endp++;
dirp = endp; /* next dir */
}
return (-1);
}
/*
* Set the file descriptor SETFD to FILE,
* which was opened with OTYPE and MODE.
*/
void
child_newfd(setfd, file, otype)
int setfd; /* what to set (i.e. 0,1,2) */
char *file;
int otype; /* O_RDONLY, etc. */
{
int newfd;
close(setfd);
if ((newfd = open(file, otype, def_omode)) < 0) {
perror(file);
exit(1);
}
if (newfd != setfd) {
dup2(newfd, setfd);
close(newfd);
}
}
void
print_termsig(fp, cstat)
FILE *fp;
int cstat;
{
fprintf(fp, "Terminated, signal %d",
WTERMSIG(cstat));
if (WCOREDUMP(cstat))
fprintf(fp, " (core dumped)");
fprintf(fp, "\n");
}