/* * options.c - handle pre-connection options * * This file is part of the SSH Library * * Copyright (c) 2003-2008 by Aris Adamantiadis * * The SSH Library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or (at your * option) any later version. * * The SSH Library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the SSH Library; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. * * vim: ts=2 sw=2 et cindent */ #include #include #include #include #ifndef _WIN32 #include #endif #include #include "libssh/priv.h" /** \defgroup ssh_options SSH Options * \brief options settings for a new SSH session */ /** \addtogroup ssh_options * @{ */ /** This structure is freed automaticaly by ssh_disconnect() * when you use it. \n * It can be used by only one ssh_connect(), not more.\n * also by default, ssh1 support is not allowed * * \brief initializes a new option structure * \returns an empty intialized option structure. * \see ssh_options_getopt() */ SSH_OPTIONS *ssh_options_new(void) { SSH_OPTIONS *option; option = malloc(sizeof(SSH_OPTIONS)); if (option == NULL) { return NULL; } memset(option,0,sizeof(SSH_OPTIONS)); option->port=22; /* set the default port */ option->fd=-1; option->ssh2allowed=1; #ifdef HAVE_SSH1 option->ssh1allowed=1; #else option->ssh1allowed=0; #endif option->bindport=22; return option; } /** * @brief Set port to connect or to bind for a connection. * * @param opt The options structure to use. * * @param port The port to connect or to bind. * * @return 0 on success, < 0 on error. */ int ssh_options_set_port(SSH_OPTIONS *opt, unsigned int port) { if (opt == NULL) { return -1; } opt->port = port & 0xffff; opt->bindport = port & 0xffff; return 0; } /** * @brief Duplicate an option structure. * * If you make several sessions with the same options this is useful. You * cannot use twice the same option structure in ssh_session_connect. * * @param opt Option structure to copy. * * @returns New copied option structure, NULL on error. * * @see ssh_session_connect() */ SSH_OPTIONS *ssh_options_copy(SSH_OPTIONS *opt) { SSH_OPTIONS *new = NULL; int i; if (opt == NULL) { return NULL; } new = ssh_options_new(); if (new == NULL) { return NULL; } if (opt->username) { new->username = strdup(opt->username); if (new->username == NULL) { goto err; } } if (opt->host) { new->host = strdup(opt->host); if (new->host == NULL) { goto err; } } if (opt->bindaddr) { new->host = strdup(opt->bindaddr); if (new->host == NULL) { goto err; } } if (opt->identity) { new->identity=strdup(opt->identity); if (new->identity == NULL) { return NULL; } } if (opt->ssh_dir) { new->ssh_dir = strdup(opt->ssh_dir); if (new->ssh_dir == NULL) { goto err; } } if (opt->known_hosts_file) { new->known_hosts_file = strdup(opt->known_hosts_file); if (new->known_hosts_file == NULL) { goto err; } } if (opt->dsakey) { new->dsakey = strdup(opt->dsakey); if (new->dsakey == NULL) { goto err; } } if (opt->rsakey) { new->rsakey = strdup(opt->rsakey); if (new->rsakey == NULL) { goto err; } } for (i = 0; i < 10; ++i) { if (opt->wanted_methods[i]) { new->wanted_methods[i] = strdup(opt->wanted_methods[i]); if (new->wanted_methods[i] == NULL) { goto err; } } } new->fd = opt->fd; new->port = opt->port; new->auth_function = opt->auth_function; new->auth_userdata = opt->auth_userdata; new->connect_status_function = opt->connect_status_function; new->connect_status_arg = opt->connect_status_arg; new->timeout = opt->timeout; new->timeout_usec = opt->timeout_usec; new->ssh2allowed = opt->ssh2allowed; new->ssh1allowed = opt->ssh1allowed; new->log_function = opt->log_function; new->log_verbosity = opt->log_verbosity; return new; err: ssh_options_free(new); return NULL; } /** * @brief Frees an option structure. * * @param opt Option structure to free. */ void ssh_options_free(SSH_OPTIONS *opt) { int i; if (opt == NULL) { return; } /* * We don't touch the banner. If the implementation * did use it, they have to free it */ SAFE_FREE(opt->username); SAFE_FREE(opt->host); SAFE_FREE(opt->identity); SAFE_FREE(opt->bindaddr); SAFE_FREE(opt->ssh_dir); SAFE_FREE(opt->known_hosts_file); SAFE_FREE(opt->dsakey); SAFE_FREE(opt->rsakey); for (i = 0; i < 10; i++) { if (opt->wanted_methods[i]) { free(opt->wanted_methods[i]); } } ZERO_STRUCTP(opt); SAFE_FREE(opt); } /** * @brief Set destination hostname * * @param opt The option structure to use. * * @param hostname The host name to connect. * * @return 0 on succes, < 0 on error. */ int ssh_options_set_host(SSH_OPTIONS *opt, const char *hostname){ char *h; char *p; if (opt == NULL || hostname == NULL) { return -1; } h = strdup(hostname); if (h == NULL) { return -1; } p = strchr(h, '@'); SAFE_FREE(opt->host); if (p) { *p = '\0'; opt->host = strdup(p + 1); if (opt->host == NULL) { SAFE_FREE(h); return -1; } SAFE_FREE(opt->username); opt->username = strdup(h); SAFE_FREE(h); if (opt->username == NULL) { return -1; } } else { opt->host = h; } return 0; } /** * @brief Set the username for authentication * * @param opt The options structure to use. * * @param username The username to authenticate. * * @return 0 on success, -1 on error. * * @bug this should not be set at options time */ int ssh_options_set_username(SSH_OPTIONS *opt, const char *username) { if (opt == NULL || username == NULL) { return -1; } SAFE_FREE(opt->username); opt->username = strdup(username); if (opt->username == NULL) { return -1; } return 0; } /** * @brief Set a file descriptor for connection. * * If you wish to open the socket yourself for a reason or another, set the * file descriptor. Don't forget to use ssh_option_set_hostname() as the * hostname is used as a key in the known_host mechanism. * * @param opt The options structure to use. * * @param fd An opened file descriptor to use. * * @return 0 on success, < 0 on error. */ int ssh_options_set_fd(SSH_OPTIONS *opt, socket_t fd) { if (opt == NULL) { return -1; } opt->fd = fd; return 0; } /** * @brief Set the local address and port binding. * * In case your client has multiple IP adresses, select the local address and * port to use for the socket.\n * If the address or port is not bindable, it may be impossible to connect. * * @param opt The options structure to use. * * @param bindaddr The bind address in form of hostname or ip address. * * @param port The port number to bind. * * @return 0 on success, < 0 on error. */ int ssh_options_set_bind(SSH_OPTIONS *opt, const char *bindaddr, int port) { if (opt == NULL || bindaddr == NULL) { return -1; } SAFE_FREE(opt->bindaddr); opt->bindaddr = strdup(bindaddr); if (opt->bindaddr == NULL) { return -1; } opt->bindport = port; return 0; } /** * @brief Set the ssh directory. * * The ssh directory is used for files like known_hosts and identity (public * and private keys) * * @param opt The options structure to use. * * @param dir The directory to set. It may include "%s" which will be * replaced by the user home directory. * * @return 0 on success, < 0 on error. * * @see ssh_options_set_user_home_dir() */ int ssh_options_set_ssh_dir(SSH_OPTIONS *opt, const char *dir) { char buffer[1024] = {0}; if (opt == NULL || dir == NULL) { return -1; } snprintf(buffer, 1024, dir, ssh_get_user_home_dir()); SAFE_FREE(opt->ssh_dir); opt->ssh_dir = strdup(buffer); if (opt->ssh_dir == NULL) { return -1; } return 0; } /** * @brief Set the known hosts file name. * * The known hosts file is used to certify remote hosts are genuine. * * @param opt The options structure to use. * * @param dir The path to the file including its name. "%s" will be * substitued with the user home directory. * * @return 0 on success, < 0 on error. * * @see ssh_options_set_user_home_dir() */ int ssh_options_set_known_hosts_file(SSH_OPTIONS *opt, const char *dir){ char buffer[1024] = {0}; if (opt == NULL || dir == NULL) { return -1; } snprintf(buffer, 1024, dir, ssh_get_user_home_dir()); SAFE_FREE(opt->known_hosts_file); opt->known_hosts_file = strdup(buffer); if (opt->known_hosts_file == NULL) { return -1; } return 0; } /** * @brief Set the identity file name. * * The identity file is used authenticate with public key. * * @param opt The options structure to use. * * @param identity The path to the file including its name. "%s" will be * substitued with the user home directory. * * @return 0 on success, < 0 on error. * * @see ssh_options_set_user_home_dir() */ int ssh_options_set_identity(SSH_OPTIONS *opt, const char *identity){ char buffer[1024] = {0}; if (opt == NULL || identity == NULL) { return -1; } snprintf(buffer, 1024, identity, ssh_get_user_home_dir()); SAFE_FREE(opt->identity); opt->identity = strdup(buffer); if (opt->identity == NULL) { return -1; } return 0; } /** \warning I don't remember what these functions are supposed * to set */ void ssh_options_set_dsa_server_key(SSH_OPTIONS *opt, const char *dsakey){ opt->dsakey=strdup(dsakey); } /** \warning I don't remember what these functions are supposed * to set */ void ssh_options_set_rsa_server_key(SSH_OPTIONS *opt, const char *rsakey){ opt->rsakey=strdup(rsakey); } /** * @brief Set the server banner sent to clients. * * @param opt The options structure to use. * * @param banner A text banner to be shown. * * @return 0 on success, < 0 on error. */ int ssh_options_set_banner(SSH_OPTIONS *opt, const char *banner) { if (opt == NULL || banner == NULL) { return -1; } SAFE_FREE(opt->banner); opt->banner = strdup(banner); if (opt->banner == NULL) { return -1; } return 0; } /** * @brief Set the algorithms to be used for cryptography and compression. * * The methods are:\n * KEX_HOSTKEY (server public key type) : ssh-rsa or ssh-dss\n * KEX_CRYPT_C_S (symmetric cipher client to server)\n * KEX_CRYPT_S_C (symmetric cipher server to client)\n * KEX_COMP_C_S (Compression client to server): zlib or none\n * KEX_COMP_S_C (Compression server to client): zlib or none\n * You don't have to use this function if using the default ciphers * is okay for you\n * in order to enable compression client to server, do\n * @code * ret = ssh_options_set_wanted_algos(opt,KEX_COMP_C_S,"zlib"); * @endcode * * @param opt The options structure to use. * * @param algo The method which needs to be changed. * * @param list A list of algorithms to be used, in order of preference * and separated by commas. * * @return 0 on success, < 0 on error */ int ssh_options_set_wanted_algos(SSH_OPTIONS *opt, int algo, const char *list) { if (opt == NULL || list == NULL) { return -1; } if(algo > SSH_LANG_S_C || algo < 0) { ssh_set_error(opt, SSH_REQUEST_DENIED, "algo %d out of range", algo); return -1; } if ((!opt->use_nonexisting_algo) && !verify_existing_algo(algo, list)) { ssh_set_error(opt, SSH_REQUEST_DENIED, "Setting method: no algorithm " "for method \"%s\" (%s)\n", ssh_kex_nums[algo], list); return -1; } SAFE_FREE(opt->wanted_methods[algo]); opt->wanted_methods[algo] = strdup(list); if (opt->wanted_methods[algo] == NULL) { return -1; } return 0; } #ifndef _WIN32 static char *get_username_from_uid(SSH_OPTIONS *opt, uid_t uid){ struct passwd *pwd = NULL; pwd = getpwuid(uid); if (pwd == NULL) { ssh_set_error(opt,SSH_FATAL,"uid %d doesn't exist !",uid); return NULL; } return strdup(pwd->pw_name); } #endif /* this function must be called when no specific username has been asked. it has to guess it */ int ssh_options_default_username(SSH_OPTIONS *opt) { char *user = NULL; if (opt->username) { return 0; } #ifndef _WIN32 user = get_username_from_uid(opt,getuid()); if (user) { opt->username = user; return 0; } #else DWORD Size = 0; GetUserName(NULL, &Size); //Get Size user = malloc(Size); if (user == NULL) { return -1; } if (GetUserName(user, &Size)) { opt->username=user; return 0; } else { SAFE_FREE(user); } #endif return -1; } int ssh_options_default_ssh_dir(SSH_OPTIONS *opt) { char buffer[256] = {0}; if (opt->ssh_dir) { return 0; } snprintf(buffer, 256, "%s/.ssh/", ssh_get_user_home_dir()); opt->ssh_dir = strdup(buffer); if (opt->ssh_dir == NULL) { return -1; } return 0; } int ssh_options_default_known_hosts_file(SSH_OPTIONS *opt) { char buffer[1024] = {0}; if (opt->known_hosts_file) { return 0; } if (ssh_options_default_ssh_dir(opt) < 0) { return -1; } snprintf(buffer, 1024, "%s/known_hosts", opt->ssh_dir); opt->known_hosts_file = strdup(buffer); if (opt->known_hosts_file == NULL) { return -1; } return 0; } /** * @brief Set a callback to show connection status in realtime. * * During ssh_connect(), libssh will call the callback with status from * 0.0 to 1.0 * * @param opt The options structure to use. * * @param callback A function pointer to a callback in form * f(void *userarg, float status). * * @param arg The value to be given as argument to the callback * function when it is called. * * @return 0 on success, < 0 on error. * * @see ssh_connect() */ int ssh_options_set_status_callback(SSH_OPTIONS *opt, void (*callback)(void *arg, float status), void *arg) { if (opt == NULL || callback == NULL) { return -1; } opt->connect_status_function = callback; opt->connect_status_arg = arg; return 0; } /** * @brief Set a timeout for the connection. * * @param opt The options structure to use. * * @param seconds Number of seconds. * * @param usec Number of micro seconds. * * @return 0 on success, < 0 on error. * * @bug Currently it only timeouts the socket connection, not the * complete exchange. */ int ssh_options_set_timeout(SSH_OPTIONS *opt, long seconds, long usec) { if (opt == NULL) { return -1; } opt->timeout=seconds; opt->timeout_usec=usec; return 0; } /** Default value is 0 (no connection to SSH1 servers) * \brief allow or deny the connection to SSH1 servers * \param opt options structure * \param allow nonzero values allow ssh1 */ void ssh_options_allow_ssh1(SSH_OPTIONS *opt, int allow){ if(allow) opt->ssh1allowed=1; else opt->ssh1allowed=0; } /** Default value is 1 (allow connection to SSH2 servers) * \brief allow or deny the connection to SSH2 servers * \param opt options structure * \param allow nonzero values allow ssh2 */ void ssh_options_allow_ssh2(SSH_OPTIONS *opt, int allow){ if(allow) opt->ssh2allowed=1; else opt->ssh2allowed=0; } /** Default is a write on stderr * \brief Change the writer callback for logging * \param opt options structure * \param callback a callback function for the printing * \warning the message string may contain format string characters. */ void ssh_options_set_log_function(SSH_OPTIONS *opt, void (*callback)(const char *message, SSH_SESSION *session, int priority )){ opt->log_function=callback; } /** \brief set this session's logging priority * \param opt options structure * \param verbosity verbosity of the messages. Every log smaller or equal to verbosity will be shown\n * SSH_LOG_NOLOG No logging \n * SSH_LOG_RARE Rare conditions or warnings\n * SSH_LOG_ENTRY Api-accessible entrypoints\n * SSH_LOG_PACKET Packet id and size\n * SSH_LOG_FUNCTIONS function entering and leaving\n */ void ssh_options_set_log_verbosity(SSH_OPTIONS *opt, int verbosity){ opt->log_verbosity=verbosity; } /** * This is a helper for your application to generate the appropriate * options from the command line arguments.\n * the argv array and argc value are changed so that parsed * arguments won't appear anymore in them.\n * The single arguments (without switches) are not parsed. thus, * myssh -u aris localhost \n * command won't set the hostname value of options to localhost. * \brief parse command line arguments * \param options an empty option structure pointer * \param argcptr pointer to argument count * \param argv arguments list pointer * \returns 0 on success, -1 on error * \sa ssh_options_new() */ int ssh_options_getopt(SSH_OPTIONS *options, int *argcptr, char **argv){ int i; int argc=*argcptr; char *user=NULL; int port=22; int debuglevel=0; int usersa=0; int usedss=0; int compress=0; int cont=1; char *cipher=NULL; char *localaddr=NULL; char *identity=NULL; char **save = NULL; int current=0; #ifdef HAVE_SSH1 int ssh1=1; #else int ssh1=0; #endif int ssh2=1; int saveoptind=optind; /* need to save 'em */ int saveopterr=opterr; save = malloc(argc * sizeof(char *)); if (save == NULL) { return -1; } opterr=0; /* shut up getopt */ while(cont && ((i=getopt(argc,argv,"c:i:Cl:p:vb:rd12"))!=-1)){ switch(i){ case 'l': user=optarg; break; case 'p': port=atoi(optarg)&0xffff; break; case 'v': debuglevel++; break; case 'r': usersa++; break; case 'd': usedss++; break; case 'c': cipher=optarg; break; case 'i': identity=optarg; break; case 'b': localaddr=optarg; break; case 'C': compress++; break; case '2': ssh2=1; ssh1=0; break; case '1': ssh2=0; ssh1=1; break; default: { char opt[3]="- "; opt[1]=optopt; save[current++]=strdup(opt); if(optarg) save[current++]=argv[optind+1]; } } } opterr=saveopterr; while(optind < argc) save[current++]=argv[optind++]; if(usersa && usedss){ ssh_set_error(options,SSH_FATAL,"either RSA or DSS must be chosen"); cont=0; } ssh_options_set_log_verbosity(options,debuglevel); optind=saveoptind; if(!cont){ free(save); return -1; } /* first recopy the save vector into original's */ for(i=0;ibindport=port; ssh_options_allow_ssh1(options,ssh1); ssh_options_allow_ssh2(options,ssh2); if(!cont){ return -1; } else return 0 ; } void ssh_options_set_auth_callback(SSH_OPTIONS *opt, ssh_auth_callback cb, void *userdata) { if (opt == NULL) { return; } opt->auth_function = cb; opt->auth_userdata = userdata; } /** @} */