/* $Id: ssh_main.c,v 1.38 2001/02/11 03:35:12 tls Exp $ */ /* * Copyright (c) 2000 Andrew Brown. * 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. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "options.h" #include "ssh_defines.h" #include "ssh_cipher.h" #include "ssh_logging.h" #include "ssh_auth.h" #include "ssh_buffer.h" #include "ssh_global.h" #include "ssh_util.h" #include "ssh_main.h" #include "ssh_types.h" #include "ssh_threads.h" #include "ssh_v1_child.h" #include "ssh_v1_proto.h" #include "pathnames.h" extern char *__progname; void initialize_client_options(ssh_context_t *); int ssh_setup(ssh_context_t *); int ssh_configuration(int, char *[], ssh_context_t *); void ssh_final_defaults(ssh_context_t *); int confighost(ssh_context_t *, char *, FILE *, char *); void configline(ssh_context_t *, char *, int, const char *, const char *, int, int); void ssh_show_version(void); void ssh_usage(void); int ssh_connect(ssh_context_t *); int ssh_main_loop(ssh_context_t *); int handle_socket(ssh_context_t *); void signal_handler(int); void signal_handler(int sig) { switch (sig) { case SIGTSTP: SSH_DLOG(3, ("handling tstp")); if (g_context.send_pid > 0) kill(g_context.send_pid, SIGSTOP); if (g_context.recv_pid > 0) kill(g_context.recv_pid, SIGSTOP); break; case SIGWINCH: SSH_DLOG(3, ("handling winch")); g_context.client->sigwinch = 1; break; default: exit(1); break; } } void initialize_client_options(ssh_context_t * context) { context->client = malloc(sizeof(struct ssh_client_options)); /* * things found in the config file(s). defaults are -1 for unset * integers, and null for unset pointers (of course). */ context->client->ForwardAgent = -1; context->client->ForwardX11 = -1; context->client->RhostsAuthentication = -1; context->client->RhostsRSAAuthentication = -1; context->client->RSAAuthentication = -1; context->client->TISAuthentication = -1; context->client->PasswordAuthentication = -1; context->client->FallBackToRsh = -1; context->client->UseRsh = -1; context->client->BatchMode = -1; context->client->StrictHostKeyChecking = -1; context->client->IdentityFile = NULL; context->client->Port = NULL; context->client->DefaultPort = NULL; context->client->Cipher = -1; context->client->EscapeChar = -1; /* things found in the config file that aren't there for "Host *" */ context->username = NULL; context->client->HostName = NULL; /* things that can be configured that aren't in config files (cli) */ context->client->StdinDevNull = 0; context->client->UseRemoteTTY = 0; context->client->Verbosity = 0; context->client->QuietMode = 0; context->client->InBackground = 0; context->client->NonPrivLocalPort = 0; context->client->UseCompression = 0; /* other things i found that fsecure does that aren't described */ context->client->CompressionLevel = -1; context->client->GlobalKnownHostsFile = NULL; context->client->UserKnownHostsFile = NULL; context->client->NumberOfPasswordPrompts = -1; context->client->PasswordPromptLogin = -1; context->client->PasswordPromptHost = -1; context->client->UserHostName = NULL; context->client->RemoteCommand = NULL; } int ssh_setup(ssh_context_t * context) { FUNC_DECL(ssh_setup); memset(context, 0, sizeof(*context)); ssh_sys_randinit(); channel_init(context); initialize_client_options(context); context->running_as_server = SSH_ROLE_CLIENT; context->protocol_version = SSH_V1; context->transport_layer = SSH_V1_TRANSPORT; context->thread_id = -1; memset(&context->opts, 0, sizeof(context->opts)); ssh_sys_create_eventq(context); context->disconnect_msg = NULL; memset(&context->v1_ctx, 0, sizeof(context->v1_ctx)); memset(&context->v2_ctx, 0, sizeof(context->v2_ctx)); set_supported_ciphers(&context->supported_ciphers); set_supported_auths(&context->supported_auths); memset(&context->transport_ctx, 0, sizeof(context->transport_ctx)); context->cipher = &g_cipher; g_cipher.type = -1; if (ssh_sys_lookupuid(context, getuid()) != 0) { /* init pwent member */ SSH_ERROR("who are you?"); exit(1); } context->username = NULL; /* will be remote user's name */ context->usepty = 0; if ((context->term = getenv("TERM")) == NULL) { SSH_ERROR("cannot determine local terminal type\n"); exit(1); } context->modes = NULL; context->msize = 0; context->child_pid = -1; context->send_pid = 0; context->recv_pid = 0; memset(&context->fdi, 0, sizeof(context->fdi)); context->nfds = 0; if (loginit("ssh", 2) != 0) { fprintf(stderr, "Unable to initialize logging.\n"); return (1); } #ifdef LEAVE_NO_CORES { struct rlimit rl; rl.rlim_cur = (rlim_t) 0; rl.rlim_max = (rlim_t) 0; setrlimit(RLIMIT_CORE, &rl); } #endif (context->client->localhost = malloc(256))[0] = 0; if (gethostname(context->client->localhost, 256) == -1 || context->client->localhost[0] == 0) { SSH_ERROR("cannot determine local host name"); strcpy(context->client->localhost, "debug"); } context->client->password = NULL; /* * we will behave somewhat differently if we're not reading from a * tty. or using a remote command. */ context->client->StdinIsTty = isatty(STDIN_FILENO); context->client->ttyfd = isatty(STDIN_FILENO) ? STDIN_FILENO : isatty(STDOUT_FILENO) ? STDOUT_FILENO : isatty(STDERR_FILENO) ? STDERR_FILENO : open(_PATH_TTY, O_RDWR); context->client->signal_handler = signal_handler; context->client->sigcont = 0; context->client->sigwinch = 0; context->client->exitwith = -1; /* if these fail, it's because we don't have a tty. oh well. */ tcgetattr(context->client->ttyfd, &context->client->tcap); ssh_sys_get_tty_size(context->client->ttyfd, &context->win); return (0); } int ssh_configuration(int argc, char *argv[], ssh_context_t * context) { FUNC_DECL(parse_command_line); int opt, l, i; FILE *conf; char *f, conffile[2][100]; #define USER_CONF 0 #define SYSTEM_CONF 1 /* * scan for -foo options... * then a [user@]hostname... * and continue eating -foo options after that */ if ( !strcmp(__progname, "fressh") || !strcmp(__progname, "ssh") || !strcmp(__progname, "slogin") || !strcmp(__progname, "rsh") || !strcmp(__progname, "rlogin") || !strcmp(__progname, "ssh_client") || 0) context->client->UserHostName = NULL; else context->client->UserHostName = __progname; do { while ((opt = getopt(argc, argv, "dl:naxi:tvVqfe:c:p:PL:R:Cgo:8h")) != EOF) { switch (opt) { case 'd': /* d - increment debuggery */ debug_inc(0); break; case 'l': /* l: - log in as user name */ configline(context, NULL, optind - 1, "User", optarg, 1, 0); break; case 'n': /* n - redirect stdin from /dev/null */ context->client->StdinDevNull = 1; break; case 'a': /* a - no auth forwarding */ configline(context, NULL, optind - 1, "ForwardAgent", "no", 1, 0); break; case 'x': /* x - no x forwarding */ configline(context, NULL, optind - 1, "ForwardX11", "no", 1, 0); break; case 'i': /* i: - identity file */ configline(context, NULL, optind - 1, "IdentityFile", optarg, 1, 0); break; case 't': /* t - force remote tty use */ context->client->UseRemoteTTY = 1; break; case 'v': /* v - verbose mode */ context->client->Verbosity++; break; case 'V': /* V - print version */ ssh_show_version(); exit(0); break; case 'q': /* q - quiet mode */ context->client->QuietMode = 1; break; case 'f': /* f - background mode */ context->client->InBackground = 1; break; case 'e': /* e: - escape character */ configline(context, NULL, optind - 1, "EscapeChar", optarg, 1, 0); break; case 'c': /* c: - cipher selection */ configline(context, NULL, optind - 1, "Cipher", optarg, 1, 0); break; case 'p': /* p: - port to connect to */ configline(context, NULL, optind - 1, "Port", optarg, 1, 0); break; case 'P': /* P - use non-provileged local port */ context->client->NonPrivLocalPort = 1; break; case 'L': /* L: - local forward */ SSH_ERROR("Forwarding not supported"); exit(1); break; case 'R': /* R: - remote forward */ SSH_ERROR("Forwarding not supported"); exit(1); break; case 'C': /* C - use compression */ #ifdef WITH_COMPRESSION context->client->UseCompression = 1; #else /* WITH_COMPRESSION */ fprintf(stderr, "Compression not supported.\r\n"); exit(1); #endif /* WITH_COMPRESSION */ break; case 'g': /* g - some forwarding nonsense */ SSH_ERROR("please tell me what this is supposed to do"); exit(1); break; case 'o':{ /* o: - command line 'file option' * processing */ char *m, *w, *v, *t; t = m = strdup(optarg); w = v = NULL; while (t && *t && isspace(*t)) *t++ = 0; if (t) t = strpbrk(w = t, " \t="); while (t && *t && (isspace(*t) || *t == '=')) *t++ = 0; if (t) t = strpbrk(v = t, " \t"); while (t && *t && isspace(*t)) *t++ = 0; if (!w || !*w || !v || !*v) { fprintf(stderr, "command line argument %d: " "Bad configuration option: %s\r\n", optind - 1, optarg); exit(1); } else configline(context, NULL, optind - 1, w, v, 1, 0); free(m); break; } case '8': /* 8 - eight bit clean flag * (rsh-compatibility) */ /* we are already... :) */ break; case 'h': default: ssh_usage(); } } /* * if we have no host name yet, use this arg, and advance, * possibly going back into getopts. options are allowed * before *and* after the [user@]host argument. */ if (context->client->UserHostName == NULL && argc > optind) { context->client->UserHostName = argv[optind++]; if ((f = strchr(context->client->UserHostName, '@')) != NULL) { *f = 0; configline(context, NULL, optind - 1, "User", context->client->UserHostName, 1, 0); context->client->UserHostName = f + 1; } } } while (argc > optind && argv[optind] && argv[optind][0] == '-'); if (context->client->Verbosity) ssh_show_version(); argc -= optind; argv += optind; if (argc) { char *r_cmd; for (l = 0, i = 0; i < argc; i++) l += strlen(argv[i]) + 1; /* + 1 for the space */ if ((r_cmd = malloc(l + 1)) == NULL) { SSH_ERROR("malloc failed"); exit(1); } r_cmd[0] = 0; for (i = 0; i < argc; i++) { strcat(r_cmd, argv[i]); strcat(r_cmd, " "); } r_cmd[l - 1] = 0; context->client->RemoteCommand = r_cmd; } if (context->client->UserHostName == NULL) return 1; /* after all, they didn't tell us where to go */ /* * now read config files looking for our host... * * first check the user's config file (and system config file) for * entries for this host (if found, skip the system file), then * check the user's config file for defaults, and then the system * config file for defaults. */ if (snprintf(conffile[USER_CONF], sizeof(conffile[USER_CONF]), "%s/%s/config", context->pwent.pw_dir, _PATH_SSH_USER_DIR) > sizeof(conffile[USER_CONF])) { SSH_ERROR("home directory too long"); exit(1); } if (snprintf(conffile[SYSTEM_CONF], sizeof(conffile[SYSTEM_CONF]), "/etc/ssh_config") > sizeof(conffile[SYSTEM_CONF])) { SSH_ERROR("config file name too long"); exit(1); } if ((conf = fopen(conffile[USER_CONF], "r")) != NULL) { if (context->client->Verbosity) fprintf(stderr, "%s: Reading configuration data %s\r\n", context->client->localhost, conffile[USER_CONF]); confighost(context, conffile[USER_CONF], conf, context->client->UserHostName); rewind(conf); confighost(context, conffile[USER_CONF], conf, NULL); fclose(conf); } if ((conf = fopen(conffile[SYSTEM_CONF], "r")) != NULL) { if (context->client->Verbosity) fprintf(stderr, "%s: Reading configuration data %s\r\n", context->client->localhost, conffile[SYSTEM_CONF]); confighost(context, conffile[SYSTEM_CONF], conf, context->client->UserHostName); rewind(conf); confighost(context, conffile[SYSTEM_CONF], conf, NULL); fclose(conf); } return 0; } void ssh_final_defaults(ssh_context_t * context) { FUNC_DECL(ssh_final_defaults); char *t; if (context->client->ForwardAgent == -1) context->client->ForwardAgent = def_ssh_ForwardAgent; if (context->client->ForwardX11 == -1) context->client->ForwardX11 = def_ssh_ForwardX11; if (context->client->RhostsAuthentication == -1) context->client->RhostsAuthentication = def_ssh_RhostsAuthentication; if (context->client->RhostsRSAAuthentication == -1) context->client->RhostsRSAAuthentication = def_ssh_RhostsRSAAuthentication; if (context->client->RSAAuthentication == -1) context->client->RSAAuthentication = def_ssh_RSAAuthentication; if (context->client->TISAuthentication == -1) context->client->TISAuthentication = def_ssh_TISAuthentication; if (context->client->PasswordAuthentication == -1) context->client->PasswordAuthentication = def_ssh_PasswordAuthentication; if (context->client->FallBackToRsh == -1) context->client->FallBackToRsh = def_ssh_FallBackToRsh; if (context->client->UseRsh == -1) context->client->UseRsh = def_ssh_UseRsh; if (context->client->BatchMode == -1) context->client->BatchMode = def_ssh_BatchMode; if (context->client->StrictHostKeyChecking == -1) context->client->StrictHostKeyChecking = def_ssh_StrictHostKeyChecking; if (context->client->IdentityFile == NULL) /* use strdup here, since i need a "writable string" later on */ context->client->IdentityFile = strdup(def_ssh_IdentityFile); if (context->client->Port == NULL) context->client->Port = def_ssh_Port; if (context->client->Cipher == -1) context->client->Cipher = def_ssh_Cipher; if (context->client->EscapeChar == -1) context->client->EscapeChar = def_ssh_EscapeChar; if (context->username == NULL) context->username = context->pwent.pw_name; if (context->client->HostName == NULL) context->client->HostName = context->client->UserHostName; if (context->client->CompressionLevel == -1) context->client->CompressionLevel = def_ssh_CompressionLevel; if (context->client->GlobalKnownHostsFile == NULL) context->client->GlobalKnownHostsFile = def_ssh_GlobalKnownHostsFile; if (context->client->UserKnownHostsFile == NULL) context->client->UserKnownHostsFile = def_ssh_UserKnownHostsFile; if (context->client->NumberOfPasswordPrompts == -1) context->client->NumberOfPasswordPrompts = def_ssh_NumberOfPasswordPrompts; if (context->client->PasswordPromptLogin == -1) context->client->PasswordPromptLogin = def_ssh_PasswordPromptLogin; if (context->client->PasswordPromptHost == -1) context->client->PasswordPromptHost = def_ssh_PasswordPromptHost; /* * note that we are willing to expand ~ into the user's home * directory, but we're *not* willing to go trawling all over the * place looking for ~foo. */ #define TILDE_EXPAND(c, s) \ do { \ if (strncmp((s), "~/", 2) == 0) { \ int te_l = strlen(s) + strlen((c)->pwent.pw_dir); \ char *te_t = malloc(te_l); \ snprintf(te_t, te_l, "%s%s", (c)->pwent.pw_dir, \ (s) + 1); \ (s) = te_t; \ } \ } while (0) TILDE_EXPAND(context, context->client->IdentityFile); TILDE_EXPAND(context, context->client->UserKnownHostsFile); /* * decide based on the names of the files what the public key is, * since f-secure names things somewhat differently. this is a * little shady, but it's the best we can do on short notice. */ if ((t = strstr(context->client->IdentityFile, ".priv")) != NULL && t[5] == 0) { /* looks like a fressh file name */ context->client->PublicIdentityFile = malloc(strlen(context->client->IdentityFile)); *t = 0; sprintf(context->client->PublicIdentityFile, "%s.pub", context->client->IdentityFile); *t = '.'; } else { /* i guess it's a f-secure file */ context->client->PublicIdentityFile = malloc(strlen(context->client->IdentityFile) + 5); sprintf(context->client->PublicIdentityFile, "%s.pub", context->client->IdentityFile); } /* the use of some options forces some others */ if (!context->client->RemoteCommand) context->client->UseRemoteTTY = 1; if (context->client->InBackground) context->client->StdinDevNull = 1; if (context->client->StdinDevNull) context->client->StdinIsTty = 0; } int confighost(ssh_context_t * context, char *file, FILE * conf, char *host) { FUNC_DECL(confighost); char line[1024]; char *lhost, *word, *val, *nval; int n; lhost = NULL; n = 0; while (fgets(line, sizeof(line), conf) != NULL) { n++; /* might be a comment */ if (line[0] == '#') continue; /* skip white space until keyword */ for (word = line; isblank(word[0]) && word < line + sizeof(line); word++); /* values start after the keyword... */ if ((val = strpbrk(word, " \t")) == NULL) continue; /* ...and some more white space */ for (; isblank(val[0]) && val < line + sizeof(line); val++) val[0] = 0; /* we might have found another host line after we've used one */ if (strcasecmp(word, "Host") == 0 && lhost) break; /* so...process all values for this line (only Host has many) */ while (val[0]) { /* where does the next one start? */ if ((nval = strpbrk(val, " \t\r\n")) != NULL) nval++[0] = 0; /* if the host name matches, save it */ if (strcasecmp(word, "Host") == 0 && ((host != NULL && strcasecmp(val, host) == 0) || (host == NULL && strcasecmp(val, "*") == 0))) { lhost = strdup(val); /* we have a host-based * match */ if (context->client->Verbosity) fprintf(stderr, "%s: Applying options for %s\r\n", context->client->localhost, val); break; /* and can skip to the next line */ } /* for a match, use config lines */ if (lhost) configline(context, file, n, word, val, 0, host != NULL); /* retrieve next value */ for (val = nval; isblank(val[0]) && val < line + sizeof(line); val++) val[0] = 0; } } /* we were looking for a host and we found it */ return (host != NULL && lhost != NULL); } void configline(ssh_context_t * context, char *file, int line, const char *word, const char *val, int forceit, int doinghost) { FUNC_DECL(configline); const char *problem; /* sanity check */ if (!word || !val) return; /* * basically, we only want to set these things when they are * defaults (host is null so we found them in a 'Host *' section) * or when the option has not been previously set (host is set to * the host we want, and the config file is being scanned for that * host */ if (strcasecmp(word, "ForwardAgent") == 0) { if (forceit || context->client->ForwardAgent == -1) if ((context->client->ForwardAgent = is_yes_or_no(val)) == -1) { problem = "Bad yes/no argument for"; goto bad; } } else if (strcasecmp(word, "ForwardX11") == 0) { if (forceit || context->client->ForwardX11 == -1) if ((context->client->ForwardX11 = is_yes_or_no(val)) == -1) { problem = "Bad yes/no argument for"; goto bad; } } else if (strcasecmp(word, "RhostsAuthentication") == 0) { if (forceit || context->client->RhostsAuthentication == -1) if ((context->client->RhostsAuthentication = is_yes_or_no(val)) == -1) { problem = "Bad yes/no argument for"; goto bad; } } else if (strcasecmp(word, "RhostsRSAAuthentication") == 0) { if (forceit || context->client->RhostsRSAAuthentication == -1) if ((context->client->RhostsRSAAuthentication = is_yes_or_no(val)) == -1) { problem = "Bad yes/no argument for"; goto bad; } } else if (strcasecmp(word, "RSAAuthentication") == 0) { if (forceit || context->client->RSAAuthentication == -1) if ((context->client->RSAAuthentication = is_yes_or_no(val)) == -1) { problem = "Bad yes/no argument for"; goto bad; } } else if (strcasecmp(word, "TISAuthentication") == 0) { if (forceit || context->client->TISAuthentication == -1) if ((context->client->TISAuthentication = is_yes_or_no(val)) == -1) { problem = "Bad yes/no argument for"; goto bad; } } else if (strcasecmp(word, "PasswordAuthentication") == 0) { if (forceit || context->client->PasswordAuthentication == -1) if ((context->client->PasswordAuthentication = is_yes_or_no(val)) == -1) { problem = "Bad yes/no argument for"; goto bad; } } else if (strcasecmp(word, "FallBackToRsh") == 0) { if (forceit || context->client->FallBackToRsh == -1) if ((context->client->FallBackToRsh = is_yes_or_no(val)) == -1) { problem = "Bad yes/no argument for"; goto bad; } } else if (strcasecmp(word, "UseRsh") == 0) { if (forceit || context->client->UseRsh == -1) if ((context->client->UseRsh = is_yes_or_no(val)) == -1) { problem = "Bad yes/no argument for"; goto bad; } } else if (strcasecmp(word, "BatchMode") == 0) { if (forceit || context->client->BatchMode == -1) if ((context->client->BatchMode = is_yes_or_no(val)) == -1) { problem = "Bad yes/no argument for"; goto bad; } } else if (strcasecmp(word, "StrictHostKeyChecking") == 0) { if (forceit || context->client->StrictHostKeyChecking == -1) if ((context->client->StrictHostKeyChecking = is_yes_or_no_or_ask(val)) == -1) { problem = "Bad yes/no/ask argument for"; goto bad; } } else if (strcasecmp(word, "IdentityFile") == 0) { if (forceit || context->client->IdentityFile == NULL) /* no error here...we only find out later if it * doesn't exist */ context->client->IdentityFile = strdup(val); } else if (strcasecmp(word, "Port") == 0) { if (forceit || context->client->Port == NULL) { context->client->Port = strdup(val); } } else if (strcasecmp(word, "Cipher") == 0) { if (forceit || context->client->Cipher == -1) if ((context->client->Cipher = cipher_number(val)) == -1) { problem = "Bad cipher"; goto bad; } } else if (strcasecmp(word, "EscapeChar") == 0) { if (forceit || context->client->EscapeChar == -1) { /* * XXX should maybe use unvis on the string * first, no? */ if (strcasecmp("none", val) == 0) context->client->EscapeChar = NO_ESCAPE_CHAR; else context->client->EscapeChar = *val; } } else if (strcasecmp(word, "User") == 0) { char *t; if (forceit || context->username == NULL) context->username = strdup(val); /* username[:password] support */ if ((t = strchr(context->username, ':')) != NULL) { *t = 0; context->client->password = t + 1; } } else if (strcasecmp(word, "HostName") == 0) { /* * we don't want these from 'Host *' sections or the command * line. not because you can't do it (f-secure does), but * because it's rather stupid. * * as a result, we *only* set it if we're reading a config * file *and* looking to expand on the hostname the user gave * us. the command line hostname argument sets the user * visible hostname...not the name of the thing we're actually * gonna try to connect to. it's like cnames and other * aliasing methods. */ if (doinghost && context->client->HostName == NULL) context->client->HostName = strdup(val); } else if (strcasecmp(word, "CompressionLevel") == 0) { #ifdef WITH_COMPRESSION if (forceit || context->client->CompressionLevel == -1) { int t = atoi(val); if (t < 0 || t > 9) { problem = "Bad compression level (should 1-9):"; word = val; goto bad; } else context->client->CompressionLevel = t; } #else /* WITH_COMPRESSION */ fprintf(stderr, "Compression not supported.\r\n"); exit(1); #endif /* WITH_COMPRESSION */ } else if (strcasecmp(word, "GlobalKnownHostsFile") == 0) { if (forceit || context->client->GlobalKnownHostsFile == NULL) context->client->GlobalKnownHostsFile = strdup(val); } else if (strcasecmp(word, "UserKnownHostsFile") == 0) { if (forceit || context->client->UserKnownHostsFile == NULL) context->client->UserKnownHostsFile = strdup(val); } else if (strcasecmp(word, "NumberOfPasswordPrompts") == 0) { if (forceit || context->client->NumberOfPasswordPrompts == -1) { int t = atoi(val); if (t < 0) { problem = "Bad number of password prompts"; word = val; goto bad; } else context->client->NumberOfPasswordPrompts = t; } } else if (strcasecmp(word, "PasswordPromptLogin") == 0) { if (forceit || context->client->PasswordPromptLogin == -1) if ((context->client->PasswordPromptLogin = is_yes_or_no(val)) == -1) { problem = "Bad yes/no argument for"; goto bad; } } else if (strcasecmp(word, "PasswordPromptHost") == 0) { if (forceit || context->client->PasswordPromptHost == -1) if ((context->client->PasswordPromptHost = is_yes_or_no(val)) == -1) { problem = "Bad yes/no argument for"; goto bad; } } else { problem = "Bad configuration option:"; bad: if (file) fprintf(stderr, "%s line %d: %s %s\r\n", file, line, problem, word); else fprintf(stderr, "command line argument %d: %s %s\r\n", line, problem, word); exit(1); goto bad; } } void ssh_show_version(void) { fprintf(stderr, "%s, protocol version %d.%d.\n" "Standard version. Does not use RSAREF.\n", SSHD_REV, V1_PROTO_MAJOR, V1_PROTO_MINOR); } void ssh_usage(void) { FUNC_DECL(ssh_usage); fprintf(stderr, "%s [options] host [command]\n", __progname); fprintf(stderr, "Options:\n" " -l user Log in using this user name.\n" " -n Redirect input from /dev/null.\n" " -a Disable authentication agent forwarding.\n" " -x Disable X11 connection forwarding.\n" " -i file Identity for RSA authentication (default: %s).\n" " -t Tty; allocate a tty even if command is given.\n" " -v Verbose; display verbose debugging messages.\n" " -V Display version number only.\n" " -q Quiet; don't display any warning messages.\n" " -f Fork into background after authentication.\n" " -e char Set escape character; ``none'' = disable (default: %c).\n" " -c cipher Select encryption algorithm:\n", def_ssh_IdentityFile, def_ssh_EscapeChar); #ifdef WITH_CIPHER_NONE printf("\t\tnone\n"); #endif /* WITH_CIPHER_NONE */ #ifdef WITH_CIPHER_IDEA printf("\t\tidea\n"); #endif /* WITH_CIPHER_IDEA */ #ifdef WITH_CIPHER_DES printf("\t\tdes\n"); #endif /* WITH_CIPHER_DES */ #ifdef WITH_CIPHER_3DES printf("\t\t3des\n"); printf("\t\tdes3\n"); #endif /* WITH_CIPHER_3DES */ #ifdef WITH_CIPHER_RC4 printf("\t\trc4\n"); #endif /* WITH_CIPHER_RC4 */ #ifdef WITH_CIPHER_BLOWFISH printf("\t\tblowfish\n"); printf("\t\tfishblow\n"); #endif /* WITH_CIPHER_BLOWFISH */ fprintf(stderr, " -p port Connect to this port. Server must be on the same port.\n" " -P Don't use privileged source port.\n" " -L listen-port:host:port Forward local port to remote address\n" " -R listen-port:host:port Forward remote port to local address\n" " These cause ssh to listen for connections on a port, and\n" " forward them to the other side by connecting to host:port.\n" #ifdef WITH_COMPRESSION " -C Enable compression.\n" #endif /* WITH_COMPRESSION */ " -g Allow remote hosts to connect to local port forwardings.\n" " -o 'option' Process the option as if it was read from a configuration file.\n"); fprintf(stderr, " -h This help message.\n"); exit(1); } int ssh_connect(ssh_context_t * context) { FUNC_DECL(ssh_connect); struct addrinfo hints, *res, *res0; int s, tries, error, sobuf; char addrbuf[NI_MAXHOST], portnum[NI_MAXSERV]; const char *cause = NULL; #define MAXTRIES 4 memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; #if 0 /* XXX ...and stash it in HostName? */ hints.ai_flags = AI_CANONNAME; #endif /* calculate default port number, and save it as a number */ context->client->DefaultPort = SSHD_SERVICE; if (getaddrinfo(NULL, context->client->DefaultPort, &hints, &res0)) { context->client->DefaultPort = SSHD_PORT; getaddrinfo(NULL, context->client->DefaultPort, &hints, &res0); } getnameinfo(res0->ai_addr, res0->ai_addrlen, NULL, 0, portnum, sizeof(portnum), NI_NUMERICSERV); context->client->DefaultPort = strdup(portnum); error = getaddrinfo(context->client->HostName, context->client->Port, &hints, &res0); if (error) { SSH_ERROR("%s (%s): %s\n", context->client->UserHostName, context->client->HostName, gai_strerror(error)); exit(1); } /* convert port number as given to a number */ getnameinfo(res0->ai_addr, res0->ai_addrlen, NULL, 0, portnum, sizeof(portnum), NI_NUMERICSERV); context->client->Port = strdup(portnum); for (tries = 0; tries < MAXTRIES;) { for (s = -1, res = res0; res != NULL; res = res->ai_next) { s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (s < 0) { cause = "socket"; continue; } /* deal with getting a privileged local port */ if (context->client->Verbosity) { fprintf(stderr, "%s: Connecting to %s [%s] port %s.\r\n", context->client->localhost, context->client->HostName, ssh_sockaddr_ntop(res->ai_addr, addrbuf, sizeof(addrbuf)), context->client->Port); } if (connect(s, res->ai_addr, res->ai_addrlen) < 0) { if (context->client->Verbosity) fprintf(stderr, "%s: connect: %s\r\n", context->client->localhost, strerror(errno)); cause = "connect"; close(s); s = -1; continue; } /* Got one. */ break; } gettimeofday(&tv_start, NULL); sobuf = 65536; setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sobuf, sizeof(sobuf)); setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sobuf, sizeof(sobuf)); if (s > 0 || ++tries == MAXTRIES) break; sleep(1); if (context->client->Verbosity) fprintf(stderr, "%s: Trying again...\r\n", context->client->localhost); continue; } if (s < 0) { if (cause == NULL) SSH_ERROR("Connection failed, no suitable addresses\n"); else SSH_ERROR("Connection failed: %s: %s\n", cause, strerror(errno)); } else { if (context->client->Verbosity) fprintf(stderr, "%s: Connection established.\r\n", context->client->localhost); #ifdef SA_LEN memcpy(&context->saddr, res->ai_addr, SA_LEN(res->ai_addr)); #else memcpy(&context->saddr, res->ai_addr, res->ai_addr->sa_len); #endif } freeaddrinfo(res0); context->transport_ctx.commsock = s; return s; } static void clientsignals(ssh_context_t *context, pid_t whoami) { struct sigaction sa; /* * SIGTSTP - not handled. The send thread finds a \n~^z on stdin * and sends me a SIGSTOP instead. */ /* * SIGWINCH - if stdinistty, caught by parent, messaged to child. */ if (whoami == context->recv_pid && context->client->UseRemoteTTY) { memset(&sa, 0, sizeof(sa)); sa.sa_handler = context->client->signal_handler; sa.sa_flags = SA_NOCLDSTOP; sigaction(SIGWINCH, &sa, NULL); } /* * SIGCHLD - caught by parent. */ if (whoami == context->recv_pid) setup_sigchld_handler(context); /* * SIGPIPE - handed by both. */ setup_sigpipe_handler(context); } static void ssh_client_loop(ssh_context_t *context) { FUNC_DECL(ssh_client_loop); struct ssh_version version; struct timeval tv; if (ssh_sys_create_eventq(context) < 0) { SSH_ERROR("Unable to create event queue!\n"); close(context->transport_ctx.commsock); ssh_sys_exit(1); } /* * Get the receive thread pid now so that both sides have it. */ context->recv_pid = getpid(); /* * We set up signal handlers later, since each thread has different * ones. */ if (get_version(context, &version) != 0) { close(context->transport_ctx.commsock); ssh_sys_exit(1); } if (context->client->Verbosity) fprintf(stderr, "%s: Remote protocol version %d.%d, " "remote software version %s\r\n", context->client->localhost, version.v_major, version.v_minor, version.v_vendor); if (context->client->Verbosity) fprintf(stderr, "%s: Initializing randomness.\r\n", context->client->localhost); gettimeofday(&tv, NULL); ssh_sys_randadd(); ssh_sys_randadd(); switch ((context->send_pid = fork())) { case -1: SSH_ERROR("Unable to fork\n"); ssh_sys_exit(1); break; case 0: if (loginit(NULL, 2) != 0) { SSH_ERROR("Unable to re-initialize logging.\n"); kill(context->recv_pid, SIGINT); break; } clientsignals(context, context->send_pid = getpid()); ssh_send_thread(context); break; default: clientsignals(context, context->recv_pid); ssh_recv_thread(context, dispatch_v1_client_msg); break; } /* * Okay, we're all done: * * - Collect the child. * - Reset the TTY. * - Indicate that the connection is closed. */ signal(SIGCHLD, SIG_DFL); while (wait(NULL) < 0) { if (errno == EINTR) continue; if (errno == ECHILD) break; } if (tcsetattr(context->client->ttyfd, TCSANOW, &context->client->tcap) == -1) SSH_ERROR("Unable to reset local tty state: %s", strerror(errno)); if (!context->client->RemoteCommand) fprintf(stderr, "Connection to %s closed.\r\n", context->client->UserHostName); } int main(int argc, char *argv[]) { FUNC_DECL(main); ssh_context_t *context = &g_context; /* start up a couple of things, and punish the context */ if (ssh_setup(context) != 0) ssh_usage(); /* handle command line and read config files */ if (ssh_configuration(argc, argv, context) != 0) ssh_usage(); /* anything we need to know that wasn't set yet */ ssh_final_defaults(context); /* try to connect to the remote host */ if (ssh_connect(context) == -1) { printf("Secure connection to %s on port %s refused.\n", context->client->UserHostName, context->client->Port); exit(1); } ssh_client_loop(context); if (context->client->Verbosity) { /* fprintf(stderr, "%s: Transferred: stdin 5, stdout 423, * stderr 36 bytes in 5.9 seconds\r\n", * context->client->localhost); */ /* fprintf(stderr, "%s: Bytes per second: stdin 0.8, stdout * 71.2, stderr 6.1\r\n", context->client->localhost); */ fprintf(stderr, "%s: Exit status %d\r\n", context->client->localhost, context->client->exitwith); /* fprintf(stderr, "%s: compress outgoing: raw data 159, * compressed 158, factor 0.99\r\n", * context->client->localhost); */ /* fprintf(stderr, "%s: compress incoming: raw data 504, * compressed 397, factor 0.79\r\n", * context->client->localhost); */ } SSH_DLOG(4, ("fin\n")); exit(context->client->exitwith); }