/** * @page tutorial The Tutorial * * @section introduction Introduction * * libssh is a C library that enables you to write a program that uses the * SSH protocol. With it, you can remotely execute programs, transfer * files, or use a secure and transparent tunnel for your remote programs. * The SSH protocol is encrypted, ensures data integrity, and provides strong * means of authenticating both the server of the client. The library hides * a lot of technical details from the SSH protocol, but this does not * mean that you should not try to know about and understand these details. * * libssh is a Free Software / Open Source project. The libssh library * is distributed under LGPL license. The libssh project has nothing to do with * "libssh2", which is a completly different and independant project. * * libssh supports both client and server sides of the SSH protocol. * The following document explains how to set up a client-side connection. * If you are going to program a server, you should try first to code some * client-side programs in order to understand how libssh works. * * This tutorial describes libssh version 0.5.0. * * * Table of contents: * * @subpage session * * @subpage details * * @subpage tbd * * * @page session Chapter 1: A typical SSH session * @section ssh_session A typical SSH session * * A SSH session goes through the following steps: * * - Before connecting to the server, you can set up if you wish one or other * server public key authentication, i.e. DSA or RSA. You can choose * cryptographic algorithms you trust and compression algorithms if any. You * must of course set up the hostname. * * - The connection is established. A secure handshake is made, and resulting from * it, a public key from the server is gained. You MUST verify that the public * key is legitimate, using for instance the MD5 fingerprint or the known hosts * file. * * - The client must authenticate: the classical ways are password, or * public keys (from dsa and rsa key-pairs generated by openssh). * If a SSH agent is running, it is possible to use it. * * - Now that the user has been authenticated, you must open one or several * channels. Channels are different subways for information into a single ssh * connection. Each channel has a standard stream (stdout) and an error stream * (stderr). You can theoretically open an infinity of channels. * * - With the channel you opened, you can do several things: * - Execute a single command. * - Open a shell. You may want to request a pseudo virtual terminal before. * - Invoke the sftp subsystem to transfer files. * - Invoke the scp subsystem to transfer files. * - Invoke your own subsystem. This is outside the scope of this document, * but it is easy to do. * * - When everything is finished, just close the channels, and then the connection. * * All the libssh functions which return an error code also set an error message. * Error codes are typically SSH_ERROR for integer values, or NULL for pointers. * * * @subsection setup Creating the session and setting options * * The most important object in a SSH connection is the SSH session. In order * to allocate a new SSH session, you use ssh_new(). Don't forget to * always verify that the allocation successed. * @code * #include * #include * * int main() * { * ssh_session my_ssh_session = ssh_new(); * if (my_ssh_session == NULL) * exit(-1); * ... * ssh_free(my_ssh_session); * } * @endcode * * libssh follows the allocate-it-deallocate-it pattern. Each object that you allocate * using xxxxx_new() must be deallocated using xxxxx_free(). In this case, ssh_new() * does the allocation and ssh_free() does the contrary. * * The ssh_options_set() function sets the options of the session. The most important options are: * - SSH_OPTIONS_HOST: the name of the host you want to connect to * - SSH_OPTIONS_PORT: the used port (default is port 22) * - SSH_OPTIONS_USER: the system user under which you want to connect * - SSH_OPTIONS_LOG_VERBOSITY: the quantity of messages that are printed * * The complete list of options can be found in the documentation of ssh_options_set(). * The only mandatory option is SSH_OPTIONS_HOST. If you don't use SSH_OPTIONS_USER, * the local username of your account will be used. * * Here is a small example of how to use it: * @code * #include * #include * * int main() * { * ssh_session my_ssh_session; * int verbosity = SSH_LOG_PROTOCOL; * int port = 22; * * my_ssh_session = ssh_new(); * if (my_ssh_session == NULL) * exit(-1); * * ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); * ssh_options_set(my_ssh_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); * ssh_options_set(my_ssh_session, SSH_OPTIONS_PORT, &port); * * ... * * ssh_free(my_ssh_session); * } * @endcode * * Please notice that all parameters are passed to ssh_options_set() as pointers, * even if you need to set an integer value. * * @see ssh_new * @see ssh_free * @see ssh_options_set * @see ssh_options_parse_config * @see ssh_options_copy * @see ssh_options_getopt * * * @subsection connect Connecting to the server * * Once all settings have been made, you can connect using ssh_connect(). That * function will return SSH_OK if the connection worked, SSH_ERROR otherwise. * * You can get the error string using ssh_get_error() in order to show the * user what went wrong. Then, use ssh_disconnect() when you want to stop * the session. * * Here's an example: * * @code * #include * #include * #include * * int main() * { * ssh_session my_ssh_session; * int ret; * * my_ssh_session = ssh_new(); * if (my_ssh_session == NULL) * exit(-1); * * ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); * * ret = ssh_connect(my_ssh_session); * if (ret != SSH_OK) * { * fprintf(stderr, "Error connecting to localhost: %s\n", * ssh_get_error(my_ssh_session)); * exit(-1); * } * * ... * * ssh_disconnect(my_ssh_session); * ssh_free(my_ssh_session); * } * @endcode * * * @subsection serverauth Authenticating the server * * Once you're connected, the following step is mandatory: you must check that the server * you just connected to is known and safe to use (remember, SSH is about security and * authentication). * * There are two ways of doing this: * - The first way (recommended) is to use the ssh_is_server_known() * function. This function will look into the known host file * (~/.ssh/known_hosts on UNIX), look for the server hostname's pattern, * and determine whether this host is present or not in the list. * - The second way is to use ssh_get_pubkey_hash() to get a binary version * of the public key hash value. You can then use your own database to check * if this public key is known and secure. * * You can also use the ssh_get_pubkey_hash() to show the public key hash * value to the user, in case he knows what the public key hash value is * (some paranoid people write their public key hash values on paper before * going abroad, just in case ...). * * If the remote host is being used to for the first time, you can ask the user whether * he/she trusts it. Once he/she concluded that the host is valid and worth being * added in the known hosts file, you use ssh_write_knownhost() to register it in * the known hosts file, or any other way if you use your own database. * * The following example is part of the examples suite available in the * examples/ directory: * * @code * int verify_knownhost(ssh_session session) * { * int state, hlen; * unsigned char *hash = NULL; * char *hexa; * char buf[10]; * * state = ssh_is_server_known(session); * * hlen = ssh_get_pubkey_hash(session, &hash); * if (hlen < 0) * return -1; * * switch (state) * { * case SSH_SERVER_KNOWN_OK: * break; /* ok */ * * case SSH_SERVER_KNOWN_CHANGED: * fprintf(stderr, "Host key for server changed: it is now:\n"); * ssh_print_hexa("Public key hash", hash, hlen); * fprintf(stderr, "For security reasons, connection will be stopped\n"); * free(hash); * return -1; * * case SSH_SERVER_FOUND_OTHER: * fprintf(stderr, "The host key for this server was not found but an other" * "type of key exists.\n"); * fprintf(stderr, "An attacker might change the default server key to" * "confuse your client into thinking the key does not exist\n" * free(hash); * return -1; * * case SSH_SERVER_FILE_NOT_FOUND: * fprintf(stderr, "Could not find known host file.\n"); * fprintf(stderr, "If you accept the host key here, the file will be" * "automatically created.\n"); * /* fallback to SSH_SERVER_NOT_KNOWN behavior */ * * case SSH_SERVER_NOT_KNOWN: * hexa = ssh_get_hexa(hash, hlen); * fprintf(stderr,"The server is unknown. Do you trust the host key?\n"); * fprintf(stderr, "Public key hash: %s\n", hexa); * free(hexa); * if (fgets(buf, sizeof(buf), stdin) == NULL) * { * free(hash); * return -1; * } * if (strncasecmp(buf, "yes", 3) != 0) * { * free(hash); * return -1; * } * if (ssh_write_knownhost(session) < 0) * { * fprintf(stderr, "Error %s\n", strerror(errno)); * free(hash); * return -1; * } * break; * * case SSH_SERVER_ERROR: * fprintf(stderr, "Error %s", ssh_get_error(session)); * free(hash); * return -1; * } * * free(hash); * return 0; * } * @endcode * * @see ssh_connect * @see ssh_disconnect * @see ssh_get_error * @see ssh_get_error_code * @see ssh_get_pubkey_hash * @see ssh_is_server_known * @see ssh_write_knownhost * * * @subsection auth Authenticating yourself * * The authentication process is the way a service provider can identify a * user and verify his/her identity. The authorization process is about enabling * the authenticated user the access to ressources. In SSH, the two concepts * are linked. After authentication, the server can grant the user access to * several ressources such as port forwarding, shell, sftp subsystem, and so on. * * libssh supports several methods of authentication: * - "none" method. This method allows to get the available authentications * methods. It also gives the server a chance to authenticate the user with * just his/her login. Some very old hardware uses this feature to fallback * the user on a "telnet over SSH" style of login. * - password method. A password is sent to the server, which accepts it or not. * - keyboard-interactive method. The server sends several challenges to the * user, who must answer correctly. This makes possible the authentication * via a codebook for instance ("give code at 23:R on page 3"). * - public key method. The host knows the public key of the user, and the * user must prove he knows the associated private key. This can be done * manually, or delegated to the SSH agent as we'll see later. * * All these methods can be combined. You can for instance force the user to * authenticate with at least two of the authentication methods. In that case, * one speaks of "Partial authentication". A partial authentication is a * response from authentication functions stating that your credential was * accepted, but yet another one is required to get in. * * The example below shows an authentication with password: * * @code * #include * #include * #include * * int main() * { * ssh_session my_ssh_session; * int ret; * char *password; * * // Open session and set options * my_ssh_session = ssh_new(); * if (my_ssh_session == NULL) * exit(-1); * ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); * * // Connect to server * ret = ssh_connect(my_ssh_session); * if (ret != SSH_OK) * { * fprintf(stderr, "Error connecting to localhost: %s\n", * ssh_get_error(my_ssh_session)); * ssh_free(my_ssh_session); * exit(-1); * } * * // Verify the server's identity * // For the source code of verify_knowhost(), check previous example * if (verify_knownhost(my_ssh_session) < 0) * { * ssh_disconnect(my_ssh_session); * ssh_free(my_ssh_session); * exit(-1); * } * * // Authenticate ourselves * password = getpass("Password: "); * ret = ssh_userauth_password(my_ssh_session, NULL, password); * if (ret != SSH_AUTH_SUCCESS) * { * fprintf(stderr, "Error authenticating with password: %s\n", * ssh_get_error(my_ssh_session)); * ssh_disconnect(my_ssh_session); * ssh_free(my_ssh_session); * exit(-1); * } * * ... * * ssh_disconnect(my_ssh_session); * ssh_free(my_ssh_session); * } * @endcode * * @see @ref authentication_details * * * @subsection using_ssh Doing something * * At this point, the authenticity of both server and client is established. * Time has come to take advantage of the many possibilities offered by the SSH * protocol: execute remote commands, open remote shells, transfer files, * forward ports, etc. * * The example below puts the final touch to our step-by-step discovery * of a SSH connection: * * @code * /* *** To be replaced with something simpler *** */ * #include * * int scp_helloworld(ssh_session session) * { * ssh_scp scp_session; * int ret; * const char *helloworld = "Hello, world!\n"; * int length = strlen(helloworld); * * // Open a scp session * scp_session = ssh_scp_new * (session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, "."); * if (scp_session == NULL) * { * fprintf(stderr, "Error allocating scp session.\n"); * return SSH_ERROR; * } * * // Initialize the scp session * ret = ssh_scp_init(scp_session); * if (ret != SSH_OK) * { * fprintf(stderr, "Error initializing scp session.\n"); * ssh_scp_free(scp_session); * return ret; * } * * // Open the remote file * ret = ssh_scp_push_file * (scp_session, "helloworld.txt", length, S_IRUSR | S_IWUSR); * if (ret != SSH_OK) * { * fprintf(stderr, "Can't open remote file.\n"); * ssh_scp_close(scp_session); * ssh_scp_free(scp_session); * return ret; * } * * // Write the data into the remote file * ret = ssh_scp_write(scp_session, helloworld, length); * if (ret != SSH_OK) * { * fprintf(stderr, "Can't write to remote file.\n"); * ssh_scp_close(scp_session); * ssh_scp_free(scp_session); * return ret; * } * * ssh_scp_close(scp_session); * ssh_scp_free(scp_session); * return ret; * } * @endcode * * @see @ref opening_shell * @see @ref remote_commands * @see @ref sftp_subsystem * @see @ref scp_subsystem * * * @page details Chapter 2: A deeper insight on authentication * @section authentication_details A deeper insight on authentication * * * @subsection pubkeys Authenticating using public keys * * The public key authentication is the only method that does not compromise * your key if the remote host has been compromised (the server can't do * anything more than getting your public key). This is not the * case of a password authentication (the server can get your plaintext * password). On the other hand, if the client machine is compromised and * the private key has no passphrase, then the attacker has gained automatic * access to your server. It is not the purpose of this document to do * a detailed review of the advantages and drawbacks of each authentication * method, so refer to the abundant documentation on this topic on the * Internet to fully understand the advantages and risks linked to each method. * * libssh is fully compatible with the openssh public and private keys. You * can either use the automatic public key authentication method provided by * libssh, or roll your own using the public key functions. * * The process of authenticating by public key to a server is the following: * - you scan a list of files that contain public keys. each key is sent to * the SSH server, until the server acknowledges a key (a key it knows can be * used to authenticate the user). * - then, you retrieve the private key for this key and send a message * proving that you know that private key. * * The function ssh_userauth_autopubkey() does this using the available keys in * "~/.ssh/". The return values are the following: * - SSH_AUTH_ERROR: some serious error happened during authentication * - SSH_AUTH_DENIED: no key matched * - SSH_AUTH_SUCCESS: you are now authenticated * - SSH_AUTH_PARTIAL: some key matched but you still have to provide an other * mean of authentication (like a password). * * The ssh_userauth_autopubkey function also tries to authenticate using the * SSH agent, if you have one running, or the "none" method otherwise. * * If you wish to authenticate with public key by your own, follow these steps: * - Retrieve the public key in a ssh_string using publickey_from_file(). * - Offer the public key to the SSH server using ssh_userauth_offer_pubkey(). * If the return value is SSH_AUTH_SUCCESS, the SSH server accepts to * authenticate using the public key and you can go to the next step. * - Retrieve the private key, using the privatekey_from_file() function. If * a passphrase is needed, either the passphrase specified as argument or * a callback (see callbacks section) will be used. * - Authenticate using ssh_userauth_pubkey() with your public key string * and private key. * - Do not forget cleaning up memory using string_free() and privatekey_free(). * * Here is a minimalistic example of public key authentication: * * @code * int authenticate_pubkey(ssh_session session) * { * int rc; * * rc = ssh_userauth_autopubkey(session, NULL); * * if (rc == SSH_AUTH_ERROR) * { * fprintf(stderr, "Authentication failed: %s\n", * ssh_get_error(session)); * return SSH_AUTH_ERROR; * } * * return rc; * } * @endcode * * @see ssh_userauth_autopubkey * @see ssh_userauth_offer_pubkey * @see ssh_userauth_pubkey * @see publickey_from_file * @see publickey_from_privatekey * @see string_free * @see privatekey_from_file * @see privatekey_free * * * @subsection password Authenticating using a password * * The function ssh_userauth_password() serves the purpose of authenticating * using a password. It will return SSH_AUTH_SUCCESS if the password worked, * or one of other constants otherwise. It's your work to ask the password * and to deallocate it in a secure manner. * * If your server complains that the password is wrong, but you can still * authenticate using openssh's client (issuing password), it's probably * because openssh only accept keyboard-interactive. Switch to * keyboard-interactive authentication, or try to configure plain text passwords * on the SSH server. * * Here is a small example of password authentication: * * @code * int authenticate_password(ssh_session session) * { * char *password; * int rc; * * password = getpass("Enter your password: "); * rc = ssh_userauth_password(session, NULL, password); * if (rc == SSH_AUTH_ERROR) * { * fprintf(stderr, "Authentication failed: %s\n", * ssh_get_error(session)); * return SSH_AUTH_ERROR; * } * * return rc; * } * @endcode * * @see ssh_userauth_password * * * @subsection keyb_int The keyboard-interactive authentication method * * The keyboard-interactive method is, as its name tells, interactive. The * server will issue one or more challenges that the user has to answer, * until the server takes an authentication decision. * * ssh_userauth_kbdint() is the the main keyboard-interactive function. * It will return SSH_AUTH_SUCCESS,SSH_AUTH_DENIED, SSH_AUTH_PARTIAL, * SSH_AUTH_ERROR, or SSH_AUTH_INFO, depending on the result of the request. * * The keyboard-interactive authentication method of SSH2 is a feature that * permits the server to ask a certain number of questions in an interactive * manner to the client, until it decides to accept or deny the login. * * To begin, you call ssh_userauth_kbdint() (just set user and submethods to * NULL) and store the answer. * * If the answer is SSH_AUTH_INFO, it means that the server has sent a few * questions that you should ask the user. You can retrieve these questions * with the following functions: ssh_userauth_kbdint_getnprompts(), * ssh_userauth_kbdint_getname(), ssh_userauth_kbdint_getinstruction(), and * ssh_userauth_kbdint_getprompt(). * * Set the answer for each question in the challenge using * ssh_userauth_kbdint_setanswer(). * * Then, call again ssh_userauth_kbdint() and start the process again until * these functions returns something else than SSH_AUTH_INFO. * * Here are a few remarks: * - Even the first call can return SSH_AUTH_DENIED or SSH_AUTH_SUCCESS. * - The server can send an empty question set (this is the default behavior * on my system) after you have sent the answers to the first questions. * You must still parse the answer, it might contain some * message from the server saying hello or such things. Just call * ssh_userauth_kbdint() until needed. * - The meaning of "name", "prompt", "instruction" may be a little * confusing. An explanation is given in the RFC section that follows. * * Here is a little note about how to use the information from * keyboard-interactive authentication, coming from the RFC itself (rfc4256): * * @verbatim * * 3.3 User Interface Upon receiving a request message, the client SHOULD * prompt the user as follows: A command line interface (CLI) client SHOULD * print the name and instruction (if non-empty), adding newlines. Then for * each prompt in turn, the client SHOULD display the prompt and read the * user input. * * A graphical user interface (GUI) client has many choices on how to prompt * the user. One possibility is to use the name field (possibly prefixed * with the application's name) as the title of a dialog window in which * the prompt(s) are presented. In that dialog window, the instruction field * would be a text message, and the prompts would be labels for text entry * fields. All fields SHOULD be presented to the user, for example an * implementation SHOULD NOT discard the name field because its windows lack * titles; it SHOULD instead find another way to display this information. If * prompts are presented in a dialog window, then the client SHOULD NOT * present each prompt in a separate window. * * All clients MUST properly handle an instruction field with embedded * newlines. They SHOULD also be able to display at least 30 characters for * the name and prompts. If the server presents names or prompts longer than 30 * characters, the client MAY truncate these fields to the length it can * display. If the client does truncate any fields, there MUST be an obvious * indication that such truncation has occured. * * The instruction field SHOULD NOT be truncated. Clients SHOULD use control * character filtering as discussed in [SSH-ARCH] to avoid attacks by * including terminal control characters in the fields to be displayed. * * For each prompt, the corresponding echo field indicates whether or not * the user input should be echoed as characters are typed. Clients SHOULD * correctly echo/mask user input for each prompt independently of other * prompts in the request message. If a client does not honor the echo field * for whatever reason, then the client MUST err on the side of * masking input. A GUI client might like to have a checkbox toggling * echo/mask. Clients SHOULD NOT add any additional characters to the prompt * such as ": " (colon-space); the server is responsible for supplying all * text to be displayed to the user. Clients MUST also accept empty responses * from the user and pass them on as empty strings. * @endverbatim * * The following example shows how to perform keyboard-interactive authentication: * * @code * int authenticate_kbdint(ssh_session session) * { * int rc; * * rc = ssh_userauth_kbdint(session, NULL, NULL); * while (rc == SSH_AUTH_INFO) * { * const char *name, *instruction; * int nprompts, iprompt; * * name = ssh_userauth_kbdint_getname(session); * instruction = ssh_userauth_kbdint_getinstruction(session); * nprompts = ssh_userauth_kbdint_getnprompts(session); * * if (strlen(name) > 0) * printf("%s\n", name); * if (strlen(instruction) > 0) * printf("%s\n", instruction); * for (iprompt = 0; iprompt < nprompts; iprompt++) * { * const char *prompt; * char echo; * * prompt = ssh_userauth_kbdint_getprompt(session, iprompt, &echo); * if (echo) * { * char buffer[128], *ptr; * * printf("%s", prompt); * if (fgets(buffer, sizeof(buffer), stdin) == NULL) * return SSH_AUTH_ERROR; * buffer[sizeof(buffer) - 1] = '\0'; * if ((ptr = strchr(buffer, '\n')) != NULL) * *ptr = '\0'; * if (ssh_userauth_kbdint_setanswer(session, iprompt, buffer) < 0) * return SSH_AUTH_ERROR; * memset(buffer, 0, strlen(buffer)); * } * else * { * char *ptr; * * ptr = getpass(prompt); * if (ssh_userauth_kbdint_setanswer(session, iprompt, ptr) < 0) * return SSH_AUTH_ERROR; * } * } * rc = ssh_userauth_kbdint(session, NULL, NULL); * } * return rc; * } * @endcode * * @see ssh_userauth_kbdint() * @see ssh_userauth_kbdint_getnprompts * @see ssh_userauth_kbdint_getname * @see ssh_userauth_kbdint_getinstruction * @see ssh_userauth_kbdint_getprompt * @see ssh_userauth_kbdint_setanswer() * * * @subsection none Authenticating with "none" method * * The primary purpose of the "none" method is to get authenticated **without** * any credential. Don't do that, use one of the other authentication methods, * unless you really want to grant anonymous access. * * If the account has no password, and if the server is configured to let you * pass, ssh_userauth_none() might answer SSH_AUTH_SUCCESS. * * The following example shows how to perform "none" authentication: * * @code * int authenticate_kbdint(ssh_session session) * { * int rc; * * rc = ssh_userauth_none(session, NULL, NULL); * return rc; * } * @endcode * * @subsection auth_list Getting the list of supported authentications * * You are not meant to choose a given authentication method, you can * let the server tell you which methods are available. Once you know them, * you try them one after the other. * * The following example shows how to get the list of available authentication * methods with ssh_userauth_list() and how to use the result: * * @code * /* Marche pas sans ssh_userauth_none(), du moins en 0.4.2 */ * int test_several_auth_methods(ssh_session session) * { * int method, rc; * * method = ssh_userauth_list(session, NULL); * * if (method & SSH_AUTH_METHOD_NONE) * { // For the source code of function authenticate_none(), * // refer to the corresponding example * rc = authenticate_none(session); * if (rc == SSH_AUTH_SUCCESS) return rc; * } * if (method & SSH_AUTH_METHOD_PUBLICKEY) * { // For the source code of function authenticate_pubkey(), * // refer to the corresponding example * rc = authenticate_pubkey(session); * if (rc == SSH_AUTH_SUCCESS) return rc; * } * if (method & SSH_AUTH_METHOD_INTERACTIVE) * { // For the source code of function authenticate_kbdint(), * // refer to the corresponding example * rc = authenticate_kbdint(session); * if (rc == SSH_AUTH_SUCCESS) return rc; * } * if (method & SSH_AUTH_METHOD_PASSWORD) * { // For the source code of function authenticate_password(), * // refer to the corresponding example * rc = authenticate_password(session); * if (rc == SSH_AUTH_SUCCESS) return rc; * } * return SSH_AUTH_ERROR; * } * @endcode * * * @subsection banner * * The SSH server might send a banner, which you can retrieve with * ssh_get_issue_banner(), then display to the user. * * The following example shows how to retrieve and dispose the issue banner: * * @code * /* Marche pas sans ssh_userauth_none(), du moins en 0.4.2 */ * int display_banner(ssh_session session) * { * int rc; * char *banner; * * rc = ssh_userauth_none(session, NULL); * if (rc == SSH_AUTH_ERROR) * return rc; * * banner = ssh_get_issue_banner(session); * if (banner) * { * printf("%s\n", banner); * free(banner); * } * * return rc; * } * @endcode * * * @page tbd Chapter 3: To be done * @section opening_shell Opening a shell * * *** To be written *** * * @section remote_commands Passing remote commands * * *** To be written *** * * @section sftp_subsystem The SFTP subsystem * * *** To be written *** * * @section scp_subsystem The SCP subsystem * * *** To be written *** * * @section threads Working with threads * * *** To be written *** * * @section forwarding_connections Forwarding connections * * *** To be written *** * */