version 1.169, 2002/03/26 11:37:05 |
version 1.169.2.5, 2003/04/03 22:35:18 |
|
|
#include "xmalloc.h" |
#include "xmalloc.h" |
#include "packet.h" |
#include "packet.h" |
#include "buffer.h" |
#include "buffer.h" |
#include "uidswap.h" |
|
#include "channels.h" |
#include "channels.h" |
#include "key.h" |
#include "key.h" |
#include "authfd.h" |
#include "authfd.h" |
|
|
|
|
/* |
/* |
* Flag indicating that ssh should fork after authentication. This is useful |
* Flag indicating that ssh should fork after authentication. This is useful |
* so that the pasphrase can be entered manually, and then ssh goes to the |
* so that the passphrase can be entered manually, and then ssh goes to the |
* background. |
* background. |
*/ |
*/ |
int fork_after_authentication_flag = 0; |
int fork_after_authentication_flag = 0; |
|
|
struct sockaddr_storage hostaddr; |
struct sockaddr_storage hostaddr; |
|
|
/* Private host keys. */ |
/* Private host keys. */ |
struct { |
Sensitive sensitive_data; |
Key **keys; |
|
int nkeys; |
|
} sensitive_data; |
|
|
|
/* Original real UID. */ |
/* Original real UID. */ |
uid_t original_real_uid; |
uid_t original_real_uid; |
|
uid_t original_effective_uid; |
|
|
/* command to be executed */ |
/* command to be executed */ |
Buffer command; |
Buffer command; |
|
|
/* Should we execute a command or invoke a subsystem? */ |
/* Should we execute a command or invoke a subsystem? */ |
int subsystem_flag = 0; |
int subsystem_flag = 0; |
|
|
|
/* # of replies received for global requests */ |
|
static int client_global_request_id = 0; |
|
|
|
/* pid of proxycommand child process */ |
|
pid_t proxy_command_pid = 0; |
|
|
/* Prints a help message to the user. This function never returns. */ |
/* Prints a help message to the user. This function never returns. */ |
|
|
static void |
static void |
|
|
fprintf(stderr, " -v Verbose; display verbose debugging messages.\n"); |
fprintf(stderr, " -v Verbose; display verbose debugging messages.\n"); |
fprintf(stderr, " Multiple -v increases verbosity.\n"); |
fprintf(stderr, " Multiple -v increases verbosity.\n"); |
fprintf(stderr, " -V Display version number only.\n"); |
fprintf(stderr, " -V Display version number only.\n"); |
fprintf(stderr, " -P Don't allocate a privileged port.\n"); |
|
fprintf(stderr, " -q Quiet; don't display any warning messages.\n"); |
fprintf(stderr, " -q Quiet; don't display any warning messages.\n"); |
fprintf(stderr, " -f Fork into background after authentication.\n"); |
fprintf(stderr, " -f Fork into background after authentication.\n"); |
fprintf(stderr, " -e char Set escape character; ``none'' = disable (default: ~).\n"); |
fprintf(stderr, " -e char Set escape character; ``none'' = disable (default: ~).\n"); |
|
|
exit(1); |
exit(1); |
} |
} |
|
|
/* |
|
* Connects to the given host using rsh (or prints an error message and exits |
|
* if rsh is not available). This function never returns. |
|
*/ |
|
static void |
|
rsh_connect(char *host, char *user, Buffer * command) |
|
{ |
|
char *args[10]; |
|
int i; |
|
|
|
log("Using rsh. WARNING: Connection will not be encrypted."); |
|
/* Build argument list for rsh. */ |
|
i = 0; |
|
args[i++] = _PATH_RSH; |
|
/* host may have to come after user on some systems */ |
|
args[i++] = host; |
|
if (user) { |
|
args[i++] = "-l"; |
|
args[i++] = user; |
|
} |
|
if (buffer_len(command) > 0) { |
|
buffer_append(command, "\0", 1); |
|
args[i++] = buffer_ptr(command); |
|
} |
|
args[i++] = NULL; |
|
if (debug_flag) { |
|
for (i = 0; args[i]; i++) { |
|
if (i != 0) |
|
fprintf(stderr, " "); |
|
fprintf(stderr, "%s", args[i]); |
|
} |
|
fprintf(stderr, "\n"); |
|
} |
|
execv(_PATH_RSH, args); |
|
perror(_PATH_RSH); |
|
exit(1); |
|
} |
|
|
|
static int ssh_session(void); |
static int ssh_session(void); |
static int ssh_session2(void); |
static int ssh_session2(void); |
static void load_public_identity_files(void); |
static void load_public_identity_files(void); |
|
|
int |
int |
main(int ac, char **av) |
main(int ac, char **av) |
{ |
{ |
int i, opt, exit_status, cerr; |
int i, opt, exit_status; |
u_short fwd_port, fwd_host_port; |
u_short fwd_port, fwd_host_port; |
char sfwd_port[6], sfwd_host_port[6]; |
char sfwd_port[6], sfwd_host_port[6]; |
char *p, *cp, buf[256]; |
char *p, *cp, buf[256]; |
struct stat st; |
struct stat st; |
struct passwd *pw; |
struct passwd *pw; |
int dummy; |
int dummy; |
uid_t original_effective_uid; |
|
extern int optind, optreset; |
extern int optind, optreset; |
extern char *optarg; |
extern char *optarg; |
|
|
|
|
original_real_uid = getuid(); |
original_real_uid = getuid(); |
original_effective_uid = geteuid(); |
original_effective_uid = geteuid(); |
|
|
|
/* |
|
* Use uid-swapping to give up root privileges for the duration of |
|
* option processing. We will re-instantiate the rights when we are |
|
* ready to create the privileged port, and will permanently drop |
|
* them when the port has been created (actually, when the connection |
|
* has been made, as we may need to create the port several times). |
|
*/ |
|
PRIV_END; |
|
|
/* If we are installed setuid root be careful to not drop core. */ |
/* If we are installed setuid root be careful to not drop core. */ |
if (original_real_uid != original_effective_uid) { |
if (original_real_uid != original_effective_uid) { |
struct rlimit rlim; |
struct rlimit rlim; |
|
|
pw = pwcopy(pw); |
pw = pwcopy(pw); |
|
|
/* |
/* |
* Use uid-swapping to give up root privileges for the duration of |
|
* option processing. We will re-instantiate the rights when we are |
|
* ready to create the privileged port, and will permanently drop |
|
* them when the port has been created (actually, when the connection |
|
* has been made, as we may need to create the port several times). |
|
*/ |
|
temporarily_use_uid(pw); |
|
|
|
/* |
|
* Set our umask to something reasonable, as some files are created |
* Set our umask to something reasonable, as some files are created |
* with the default umask. This will make them world-readable but |
* with the default umask. This will make them world-readable but |
* writable only by the owner, which is ok for all files for which we |
* writable only by the owner, which is ok for all files for which we |
|
|
case 'g': |
case 'g': |
options.gateway_ports = 1; |
options.gateway_ports = 1; |
break; |
break; |
case 'P': |
case 'P': /* deprecated */ |
options.use_privileged_port = 0; |
options.use_privileged_port = 0; |
break; |
break; |
case 'a': |
case 'a': |
|
|
av += optind; |
av += optind; |
|
|
if (ac > 0 && !host && **av != '-') { |
if (ac > 0 && !host && **av != '-') { |
if (strchr(*av, '@')) { |
if (strrchr(*av, '@')) { |
p = xstrdup(*av); |
p = xstrdup(*av); |
cp = strchr(p, '@'); |
cp = strrchr(p, '@'); |
if (cp == NULL || cp == p) |
if (cp == NULL || cp == p) |
usage(); |
usage(); |
options.user = p; |
options.user = p; |
|
|
host = ++cp; |
host = ++cp; |
} else |
} else |
host = *av; |
host = *av; |
ac--, av++; |
if (ac > 1) { |
if (ac > 0) { |
optind = optreset = 1; |
optind = 0; |
|
optreset = 1; |
|
goto again; |
goto again; |
} |
} |
|
ac--, av++; |
} |
} |
|
|
/* Check that we got a host name. */ |
/* Check that we got a host name. */ |
|
|
if (buffer_len(&command) == 0) |
if (buffer_len(&command) == 0) |
tty_flag = 1; |
tty_flag = 1; |
|
|
/* Force no tty*/ |
/* Force no tty */ |
if (no_tty_flag) |
if (no_tty_flag) |
tty_flag = 0; |
tty_flag = 0; |
/* Do not allocate a tty if stdin is not a tty. */ |
/* Do not allocate a tty if stdin is not a tty. */ |
|
|
if (options.hostname != NULL) |
if (options.hostname != NULL) |
host = options.hostname; |
host = options.hostname; |
|
|
|
if (options.proxy_command != NULL && |
|
strcmp(options.proxy_command, "none") == 0) |
|
options.proxy_command = NULL; |
|
|
/* Disable rhosts authentication if not running as root. */ |
/* Disable rhosts authentication if not running as root. */ |
if (original_effective_uid != 0 || !options.use_privileged_port) { |
if (original_effective_uid != 0 || !options.use_privileged_port) { |
debug("Rhosts Authentication disabled, " |
debug("Rhosts Authentication disabled, " |
"originating port will not be trusted."); |
"originating port will not be trusted."); |
options.rhosts_authentication = 0; |
options.rhosts_authentication = 0; |
} |
} |
/* |
|
* If using rsh has been selected, exec it now (without trying |
|
* anything else). Note that we must release privileges first. |
|
*/ |
|
if (options.use_rsh) { |
|
/* |
|
* Restore our superuser privileges. This must be done |
|
* before permanently setting the uid. |
|
*/ |
|
restore_uid(); |
|
|
|
/* Switch to the original uid permanently. */ |
|
permanently_set_uid(pw); |
|
|
|
/* Execute rsh. */ |
|
rsh_connect(host, options.user, &command); |
|
fatal("rsh_connect returned"); |
|
} |
|
/* Restore our superuser privileges. */ |
|
restore_uid(); |
|
|
|
/* Open a connection to the remote host. */ |
/* Open a connection to the remote host. */ |
|
|
cerr = ssh_connect(host, &hostaddr, options.port, IPv4or6, |
if (ssh_connect(host, &hostaddr, options.port, IPv4or6, |
options.connection_attempts, |
options.connection_attempts, |
original_effective_uid != 0 || !options.use_privileged_port, |
original_effective_uid == 0 && options.use_privileged_port, |
pw, options.proxy_command); |
options.proxy_command) != 0) |
|
exit(1); |
|
|
/* |
/* |
* If we successfully made the connection, load the host private key |
* If we successfully made the connection, load the host private key |
* in case we will need it later for combined rsa-rhosts |
* in case we will need it later for combined rsa-rhosts |
* authentication. This must be done before releasing extra |
* authentication. This must be done before releasing extra |
* privileges, because the file is only readable by root. |
* privileges, because the file is only readable by root. |
|
* If we cannot access the private keys, load the public keys |
|
* instead and try to execute the ssh-keysign helper instead. |
*/ |
*/ |
sensitive_data.nkeys = 0; |
sensitive_data.nkeys = 0; |
sensitive_data.keys = NULL; |
sensitive_data.keys = NULL; |
if (!cerr && (options.rhosts_rsa_authentication || |
sensitive_data.external_keysign = 0; |
options.hostbased_authentication)) { |
if (options.rhosts_rsa_authentication || |
|
options.hostbased_authentication) { |
sensitive_data.nkeys = 3; |
sensitive_data.nkeys = 3; |
sensitive_data.keys = xmalloc(sensitive_data.nkeys*sizeof(Key)); |
sensitive_data.keys = xmalloc(sensitive_data.nkeys * |
|
sizeof(Key)); |
|
|
|
PRIV_START; |
sensitive_data.keys[0] = key_load_private_type(KEY_RSA1, |
sensitive_data.keys[0] = key_load_private_type(KEY_RSA1, |
_PATH_HOST_KEY_FILE, "", NULL); |
_PATH_HOST_KEY_FILE, "", NULL); |
sensitive_data.keys[1] = key_load_private_type(KEY_DSA, |
sensitive_data.keys[1] = key_load_private_type(KEY_DSA, |
_PATH_HOST_DSA_KEY_FILE, "", NULL); |
_PATH_HOST_DSA_KEY_FILE, "", NULL); |
sensitive_data.keys[2] = key_load_private_type(KEY_RSA, |
sensitive_data.keys[2] = key_load_private_type(KEY_RSA, |
_PATH_HOST_RSA_KEY_FILE, "", NULL); |
_PATH_HOST_RSA_KEY_FILE, "", NULL); |
|
PRIV_END; |
|
|
|
if (options.hostbased_authentication == 1 && |
|
sensitive_data.keys[0] == NULL && |
|
sensitive_data.keys[1] == NULL && |
|
sensitive_data.keys[2] == NULL) { |
|
sensitive_data.keys[1] = key_load_public( |
|
_PATH_HOST_DSA_KEY_FILE, NULL); |
|
sensitive_data.keys[2] = key_load_public( |
|
_PATH_HOST_RSA_KEY_FILE, NULL); |
|
sensitive_data.external_keysign = 1; |
|
} |
} |
} |
/* |
/* |
* Get rid of any extra privileges that we may have. We will no |
* Get rid of any extra privileges that we may have. We will no |
|
|
* user's home directory if it happens to be on a NFS volume where |
* user's home directory if it happens to be on a NFS volume where |
* root is mapped to nobody. |
* root is mapped to nobody. |
*/ |
*/ |
|
seteuid(original_real_uid); |
|
setuid(original_real_uid); |
|
|
/* |
/* |
* Note that some legacy systems need to postpone the following call |
|
* to permanently_set_uid() until the private hostkey is destroyed |
|
* with RSA_free(). Otherwise the calling user could ptrace() the |
|
* process, read the private hostkey and impersonate the host. |
|
* OpenBSD does not allow ptracing of setuid processes. |
|
*/ |
|
permanently_set_uid(pw); |
|
|
|
/* |
|
* Now that we are back to our own permissions, create ~/.ssh |
* Now that we are back to our own permissions, create ~/.ssh |
* directory if it doesn\'t already exist. |
* directory if it doesn\'t already exist. |
*/ |
*/ |
|
|
if (mkdir(buf, 0700) < 0) |
if (mkdir(buf, 0700) < 0) |
error("Could not create directory '%.200s'.", buf); |
error("Could not create directory '%.200s'.", buf); |
|
|
/* Check if the connection failed, and try "rsh" if appropriate. */ |
|
if (cerr) { |
|
if (!options.fallback_to_rsh) |
|
exit(1); |
|
if (options.port != 0) |
|
log("Secure connection to %.100s on port %hu refused; " |
|
"reverting to insecure method", |
|
host, options.port); |
|
else |
|
log("Secure connection to %.100s refused; " |
|
"reverting to insecure method.", host); |
|
|
|
rsh_connect(host, options.user, &command); |
|
fatal("rsh_connect returned"); |
|
} |
|
/* load options.identity_files */ |
/* load options.identity_files */ |
load_public_identity_files(); |
load_public_identity_files(); |
|
|
|
|
signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE early */ |
signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE early */ |
|
|
/* Log into the remote system. This never returns if the login fails. */ |
/* Log into the remote system. This never returns if the login fails. */ |
ssh_login(sensitive_data.keys, sensitive_data.nkeys, |
ssh_login(&sensitive_data, host, (struct sockaddr *)&hostaddr, pw); |
host, (struct sockaddr *)&hostaddr, pw); |
|
|
|
/* We no longer need the private host keys. Clear them now. */ |
/* We no longer need the private host keys. Clear them now. */ |
if (sensitive_data.nkeys != 0) { |
if (sensitive_data.nkeys != 0) { |
|
|
|
|
exit_status = compat20 ? ssh_session2() : ssh_session(); |
exit_status = compat20 ? ssh_session2() : ssh_session(); |
packet_close(); |
packet_close(); |
|
|
|
/* |
|
* Send SIGHUP to proxy command if used. We don't wait() in |
|
* case it hangs and instead rely on init to reap the child |
|
*/ |
|
if (proxy_command_pid > 1) |
|
kill(proxy_command_pid, SIGHUP); |
|
|
return exit_status; |
return exit_status; |
} |
} |
|
|
|
|
FILE *f; |
FILE *f; |
int got_data = 0, i; |
int got_data = 0, i; |
char *display; |
char *display; |
|
struct stat st; |
|
|
*_proto = proto; |
*_proto = proto; |
*_data = data; |
*_data = data; |
proto[0] = data[0] = '\0'; |
proto[0] = data[0] = '\0'; |
if (options.xauth_location && (display = getenv("DISPLAY"))) { |
if (!options.xauth_location || |
|
(stat(options.xauth_location, &st) == -1)) { |
|
debug("No xauth program."); |
|
} else { |
|
if ((display = getenv("DISPLAY")) == NULL) { |
|
debug("x11_get_proto: DISPLAY not set"); |
|
return; |
|
} |
/* Try to get Xauthority information for the display. */ |
/* Try to get Xauthority information for the display. */ |
if (strncmp(display, "localhost:", 10) == 0) |
if (strncmp(display, "localhost:", 10) == 0) |
/* |
/* |
|
|
* XXX: "localhost" match to determine FamilyLocal |
* XXX: "localhost" match to determine FamilyLocal |
* is not perfect. |
* is not perfect. |
*/ |
*/ |
snprintf(line, sizeof line, "%.100s list unix:%s 2>" |
snprintf(line, sizeof line, "%s list unix:%s 2>" |
_PATH_DEVNULL, options.xauth_location, display+10); |
_PATH_DEVNULL, options.xauth_location, display+10); |
else |
else |
snprintf(line, sizeof line, "%.100s list %.200s 2>" |
snprintf(line, sizeof line, "%s list %.200s 2>" |
_PATH_DEVNULL, options.xauth_location, display); |
_PATH_DEVNULL, options.xauth_location, display); |
debug2("x11_get_proto %s", line); |
debug2("x11_get_proto: %s", line); |
f = popen(line, "r"); |
f = popen(line, "r"); |
if (f && fgets(line, sizeof(line), f) && |
if (f && fgets(line, sizeof(line), f) && |
sscanf(line, "%*s %511s %511s", proto, data) == 2) |
sscanf(line, "%*s %511s %511s", proto, data) == 2) |
|
|
if (!got_data) { |
if (!got_data) { |
u_int32_t rand = 0; |
u_int32_t rand = 0; |
|
|
|
log("Warning: No xauth data; using fake authentication data for X11 forwarding."); |
strlcpy(proto, "MIT-MAGIC-COOKIE-1", sizeof proto); |
strlcpy(proto, "MIT-MAGIC-COOKIE-1", sizeof proto); |
for (i = 0; i < 16; i++) { |
for (i = 0; i < 16; i++) { |
if (i % 4 == 0) |
if (i % 4 == 0) |
|
|
{ |
{ |
if (options.forward_agent) { |
if (options.forward_agent) { |
/* Clear agent forwarding if we don\'t have an agent. */ |
/* Clear agent forwarding if we don\'t have an agent. */ |
int authfd = ssh_get_authentication_socket(); |
if (!ssh_agent_present()) |
if (authfd < 0) |
|
options.forward_agent = 0; |
options.forward_agent = 0; |
else |
|
ssh_close_authentication_socket(authfd); |
|
} |
} |
} |
} |
|
|
|
|
len, (u_char *)buffer_ptr(&command), id); |
len, (u_char *)buffer_ptr(&command), id); |
} |
} |
|
|
|
void |
|
client_global_request_reply(int type, u_int32_t seq, void *ctxt) |
|
{ |
|
int i; |
|
|
|
i = client_global_request_id++; |
|
if (i >= options.num_remote_forwards) { |
|
debug("client_global_request_reply: too many replies %d > %d", |
|
i, options.num_remote_forwards); |
|
return; |
|
} |
|
debug("remote forward %s for: listen %d, connect %s:%d", |
|
type == SSH2_MSG_REQUEST_SUCCESS ? "success" : "failure", |
|
options.remote_forwards[i].port, |
|
options.remote_forwards[i].host, |
|
options.remote_forwards[i].host_port); |
|
if (type == SSH2_MSG_REQUEST_FAILURE) |
|
log("Warning: remote port forwarding failed for listen port %d", |
|
options.remote_forwards[i].port); |
|
} |
|
|
/* request pty/x11/agent/tcpfwd/shell for channel */ |
/* request pty/x11/agent/tcpfwd/shell for channel */ |
static void |
static void |
ssh_session2_setup(int id, void *arg) |
ssh_session2_setup(int id, void *arg) |
|
|
int interactive = 0; |
int interactive = 0; |
struct termios tio; |
struct termios tio; |
|
|
debug("ssh_session2_setup: id %d", id); |
debug2("ssh_session2_setup: id %d", id); |
|
|
if (tty_flag) { |
if (tty_flag) { |
struct winsize ws; |
struct winsize ws; |
|
|
debug("Sending subsystem: %.*s", len, (u_char *)buffer_ptr(&command)); |
debug("Sending subsystem: %.*s", len, (u_char *)buffer_ptr(&command)); |
channel_request_start(id, "subsystem", /*want reply*/ 1); |
channel_request_start(id, "subsystem", /*want reply*/ 1); |
/* register callback for reply */ |
/* register callback for reply */ |
/* XXX we asume that client_loop has already been called */ |
/* XXX we assume that client_loop has already been called */ |
dispatch_set(SSH2_MSG_CHANNEL_FAILURE, &client_subsystem_reply); |
dispatch_set(SSH2_MSG_CHANNEL_FAILURE, &client_subsystem_reply); |
dispatch_set(SSH2_MSG_CHANNEL_SUCCESS, &client_subsystem_reply); |
dispatch_set(SSH2_MSG_CHANNEL_SUCCESS, &client_subsystem_reply); |
} else { |
} else { |