Add libssh 0.95.
No files matched your search
@@ -0,0 +1,49 @@
|
||||
#
|
||||
# Build the documentation
|
||||
#
|
||||
if (${CMAKE_VERSION} VERSION_GREATER "3.8.99")
|
||||
|
||||
find_package(Doxygen)
|
||||
|
||||
if (DOXYGEN_FOUND)
|
||||
set(DOXYGEN_PROJECT_NAME ${PROJECT_NAME})
|
||||
set(DOXYGEN_PROJECT_NUMBER ${PROJECT_VERSION})
|
||||
set(DOXYGEN_PROJECT_BRIEF "The SSH library")
|
||||
|
||||
set(DOXYGEN_TAB_SIZE 4)
|
||||
set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES)
|
||||
set(DOXYGEN_MARKDOWN_SUPPORT YES)
|
||||
set(DOXYGEN_FULL_PATH_NAMES NO)
|
||||
|
||||
set(DOXYGEN_PREDEFINED DOXYGEN
|
||||
WITH_SERVER
|
||||
WITH_SFTP
|
||||
PRINTF_ATTRIBUTE(x,y))
|
||||
|
||||
set(DOXYGEN_EXCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/that_style)
|
||||
set(DOXYGEN_HTML_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/that_style/header.html)
|
||||
set(DOXYGEN_HTML_EXTRA_STYLESHEET ${CMAKE_CURRENT_SOURCE_DIR}/that_style/that_style.css)
|
||||
set(DOXYGEN_HTML_EXTRA_FILES ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/nav_edge_left.svg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/nav_edge_right.svg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/nav_edge_inter.svg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/sync_off.png
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/sync_on.png
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/splitbar_handle.svg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/doc.svg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/mag_glass.svg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/folderclosed.svg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/folderopen.svg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/that_style/js/striped_bg.js)
|
||||
|
||||
# This updates the Doxyfile if we do changes here
|
||||
set(_doxyfile_template "${CMAKE_BINARY_DIR}/CMakeDoxyfile.in")
|
||||
set(_target_doxyfile "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.docs")
|
||||
configure_file("${_doxyfile_template}" "${_target_doxyfile}")
|
||||
|
||||
doxygen_add_docs(docs
|
||||
${CMAKE_SOURCE_DIR}/include/libssh
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${CMAKE_CURRENT_SOURCE_DIR})
|
||||
endif() # DOXYGEN_FOUND
|
||||
|
||||
endif() # CMAKE_VERSION
|
||||
@@ -0,0 +1,375 @@
|
||||
/**
|
||||
@page libssh_tutor_authentication Chapter 2: A deeper insight on authentication
|
||||
@section authentication_details A deeper insight on authentication
|
||||
|
||||
In our guided tour, we merely mentioned that the user needed to authenticate.
|
||||
We didn't explain much in detail how that was supposed to happen.
|
||||
This chapter explains better the four authentication methods: with public keys,
|
||||
with a password, with challenges and responses (keyboard-interactive), and with
|
||||
no authentication at all.
|
||||
|
||||
If your software is supposed to connect to an arbitrary server, then you
|
||||
might need to support all authentication methods. If your software will
|
||||
connect only to a given server, then it might be enough for your software
|
||||
to support only the authentication methods used by that server. If you are
|
||||
the administrator of the server, it might be your call to choose those
|
||||
authentication methods.
|
||||
|
||||
It is not the purpose of this document to review in detail the advantages
|
||||
and drawbacks of each authentication method. You are therefore invited
|
||||
to read the abundant documentation on this topic to fully understand the
|
||||
advantages and security risks linked to each method.
|
||||
|
||||
|
||||
@subsection pubkeys Authenticating with public keys
|
||||
|
||||
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_publickey_auto() 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 with ssh_pki_import_pubkey_file().
|
||||
- Offer the public key to the SSH server using ssh_userauth_try_publickey().
|
||||
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 ssh_pki_import_privkey_file() function.
|
||||
If a passphrase is needed, either the passphrase specified as argument or
|
||||
a callback will be used.
|
||||
- Authenticate using ssh_userauth_publickey() with your private key.
|
||||
- Do not forget cleaning up memory using ssh_key_free().
|
||||
|
||||
Here is a minimalistic example of public key authentication:
|
||||
|
||||
@code
|
||||
int authenticate_pubkey(ssh_session session)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = ssh_userauth_publickey_auto(session, NULL, 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_publickey_auto()
|
||||
@see ssh_userauth_try_publickey()
|
||||
@see ssh_userauth_publickey()
|
||||
@see ssh_pki_import_pubkey_file()
|
||||
@see ssh_pki_import_privkey_file()
|
||||
@see ssh_key_free()
|
||||
|
||||
|
||||
@subsection password Authenticating with 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 occurred.
|
||||
|
||||
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_none(ssh_session session)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = ssh_userauth_none(session, 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
|
||||
int test_several_auth_methods(ssh_session session)
|
||||
{
|
||||
int method, rc;
|
||||
|
||||
rc = ssh_userauth_none(session, NULL);
|
||||
if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_ERROR) {
|
||||
return 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 Getting the 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
|
||||
int display_banner(ssh_session session)
|
||||
{
|
||||
int rc;
|
||||
char *banner;
|
||||
|
||||
/*
|
||||
*** Does not work without calling ssh_userauth_none() first ***
|
||||
*** That will be fixed ***
|
||||
*/
|
||||
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
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
@page libssh_tutor_command Chapter 4: Passing a remote command
|
||||
@section remote_command Passing a remote command
|
||||
|
||||
Previous chapter has shown how to open a full shell session, with an attached
|
||||
terminal or not. If you only need to execute a command on the remote end,
|
||||
you don't need all that complexity.
|
||||
|
||||
The method described here is suited for executing only one remote command.
|
||||
If you need to issue several commands in a row, you should consider using
|
||||
a non-interactive remote shell, as explained in previous chapter.
|
||||
|
||||
@see shell
|
||||
|
||||
|
||||
@subsection exec_remote Executing a remote command
|
||||
|
||||
The first steps for executing a remote command are identical to those
|
||||
for opening remote shells. You first need a SSH channel, and then
|
||||
a SSH session that uses this channel:
|
||||
|
||||
@code
|
||||
int show_remote_files(ssh_session session)
|
||||
{
|
||||
ssh_channel channel;
|
||||
int rc;
|
||||
|
||||
channel = ssh_channel_new(session);
|
||||
if (channel == NULL) return SSH_ERROR;
|
||||
|
||||
rc = ssh_channel_open_session(channel);
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
ssh_channel_free(channel);
|
||||
return rc;
|
||||
}
|
||||
@endcode
|
||||
|
||||
Once a session is open, you can start the remote command with
|
||||
ssh_channel_request_exec():
|
||||
|
||||
@code
|
||||
rc = ssh_channel_request_exec(channel, "ls -l");
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
return rc;
|
||||
}
|
||||
@endcode
|
||||
|
||||
If the remote command displays data, you get them with ssh_channel_read().
|
||||
This function returns the number of bytes read. If there is no more
|
||||
data to read on the channel, this function returns 0, and you can go to next step.
|
||||
If an error has been encountered, it returns a negative value:
|
||||
|
||||
@code
|
||||
char buffer[256];
|
||||
int nbytes;
|
||||
|
||||
nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
|
||||
while (nbytes > 0)
|
||||
{
|
||||
if (fwrite(buffer, 1, nbytes, stdout) != nbytes)
|
||||
{
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
|
||||
}
|
||||
|
||||
if (nbytes < 0)
|
||||
{
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
@endcode
|
||||
|
||||
Once you read the result of the remote command, you send an
|
||||
end-of-file to the channel, close it, and free the memory
|
||||
that it used:
|
||||
|
||||
@code
|
||||
ssh_channel_send_eof(channel);
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
|
||||
return SSH_OK;
|
||||
}
|
||||
@endcode
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,119 @@
|
||||
curve25519-sha256@libssh.org.txt Aris Adamantiadis <aris@badcode.be>
|
||||
21/9/2013
|
||||
|
||||
1. Introduction
|
||||
|
||||
This document describes the key exchange methode curve25519-sha256@libssh.org
|
||||
for SSH version 2 protocol. It is provided as an alternative to the existing
|
||||
key exchange mechanisms based on either Diffie-Hellman or Elliptic Curve Diffie-
|
||||
Hellman [RFC5656].
|
||||
The reason is the following : During summer of 2013, revelations from ex-
|
||||
consultant at NSA Edward Snowden gave proof that NSA willingly inserts backdoors
|
||||
into softwares, hardware components and published standards. While it is still
|
||||
believed that the mathematics behind ECC cryptography are still sound and solid,
|
||||
some people (including Bruce Schneier [SCHNEIER]), showed their lack of confidence
|
||||
in NIST-published curves such as nistp256, nistp384, nistp521, for which constant
|
||||
parameters (including the generator point) are defined without explanation. It
|
||||
is also believed that NSA had a word to say in their definition. These curves
|
||||
are not the most secure or fastest possible for their key sizes [DJB], and
|
||||
researchers think it is possible that NSA have ways of cracking NIST curves.
|
||||
It is also interesting to note that SSH belongs to the list of protocols the NSA
|
||||
claims to be able to eavesdrop. Having a secure replacement would make passive
|
||||
attacks much harder if such a backdoor exists.
|
||||
|
||||
However an alternative exists in the form of Curve25519. This algorithm has been
|
||||
proposed in 2006 by DJB [Curve25519]. Its main strengths are its speed, its
|
||||
constant-time run time (and resistance against side-channel attacks), and its
|
||||
lack of nebulous hard-coded constants.
|
||||
|
||||
The reference version being used in this document is the one described in
|
||||
[Curve25519] as implemented in the library NaCl [NaCl].
|
||||
This document does not attempt to provide alternatives to the ecdsa-sha1-*
|
||||
authentication keys.
|
||||
|
||||
2. Key exchange
|
||||
|
||||
The key exchange procedure is very similar to the one described chapter 4 of
|
||||
[RFC5656]. Public ephemeral keys are transmitted over SSH encapsulated into
|
||||
standard SSH strings.
|
||||
|
||||
The following is an overview of the key exchange process:
|
||||
|
||||
Client Server
|
||||
------ ------
|
||||
Generate ephemeral key pair.
|
||||
SSH_MSG_KEX_ECDH_INIT -------->
|
||||
Verify that client public key
|
||||
length is 32 bytes.
|
||||
Generate ephemeral key pair.
|
||||
Compute shared secret.
|
||||
Generate and sign exchange hash.
|
||||
<-------- SSH_MSG_KEX_ECDH_REPLY
|
||||
Verify that server public key length is 32 bytes.
|
||||
* Verify host keys belong to server.
|
||||
Compute shared secret.
|
||||
Generate exchange hash.
|
||||
Verify server's signature.
|
||||
|
||||
* Optional but strongly recommanded as this protects against MITM attacks.
|
||||
|
||||
This is implemented using the same messages as described in RFC5656 chapter 4
|
||||
|
||||
3. Method Name
|
||||
|
||||
The name of this key exchange method is "curve25519-sha256@libssh.org".
|
||||
|
||||
4. Implementation considerations
|
||||
|
||||
The whole method is based on the curve25519 scalar multiplication. In this
|
||||
method, a private key is a scalar of 256 bits, and a public key is a point
|
||||
of 256 bits.
|
||||
|
||||
4.1. Private key generation
|
||||
|
||||
A 32 bytes private key should be generated for each new connection,
|
||||
using a secure PRNG. The following actions must be done on the private key:
|
||||
mysecret[0] &= 248;
|
||||
mysecret[31] &= 127;
|
||||
mysecret[31] |= 64;
|
||||
In order to keep the key valid. However, many cryptographic libraries will do
|
||||
this automatically.
|
||||
It should be noted that, in opposition to NIST curves, no special validation
|
||||
should be done to ensure the result is a valid and secure private key.
|
||||
|
||||
4.2 Public key generation
|
||||
|
||||
The 32 bytes public key of either a client or a server must be generated using
|
||||
the 32 bytes private key and a common generator base. This base is defined as 9
|
||||
followed by all zeroes:
|
||||
const unsigned char basepoint[32] = {9};
|
||||
|
||||
The public key is calculated using the cryptographic scalar multiplication:
|
||||
const unsigned char privkey[32];
|
||||
unsigned char pubkey[32];
|
||||
crypto_scalarmult (pubkey, privkey, basepoint);
|
||||
However some cryptographic libraries may provide a combined function:
|
||||
crypto_scalarmult_base (pubkey, privkey);
|
||||
|
||||
It should be noted that, in opposition to NIST curves, no special validation
|
||||
should be done to ensure the received public keys are valid curves point. The
|
||||
Curve25519 algorithm ensure that every possible public key maps to a valid
|
||||
ECC Point.
|
||||
|
||||
4.3 Shared secret generation
|
||||
|
||||
The shared secret, k, is defined in SSH specifications to be a big integer.
|
||||
This number is calculated using the following procedure:
|
||||
|
||||
X is the 32 bytes point obtained by the scalar multiplication of the other
|
||||
side's public key and the local private key scalar.
|
||||
|
||||
The whole 32 bytes of the number X are then converted into a big integer k.
|
||||
This conversion follows the network byte order. This step differs from
|
||||
RFC5656.
|
||||
|
||||
[RFC5656] https://tools.ietf.org/html/rfc5656
|
||||
[SCHNEIER] https://www.schneier.com/blog/archives/2013/09/the_nsa_is_brea.html#c1675929
|
||||
[DJB] https://cr.yp.to/talks/2013.05.31/slides-dan+tanja-20130531-4x3.pdf
|
||||
[Curve25519] "Curve25519: new Diffie-Hellman speed records."
|
||||
https://cr.yp.to/ecdh/curve25519-20060209.pdf
|
||||
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
@page libssh_tutor_forwarding Chapter 7: Forwarding connections (tunnel)
|
||||
@section forwarding_connections Forwarding connections
|
||||
|
||||
Port forwarding comes in SSH protocol in two different flavours:
|
||||
direct or reverse port forwarding. Direct port forwarding is also
|
||||
named local port forwarding, and reverse port forwarding is also called
|
||||
remote port forwarding. SSH also allows X11 tunnels.
|
||||
|
||||
|
||||
|
||||
@subsection forwarding_direct Direct port forwarding
|
||||
|
||||
Direct port forwarding is from client to server. The client opens a tunnel,
|
||||
and forwards whatever data to the server. Then, the server connects to an
|
||||
end point. The end point can reside on another machine or on the SSH
|
||||
server itself.
|
||||
|
||||
Example of use of direct port forwarding:
|
||||
@verbatim
|
||||
Mail client application Google Mail
|
||||
| ^
|
||||
5555 (arbitrary) |
|
||||
| 143 (IMAP2)
|
||||
V |
|
||||
SSH client =====> SSH server
|
||||
|
||||
Legend:
|
||||
--P-->: port connections through port P
|
||||
=====>: SSH tunnel
|
||||
@endverbatim
|
||||
A mail client connects to port 5555 of a client. An encrypted tunnel is
|
||||
established to the server. The server connects to port 143 of Google Mail (the
|
||||
end point). Now the local mail client can retrieve mail.
|
||||
|
||||
|
||||
@subsection forwarding_reverse Reverse port forwarding
|
||||
|
||||
The reverse forwarding is slightly different. It goes from server to client,
|
||||
even though the client has the initiative of establishing the tunnel.
|
||||
Once the tunnel is established, the server will listen on a port. Whenever
|
||||
a connection to this port is made, the server forwards the data to the client.
|
||||
|
||||
Example of use of reverse port forwarding:
|
||||
@verbatim
|
||||
Local mail server Mail client application
|
||||
^ |
|
||||
| 5555 (arbitrary)
|
||||
143 (IMAP2) |
|
||||
| V
|
||||
SSH client <===== SSH server
|
||||
|
||||
Legend:
|
||||
--P-->: port connections through port P
|
||||
=====>: SSH tunnel
|
||||
@endverbatim
|
||||
In this example, the SSH client establishes the tunnel,
|
||||
but it is used to forward the connections established at
|
||||
the server to the client.
|
||||
|
||||
|
||||
@subsection forwarding_x11 X11 tunnels
|
||||
|
||||
X11 tunnels allow a remote application to display locally.
|
||||
|
||||
Example of use of X11 tunnels:
|
||||
@verbatim
|
||||
Local display Graphical application
|
||||
(X11 server) (X11 client)
|
||||
^ |
|
||||
| V
|
||||
SSH client <===== SSH server
|
||||
|
||||
Legend:
|
||||
----->: X11 connection through X11 display number
|
||||
=====>: SSH tunnel
|
||||
@endverbatim
|
||||
The SSH tunnel is established by the client.
|
||||
|
||||
How to establish X11 tunnels with libssh has already been described in
|
||||
this tutorial.
|
||||
|
||||
@see x11
|
||||
|
||||
|
||||
@subsection libssh_direct Doing direct port forwarding with libssh
|
||||
|
||||
To do direct port forwarding, call function ssh_channel_open_forward():
|
||||
- you need a separate channel for the tunnel as first parameter;
|
||||
- second and third parameters are the remote endpoint;
|
||||
- fourth and fifth parameters are sent to the remote server
|
||||
so that they can be logged on that server.
|
||||
|
||||
If you don't plan to forward the data you will receive to any local port,
|
||||
just put fake values like "localhost" and 5555 as your local host and port.
|
||||
|
||||
The example below shows how to open a direct channel that would be
|
||||
used to retrieve google's home page from the remote SSH server.
|
||||
|
||||
@code
|
||||
int direct_forwarding(ssh_session session)
|
||||
{
|
||||
ssh_channel forwarding_channel;
|
||||
int rc;
|
||||
char *http_get = "GET / HTTP/1.1\nHost: www.google.com\n\n";
|
||||
int nbytes, nwritten;
|
||||
|
||||
forwarding_channel = ssh_channel_new(session);
|
||||
if (forwarding_channel == NULL) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ssh_channel_open_forward(forwarding_channel,
|
||||
"www.google.com", 80,
|
||||
"localhost", 5555);
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
ssh_channel_free(forwarding_channel);
|
||||
return rc;
|
||||
}
|
||||
|
||||
nbytes = strlen(http_get);
|
||||
nwritten = ssh_channel_write(forwarding_channel,
|
||||
http_get,
|
||||
nbytes);
|
||||
if (nbytes != nwritten)
|
||||
{
|
||||
ssh_channel_free(forwarding_channel);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
ssh_channel_free(forwarding_channel);
|
||||
return SSH_OK;
|
||||
}
|
||||
@endcode
|
||||
|
||||
The data sent by Google can be retrieved for example with ssh_select()
|
||||
and ssh_channel_read(). Goggle's home page can then be displayed on the
|
||||
local SSH client, saved into a local file, made available on a local port,
|
||||
or whatever use you have for it.
|
||||
|
||||
|
||||
@subsection libssh_reverse Doing reverse port forwarding with libssh
|
||||
|
||||
To do reverse port forwarding, call ssh_channel_listen_forward(),
|
||||
then ssh_channel_accept_forward().
|
||||
|
||||
When you call ssh_channel_listen_forward(), you can let the remote server
|
||||
chose the non-privileged port it should listen to. Otherwise, you can chose
|
||||
your own privileged or non-privileged port. Beware that you should have
|
||||
administrative privileges on the remote server to open a privileged port
|
||||
(port number < 1024).
|
||||
|
||||
Below is an example of a very rough web server waiting for connections on port
|
||||
8080 of remote SSH server. The incoming connections are passed to the
|
||||
local libssh application, which handles them:
|
||||
|
||||
@code
|
||||
int web_server(ssh_session session)
|
||||
{
|
||||
int rc;
|
||||
ssh_channel channel;
|
||||
char buffer[256];
|
||||
int nbytes, nwritten;
|
||||
int port = 0;
|
||||
char *helloworld = ""
|
||||
"HTTP/1.1 200 OK\n"
|
||||
"Content-Type: text/html\n"
|
||||
"Content-Length: 113\n"
|
||||
"\n"
|
||||
"<html>\n"
|
||||
" <head>\n"
|
||||
" <title>Hello, World!</title>\n"
|
||||
" </head>\n"
|
||||
" <body>\n"
|
||||
" <h1>Hello, World!</h1>\n"
|
||||
" </body>\n"
|
||||
"</html>\n";
|
||||
|
||||
rc = ssh_channel_listen_forward(session, NULL, 8080, NULL);
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
fprintf(stderr, "Error opening remote port: %s\n",
|
||||
ssh_get_error(session));
|
||||
return rc;
|
||||
}
|
||||
|
||||
channel = ssh_channel_accept_forward(session, 60000, &port);
|
||||
if (channel == NULL)
|
||||
{
|
||||
fprintf(stderr, "Error waiting for incoming connection: %s\n",
|
||||
ssh_get_error(session));
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
while (1)
|
||||
{
|
||||
nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
|
||||
if (nbytes < 0)
|
||||
{
|
||||
fprintf(stderr, "Error reading incoming data: %s\n",
|
||||
ssh_get_error(session));
|
||||
ssh_channel_send_eof(channel);
|
||||
ssh_channel_free(channel);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
if (strncmp(buffer, "GET /", 5)) continue;
|
||||
|
||||
nbytes = strlen(helloworld);
|
||||
nwritten = ssh_channel_write(channel, helloworld, nbytes);
|
||||
if (nwritten != nbytes)
|
||||
{
|
||||
fprintf(stderr, "Error sending answer: %s\n",
|
||||
ssh_get_error(session));
|
||||
ssh_channel_send_eof(channel);
|
||||
ssh_channel_free(channel);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
printf("Sent answer\n");
|
||||
}
|
||||
|
||||
ssh_channel_send_eof(channel);
|
||||
ssh_channel_free(channel);
|
||||
return SSH_OK;
|
||||
}
|
||||
@endcode
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,473 @@
|
||||
/**
|
||||
@page libssh_tutor_guided_tour 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-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 can be done.
|
||||
|
||||
- When everything is finished, just close the channels, and then the connection.
|
||||
|
||||
The sftp and scp subsystems use channels, but libssh hides them to
|
||||
the programmer. If you want to use those subsystems, instead of a channel,
|
||||
you'll usually open a "sftp session" or a "scp session".
|
||||
|
||||
|
||||
@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 succeeded.
|
||||
@code
|
||||
#include <libssh/libssh.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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 <libssh/libssh.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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 English error string with 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 <libssh/libssh.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
ssh_session my_ssh_session;
|
||||
int rc;
|
||||
|
||||
my_ssh_session = ssh_new();
|
||||
if (my_ssh_session == NULL)
|
||||
exit(-1);
|
||||
|
||||
ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost");
|
||||
|
||||
rc = ssh_connect(my_ssh_session);
|
||||
if (rc != 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_session_is_known_server()
|
||||
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
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
int verify_knownhost(ssh_session session)
|
||||
{
|
||||
enum ssh_known_hosts_e state;
|
||||
unsigned char *hash = NULL;
|
||||
ssh_key srv_pubkey = NULL;
|
||||
size_t hlen;
|
||||
char buf[10];
|
||||
char *hexa;
|
||||
char *p;
|
||||
int cmp;
|
||||
int rc;
|
||||
|
||||
rc = ssh_get_server_publickey(session, &srv_pubkey);
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = ssh_get_publickey_hash(srv_pubkey,
|
||||
SSH_PUBLICKEY_HASH_SHA1,
|
||||
&hash,
|
||||
&hlen);
|
||||
ssh_key_free(srv_pubkey);
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
state = ssh_session_is_known_server(session);
|
||||
switch (state) {
|
||||
case SSH_KNOWN_HOSTS_OK:
|
||||
/* OK */
|
||||
|
||||
break;
|
||||
case SSH_KNOWN_HOSTS_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");
|
||||
ssh_clean_pubkey_hash(&hash);
|
||||
|
||||
return -1;
|
||||
case SSH_KNOWN_HOSTS_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");
|
||||
ssh_clean_pubkey_hash(&hash);
|
||||
|
||||
return -1;
|
||||
case SSH_KNOWN_HOSTS_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");
|
||||
|
||||
/* FALL THROUGH to SSH_SERVER_NOT_KNOWN behavior */
|
||||
|
||||
case SSH_KNOWN_HOSTS_UNKNOWN:
|
||||
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);
|
||||
ssh_string_free_char(hexa);
|
||||
ssh_clean_pubkey_hash(&hash);
|
||||
p = fgets(buf, sizeof(buf), stdin);
|
||||
if (p == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmp = strncasecmp(buf, "yes", 3);
|
||||
if (cmp != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = ssh_session_update_known_hosts(session);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Error %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
break;
|
||||
case SSH_KNOWN_HOSTS_ERROR:
|
||||
fprintf(stderr, "Error %s", ssh_get_error(session));
|
||||
ssh_clean_pubkey_hash(&hash);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssh_clean_pubkey_hash(&hash);
|
||||
return 0;
|
||||
}
|
||||
@endcode
|
||||
|
||||
@see ssh_connect
|
||||
@see ssh_disconnect
|
||||
@see ssh_get_error
|
||||
@see ssh_get_error_code
|
||||
@see ssh_get_server_publickey
|
||||
@see ssh_get_publickey_hash
|
||||
@see ssh_session_is_known_server
|
||||
@see ssh_session_update_known_hosts
|
||||
|
||||
|
||||
@subsection auth Authenticating the user
|
||||
|
||||
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 resources. In SSH, the two concepts
|
||||
are linked. After authentication, the server can grant the user access to
|
||||
several resources 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 <libssh/libssh.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
ssh_session my_ssh_session;
|
||||
int rc;
|
||||
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
|
||||
rc = ssh_connect(my_ssh_session);
|
||||
if (rc != 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_knownhost(), 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: ");
|
||||
rc = ssh_userauth_password(my_ssh_session, NULL, password);
|
||||
if (rc != 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 a remote command, open remote shells, transfer files,
|
||||
forward ports, etc.
|
||||
|
||||
The example below shows how to execute a remote command:
|
||||
|
||||
@code
|
||||
int show_remote_processes(ssh_session session)
|
||||
{
|
||||
ssh_channel channel;
|
||||
int rc;
|
||||
char buffer[256];
|
||||
int nbytes;
|
||||
|
||||
channel = ssh_channel_new(session);
|
||||
if (channel == NULL)
|
||||
return SSH_ERROR;
|
||||
|
||||
rc = ssh_channel_open_session(channel);
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
ssh_channel_free(channel);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ssh_channel_request_exec(channel, "ps aux");
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
return rc;
|
||||
}
|
||||
|
||||
nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
|
||||
while (nbytes > 0)
|
||||
{
|
||||
if (write(1, buffer, nbytes) != (unsigned int) nbytes)
|
||||
{
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
|
||||
}
|
||||
|
||||
if (nbytes < 0)
|
||||
{
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
ssh_channel_send_eof(channel);
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
|
||||
return SSH_OK;
|
||||
}
|
||||
@endcode
|
||||
|
||||
Each ssh_channel_request_exec() needs to be run on freshly created
|
||||
and connected (with ssh_channel_open_session()) channel.
|
||||
|
||||
@see @ref opening_shell
|
||||
@see @ref remote_command
|
||||
@see @ref sftp_subsystem
|
||||
@see @ref scp_subsystem
|
||||
|
||||
|
||||
@subsection errors Handling the errors
|
||||
|
||||
All the libssh functions which return an error value also set an English error message
|
||||
describing the problem.
|
||||
|
||||
Error values are typically SSH_ERROR for integer values, or NULL for pointers.
|
||||
|
||||
The function ssh_get_error() returns a pointer to the static error message.
|
||||
|
||||
ssh_error_code() returns the error code number : SSH_NO_ERROR,
|
||||
SSH_REQUEST_DENIED, SSH_INVALID_REQUEST, SSH_CONNECTION_LOST, SSH_FATAL,
|
||||
or SSH_INVALID_DATA. SSH_REQUEST_DENIED means the ssh server refused your
|
||||
request, but the situation is recoverable. The others mean something happened
|
||||
to the connection (some encryption problems, server problems, ...).
|
||||
SSH_INVALID_REQUEST means the library got some garbage from server, but
|
||||
might be recoverable. SSH_FATAL means the connection has an important
|
||||
problem and isn't probably recoverable.
|
||||
|
||||
Most of time, the error returned are SSH_FATAL, but some functions
|
||||
(generally the ssh_request_xxx ones) may fail because of server denying request.
|
||||
In these cases, SSH_REQUEST_DENIED is returned.
|
||||
|
||||
For thread safety, errors are bound to ssh_session objects.
|
||||
As long as your ssh_session object is not NULL, you can retrieve the last error
|
||||
message and error code from the ssh_session using ssh_get_error() and
|
||||
ssh_get_error_code() respectively.
|
||||
|
||||
The SFTP subsystem has its own error codes, in addition to libssh ones.
|
||||
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
@page libssh_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 completely different and independent project.
|
||||
|
||||
libssh can run on top of either libgcrypt or libcrypto,
|
||||
two general-purpose cryptographic libraries.
|
||||
|
||||
This tutorial concentrates for its main part on the "client" side of libssh.
|
||||
To learn how to accept incoming SSH connections (how to write a SSH server),
|
||||
you'll have to jump to the end of this document.
|
||||
|
||||
This tutorial describes libssh version 0.5.0. This version is a little different
|
||||
from the 0.4.X series. However, the examples should work with
|
||||
little changes on versions like 0.4.2 and later.
|
||||
|
||||
|
||||
Table of contents:
|
||||
|
||||
@subpage libssh_tutor_guided_tour
|
||||
|
||||
@subpage libssh_tutor_authentication
|
||||
|
||||
@subpage libssh_tutor_shell
|
||||
|
||||
@subpage libssh_tutor_command
|
||||
|
||||
@subpage libssh_tutor_sftp
|
||||
|
||||
@subpage libssh_tutor_scp
|
||||
|
||||
@subpage libssh_tutor_forwarding
|
||||
|
||||
@subpage libssh_tutor_threads
|
||||
|
||||
@subpage libssh_tutor_todo
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
|
||||
@page libssh_linking The Linking HowTo
|
||||
|
||||
@section dynamic Dynamic Linking
|
||||
|
||||
On UNIX and Windows systems its the same, you need at least the libssh.h
|
||||
header file and the libssh shared library.
|
||||
|
||||
@section static Static Linking
|
||||
|
||||
@warning <b>The libssh library is licensed under the LGPL! Make sure you
|
||||
understand what this means to your codebase if you want to distribute
|
||||
binaries and link statically against LGPL code!</b>
|
||||
|
||||
On UNIX systems linking against the static version of the library is the
|
||||
same as linking against the shared library. Both have the same name. Some
|
||||
build system require to use the full path to the static library.
|
||||
|
||||
To be able to compile the application you're developing you need to either pass
|
||||
LIBSSH_STATIC as a define in the compiler command line or define it before you
|
||||
include libssh.h. This is required cause the dynamic library needs to specify
|
||||
the dllimport attribute.
|
||||
|
||||
@code
|
||||
#define LIBSSH_STATIC 1
|
||||
#include <libssh/libssh.h>
|
||||
@endcode
|
||||
|
||||
If you're are statically linking with OpenSSL, read the "Linking your
|
||||
application" section in the NOTES.[OS] in the OpenSSL source tree!
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
|
||||
@mainpage
|
||||
|
||||
This is the online reference for developing with the libssh library. It
|
||||
documents the libssh C API and the C++ wrapper.
|
||||
|
||||
@section main-linking Linking
|
||||
|
||||
We created a small howto how to link libssh against your application, read
|
||||
@subpage libssh_linking.
|
||||
|
||||
@section main-tutorial Tutorial
|
||||
|
||||
You should start by reading @subpage libssh_tutorial, then reading the documentation of
|
||||
the interesting functions as you go.
|
||||
|
||||
@section main-features Features
|
||||
|
||||
The libssh library provides:
|
||||
|
||||
- <strong>Key Exchange Methods</strong>: <i>curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521</i>, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1
|
||||
- <strong>Public Key Algorithms</strong>: ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-rsa, rsa-sha2-512, rsa-sha2-256,ssh-dss
|
||||
- <strong>Ciphers</strong>: <i>aes256-ctr, aes192-ctr, aes128-ctr</i>, aes256-cbc (rijndael-cbc@lysator.liu.se), aes192-cbc, aes128-cbc, 3des-cbc, blowfish-cbc, none
|
||||
- <strong>Compression Schemes</strong>: zlib, <i>zlib@openssh.com</i>, none
|
||||
- <strong>MAC hashes</strong>: hmac-sha1, hmac-sha2-256, hmac-sha2-512, hmac-md5, none
|
||||
- <strong>Authentication</strong>: none, password, public-key, keyboard-interactive, <i>gssapi-with-mic</i>
|
||||
- <strong>Channels</strong>: shell, exec (incl. SCP wrapper), direct-tcpip, subsystem, <i>auth-agent-req@openssh.com</i>
|
||||
- <strong>Global Requests</strong>: tcpip-forward, forwarded-tcpip
|
||||
- <strong>Channel Requests</strong>: x11, pty, <i>exit-status, signal, exit-signal, keepalive@openssh.com, auth-agent-req@openssh.com</i>
|
||||
- <strong>Subsystems</strong>: sftp(version 3), <i>OpenSSH Extensions</i>
|
||||
- <strong>SFTP</strong>: <i>statvfs@openssh.com, fstatvfs@openssh.com</i>
|
||||
- <strong>Thread-safe</strong>: Just don't share sessions
|
||||
- <strong>Non-blocking</strong>: it can be used both blocking and non-blocking
|
||||
- <strong>Your sockets</strong>: the app hands over the socket, or uses libssh sockets
|
||||
- <b>OpenSSL</b> or <b>gcrypt</b>: builds with either
|
||||
|
||||
@section main-additional-features Additional Features
|
||||
|
||||
- Client <b>and</b> server support
|
||||
- SSHv2 and SSHv1 protocol support
|
||||
- Supports <a href="https://test.libssh.org/" target="_blank">Linux, UNIX, BSD, Solaris, OS/2 and Windows</a>
|
||||
- Automated test cases with nightly <a href="https://test.libssh.org/" target="_blank">tests</a>
|
||||
- Event model based on poll(2), or a poll(2)-emulation.
|
||||
|
||||
@section main-copyright Copyright Policy
|
||||
|
||||
libssh is a project with distributed copyright ownership, which means we prefer
|
||||
the copyright on parts of libssh to be held by individuals rather than
|
||||
corporations if possible. There are historical legal reasons for this, but one
|
||||
of the best ways to explain it is that it’s much easier to work with
|
||||
individuals who have ownership than corporate legal departments if we ever need
|
||||
to make reasonable compromises with people using and working with libssh.
|
||||
|
||||
We track the ownership of every part of libssh via git, our source code control
|
||||
system, so we know the provenance of every piece of code that is committed to
|
||||
libssh.
|
||||
|
||||
So if possible, if you’re doing libssh changes on behalf of a company who
|
||||
normally owns all the work you do please get them to assign personal copyright
|
||||
ownership of your changes to you as an individual, that makes things very easy
|
||||
for us to work with and avoids bringing corporate legal departments into the
|
||||
picture.
|
||||
|
||||
If you can’t do this we can still accept patches from you owned by your
|
||||
employer under a standard employment contract with corporate copyright
|
||||
ownership. It just requires a simple set-up process first.
|
||||
|
||||
We use a process very similar to the way things are done in the Linux Kernel
|
||||
community, so it should be very easy to get a sign off from your corporate
|
||||
legal department. The only changes we’ve made are to accommodate the license we
|
||||
use, which is LGPLv2 (or later) whereas the Linux kernel uses GPLv2.
|
||||
|
||||
The process is called signing.
|
||||
|
||||
How to sign your work
|
||||
----------------------
|
||||
|
||||
Once you have permission to contribute to libssh from your employer, simply
|
||||
email a copy of the following text from your corporate email address to:
|
||||
|
||||
contributing@libssh.org
|
||||
|
||||
@verbatim
|
||||
libssh Developer's Certificate of Origin. Version 1.0
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the appropriate
|
||||
version of the GNU General Public License; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best of
|
||||
my knowledge, is covered under an appropriate open source license
|
||||
and I have the right under that license to submit that work with
|
||||
modifications, whether created in whole or in part by me, under
|
||||
the GNU General Public License, in the appropriate version; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a) or (b) and I have not modified it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution are
|
||||
public and that a record of the contribution (including all
|
||||
metadata and personal information I submit with it, including my
|
||||
sign-off) is maintained indefinitely and may be redistributed
|
||||
consistent with the libssh Team's policies and the requirements of
|
||||
the GNU GPL where they are relevant.
|
||||
|
||||
(e) I am granting this work to this project 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 the option of the project) any later version.
|
||||
|
||||
https://www.gnu.org/licenses/lgpl-2.1.html
|
||||
@endverbatim
|
||||
|
||||
We will maintain a copy of that email as a record that you have the rights to
|
||||
contribute code to libssh under the required licenses whilst working for the
|
||||
company where the email came from.
|
||||
|
||||
Then when sending in a patch via the normal mechanisms described above, add a
|
||||
line that states:
|
||||
|
||||
@verbatim
|
||||
Signed-off-by: Random J Developer <random@developer.example.org>
|
||||
@endverbatim
|
||||
|
||||
using your real name and the email address you sent the original email you used
|
||||
to send the libssh Developer’s Certificate of Origin to us (sorry, no
|
||||
pseudonyms or anonymous contributions.)
|
||||
|
||||
That’s it! Such code can then quite happily contain changes that have copyright
|
||||
messages such as:
|
||||
|
||||
@verbatim
|
||||
(c) Example Corporation.
|
||||
@endverbatim
|
||||
|
||||
and can be merged into the libssh codebase in the same way as patches from any
|
||||
other individual. You don’t need to send in a copy of the libssh Developer’s
|
||||
Certificate of Origin for each patch, or inside each patch. Just the sign-off
|
||||
message is all that is required once we’ve received the initial email.
|
||||
|
||||
Have fun and happy libssh hacking!
|
||||
|
||||
The libssh Team
|
||||
|
||||
@section main-rfc Internet standard
|
||||
|
||||
@subsection main-rfc-secsh Secure Shell (SSH)
|
||||
|
||||
The following RFC documents described SSH-2 protcol as an Internet standard.
|
||||
|
||||
- <a href="https://tools.ietf.org/html/rfc4250" target="_blank">RFC 4250</a>,
|
||||
The Secure Shell (SSH) Protocol Assigned Numbers
|
||||
- <a href="https://tools.ietf.org/html/rfc4251" target="_blank">RFC 4251</a>,
|
||||
The Secure Shell (SSH) Protocol Architecture
|
||||
- <a href="https://tools.ietf.org/html/rfc4252" target="_blank">RFC 4252</a>,
|
||||
The Secure Shell (SSH) Authentication Protocol
|
||||
- <a href="https://tools.ietf.org/html/rfc4253" target="_blank">RFC 4253</a>,
|
||||
The Secure Shell (SSH) Transport Layer Protocol
|
||||
- <a href="https://tools.ietf.org/html/rfc4254" target="_blank">RFC 4254</a>,
|
||||
The Secure Shell (SSH) Connection Protocol
|
||||
- <a href="https://tools.ietf.org/html/rfc4255" target="_blank">RFC 4255</a>,
|
||||
Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints
|
||||
(not implemented in libssh)
|
||||
- <a href="https://tools.ietf.org/html/rfc4256" target="_blank">RFC 4256</a>,
|
||||
Generic Message Exchange Authentication for the Secure Shell Protocol (SSH)
|
||||
- <a href="https://tools.ietf.org/html/rfc4335" target="_blank">RFC 4335</a>,
|
||||
The Secure Shell (SSH) Session Channel Break Extension
|
||||
- <a href="https://tools.ietf.org/html/rfc4344" target="_blank">RFC 4344</a>,
|
||||
The Secure Shell (SSH) Transport Layer Encryption Modes
|
||||
- <a href="https://tools.ietf.org/html/rfc4345" target="_blank">RFC 4345</a>,
|
||||
Improved Arcfour Modes for the Secure Shell (SSH) Transport Layer Protocol
|
||||
|
||||
It was later modified and expanded by the following RFCs.
|
||||
|
||||
- <a href="https://tools.ietf.org/html/rfc4419" target="_blank">RFC 4419</a>,
|
||||
Diffie-Hellman Group Exchange for the Secure Shell (SSH) Transport Layer
|
||||
Protocol
|
||||
- <a href="https://tools.ietf.org/html/rfc4432" target="_blank">RFC 4432</a>,
|
||||
RSA Key Exchange for the Secure Shell (SSH) Transport Layer Protocol
|
||||
(not implemented in libssh)
|
||||
- <a href="https://tools.ietf.org/html/rfc4462" target="_blank">RFC 4462</a>,
|
||||
Generic Security Service Application Program Interface (GSS-API)
|
||||
Authentication and Key Exchange for the Secure Shell (SSH) Protocol
|
||||
(only the authentication implemented in libssh)
|
||||
- <a href="https://tools.ietf.org/html/rfc4716" target="_blank">RFC 4716</a>,
|
||||
The Secure Shell (SSH) Public Key File Format
|
||||
(not implemented in libssh)
|
||||
- <a href="https://tools.ietf.org/html/rfc5647" target="_blank">RFC 5647</a>,
|
||||
AES Galois Counter Mode for the Secure Shell Transport Layer Protocol
|
||||
(the algorithm negotiation implemented according to openssh.com)
|
||||
- <a href="https://tools.ietf.org/html/rfc5656" target="_blank">RFC 5656</a>,
|
||||
Elliptic Curve Algorithm Integration in the Secure Shell Transport Layer
|
||||
- <a href="https://tools.ietf.org/html/rfc6594" target="_blank">RFC 6594</a>,
|
||||
Use of the SHA-256 Algorithm with RSA, DSA, and ECDSA in SSHFP Resource Records
|
||||
(not implemented in libssh)
|
||||
- <a href="https://tools.ietf.org/html/rfc6668" target="_blank">RFC 6668</a>,
|
||||
SHA-2 Data Integrity Verification for the Secure Shell (SSH) Transport Layer Protocol
|
||||
- <a href="https://tools.ietf.org/html/rfc7479" target="_blank">RFC 7479</a>,
|
||||
Using Ed25519 in SSHFP Resource Records
|
||||
(not implemented in libssh)
|
||||
- <a href="https://tools.ietf.org/html/rfc8160" target="_blank">RFC 8160</a>,
|
||||
IUTF8 Terminal Mode in Secure Shell (SSH)
|
||||
(not handled in libssh)
|
||||
- <a href="https://tools.ietf.org/html/rfc8270" target="_blank">RFC 8270</a>,
|
||||
Increase the Secure Shell Minimum Recommended Diffie-Hellman Modulus Size to 2048 Bits
|
||||
- <a href="https://tools.ietf.org/html/rfc8308" target="_blank">RFC 8308</a>,
|
||||
Extension Negotiation in the Secure Shell (SSH) Protocol
|
||||
(only the "server-sig-algs" extension implemented)
|
||||
- <a href="https://tools.ietf.org/html/rfc8332" target="_blank">RFC 8332</a>,
|
||||
Use of RSA Keys with SHA-256 and SHA-512 in the Secure Shell (SSH) Protocol
|
||||
|
||||
There are also drafts that are being currently developed and followed.
|
||||
|
||||
- <a href="https://tools.ietf.org/html/draft-ietf-curdle-ssh-kex-sha2-10" target="_blank">draft-ietf-curdle-ssh-kex-sha2-10</a>
|
||||
Key Exchange (KEX) Method Updates and Recommendations for Secure Shell (SSH)
|
||||
- <a href="https://tools.ietf.org/html/draft-miller-ssh-agent-03" target="_blank">draft-miller-ssh-agent-03</a>
|
||||
SSH Agent Protocol
|
||||
- <a href="https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves-12" target="_blank">draft-ietf-curdle-ssh-curves-12</a>
|
||||
Secure Shell (SSH) Key Exchange Method using Curve25519 and Curve448
|
||||
|
||||
Interesting cryptography documents:
|
||||
|
||||
- <a href="https://www.cryptsoft.com/pkcs11doc/" target="_blank">PKCS #11</a>, PKCS #11 reference documents, describing interface with smartcards.
|
||||
|
||||
@subsection main-rfc-sftp Secure Shell File Transfer Protocol (SFTP)
|
||||
|
||||
The protocol is not an Internet standard but it is still widely implemented.
|
||||
OpenSSH and most other implementation implement Version 3 of the protocol. We
|
||||
do the same in libssh.
|
||||
|
||||
- <a href="https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02" target="_blank">
|
||||
draft-ietf-secsh-filexfer-02.txt</a>,
|
||||
SSH File Transfer Protocol
|
||||
|
||||
@subsection main-rfc-extensions Secure Shell Extensions
|
||||
|
||||
The OpenSSH project has defined some extensions to the protocol. We support some of
|
||||
them like the statvfs calls in SFTP or the ssh-agent.
|
||||
|
||||
- <a href="https://api.libssh.org/rfc/PROTOCOL" target="_blank">
|
||||
OpenSSH's deviations and extensions</a>
|
||||
- <a href="https://api.libssh.org/rfc/PROTOCOL.certkeys" target="_blank">
|
||||
OpenSSH's pubkey certificate authentication</a>
|
||||
- <a href="https://api.libssh.org/rfc/PROTOCOL.chacha20poly1305" target="_blank">
|
||||
chacha20-poly1305@openssh.com authenticated encryption mode</a>
|
||||
- <a href="https://api.libssh.org/rfc/PROTOCOL.key" target="_blank">
|
||||
OpenSSH private key format (openssh-key-v1)</a>
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
@page libssh_tutor_scp Chapter 6: The SCP subsystem
|
||||
@section scp_subsystem The SCP subsystem
|
||||
|
||||
The SCP subsystem has far less functionality than the SFTP subsystem.
|
||||
However, if you only need to copy files from and to the remote system,
|
||||
it does its job.
|
||||
|
||||
|
||||
@subsection scp_session Opening and closing a SCP session
|
||||
|
||||
Like in the SFTP subsystem, you don't handle the SSH channels directly.
|
||||
Instead, you open a "SCP session".
|
||||
|
||||
When you open your SCP session, you have to choose between read or write mode.
|
||||
You can't do both in the same session. So you specify either SSH_SCP_READ or
|
||||
SSH_SCP_WRITE as the second parameter of function ssh_scp_new().
|
||||
|
||||
Another important mode flag for opening your SCP session is SSH_SCP_RECURSIVE.
|
||||
When you use SSH_SCP_RECURSIVE, you declare that you are willing to emulate
|
||||
the behaviour of "scp -r" command in your program, no matter it is for
|
||||
reading or for writing.
|
||||
|
||||
Once your session is created, you initialize it with ssh_scp_init(). When
|
||||
you have finished transferring files, you terminate the SCP connection with
|
||||
ssh_scp_close(). Finally, you can dispose the SCP connection with
|
||||
ssh_scp_free().
|
||||
|
||||
The example below does the maintenance work to open a SCP connection for writing in
|
||||
recursive mode:
|
||||
|
||||
@code
|
||||
int scp_write(ssh_session session)
|
||||
{
|
||||
ssh_scp scp;
|
||||
int rc;
|
||||
|
||||
scp = ssh_scp_new
|
||||
(session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, ".");
|
||||
if (scp == NULL)
|
||||
{
|
||||
fprintf(stderr, "Error allocating scp session: %s\n",
|
||||
ssh_get_error(session));
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
rc = ssh_scp_init(scp);
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
fprintf(stderr, "Error initializing scp session: %s\n",
|
||||
ssh_get_error(session));
|
||||
ssh_scp_free(scp);
|
||||
return rc;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
ssh_scp_close(scp);
|
||||
ssh_scp_free(scp);
|
||||
return SSH_OK;
|
||||
}
|
||||
@endcode
|
||||
|
||||
The example below shows how to open a connection to read a single file:
|
||||
|
||||
@code
|
||||
int scp_read(ssh_session session)
|
||||
{
|
||||
ssh_scp scp;
|
||||
int rc;
|
||||
|
||||
scp = ssh_scp_new
|
||||
(session, SSH_SCP_READ, "helloworld/helloworld.txt");
|
||||
if (scp == NULL)
|
||||
{
|
||||
fprintf(stderr, "Error allocating scp session: %s\n",
|
||||
ssh_get_error(session));
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
rc = ssh_scp_init(scp);
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
fprintf(stderr, "Error initializing scp session: %s\n",
|
||||
ssh_get_error(session));
|
||||
ssh_scp_free(scp);
|
||||
return rc;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
ssh_scp_close(scp);
|
||||
ssh_scp_free(scp);
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
@endcode
|
||||
|
||||
|
||||
@subsection scp_write Creating files and directories
|
||||
|
||||
You create directories with ssh_scp_push_directory(). In recursive mode,
|
||||
you are placed in this directory once it is created. If the directory
|
||||
already exists and if you are in recursive mode, you simply enter that
|
||||
directory.
|
||||
|
||||
Creating files is done in two steps. First, you prepare the writing with
|
||||
ssh_scp_push_file(). Then, you write the data with ssh_scp_write().
|
||||
The length of the data to write must be identical between both function calls.
|
||||
There's no need to "open" nor "close" the file, this is done automatically
|
||||
on the remote end. If the file already exists, it is overwritten and truncated.
|
||||
|
||||
The following example creates a new directory named "helloworld/", then creates
|
||||
a file named "helloworld.txt" in that directory:
|
||||
|
||||
@code
|
||||
int scp_helloworld(ssh_session session, ssh_scp scp)
|
||||
{
|
||||
int rc;
|
||||
const char *helloworld = "Hello, world!\n";
|
||||
int length = strlen(helloworld);
|
||||
|
||||
rc = ssh_scp_push_directory(scp, "helloworld", S_IRWXU);
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
fprintf(stderr, "Can't create remote directory: %s\n",
|
||||
ssh_get_error(session));
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ssh_scp_push_file
|
||||
(scp, "helloworld.txt", length, S_IRUSR | S_IWUSR);
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
fprintf(stderr, "Can't open remote file: %s\n",
|
||||
ssh_get_error(session));
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ssh_scp_write(scp, helloworld, length);
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
fprintf(stderr, "Can't write to remote file: %s\n",
|
||||
ssh_get_error(session));
|
||||
return rc;
|
||||
}
|
||||
|
||||
return SSH_OK;
|
||||
}
|
||||
@endcode
|
||||
|
||||
|
||||
@subsection scp_recursive_write Copying full directory trees to the remote server
|
||||
|
||||
Let's say you want to copy the following tree of files to the remote site:
|
||||
|
||||
@verbatim
|
||||
+-- file1
|
||||
+-- B --+
|
||||
| +-- file2
|
||||
-- A --+
|
||||
| +-- file3
|
||||
+-- C --+
|
||||
+-- file4
|
||||
@endverbatim
|
||||
|
||||
You would do it that way:
|
||||
- open the session in recursive mode
|
||||
- enter directory A
|
||||
- enter its subdirectory B
|
||||
- create file1 in B
|
||||
- create file2 in B
|
||||
- leave directory B
|
||||
- enter subdirectory C
|
||||
- create file3 in C
|
||||
- create file4 in C
|
||||
- leave directory C
|
||||
- leave directory A
|
||||
|
||||
To leave a directory, call ssh_scp_leave_directory().
|
||||
|
||||
|
||||
@subsection scp_read Reading files and directories
|
||||
|
||||
|
||||
To receive files, you pull requests from the other side with ssh_scp_pull_request().
|
||||
If this function returns SSH_SCP_REQUEST_NEWFILE, then you must get ready for
|
||||
the reception. You can get the size of the data to receive with ssh_scp_request_get_size()
|
||||
and allocate a buffer accordingly. When you are ready, you accept the request with
|
||||
ssh_scp_accept_request(), then read the data with ssh_scp_read().
|
||||
|
||||
The following example receives a single file. The name of the file to
|
||||
receive has been given earlier, when the scp session was opened:
|
||||
|
||||
@code
|
||||
int scp_receive(ssh_session session, ssh_scp scp)
|
||||
{
|
||||
int rc;
|
||||
int size, mode;
|
||||
char *filename, *buffer;
|
||||
|
||||
rc = ssh_scp_pull_request(scp);
|
||||
if (rc != SSH_SCP_REQUEST_NEWFILE)
|
||||
{
|
||||
fprintf(stderr, "Error receiving information about file: %s\n",
|
||||
ssh_get_error(session));
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
size = ssh_scp_request_get_size(scp);
|
||||
filename = strdup(ssh_scp_request_get_filename(scp));
|
||||
mode = ssh_scp_request_get_permissions(scp);
|
||||
printf("Receiving file %s, size %d, permissions 0%o\n",
|
||||
filename, size, mode);
|
||||
free(filename);
|
||||
|
||||
buffer = malloc(size);
|
||||
if (buffer == NULL)
|
||||
{
|
||||
fprintf(stderr, "Memory allocation error\n");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
ssh_scp_accept_request(scp);
|
||||
rc = ssh_scp_read(scp, buffer, size);
|
||||
if (rc == SSH_ERROR)
|
||||
{
|
||||
fprintf(stderr, "Error receiving file data: %s\n",
|
||||
ssh_get_error(session));
|
||||
free(buffer);
|
||||
return rc;
|
||||
}
|
||||
printf("Done\n");
|
||||
|
||||
write(1, buffer, size);
|
||||
free(buffer);
|
||||
|
||||
rc = ssh_scp_pull_request(scp);
|
||||
if (rc != SSH_SCP_REQUEST_EOF)
|
||||
{
|
||||
fprintf(stderr, "Unexpected request: %s\n",
|
||||
ssh_get_error(session));
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
return SSH_OK;
|
||||
}
|
||||
@endcode
|
||||
|
||||
In this example, since we just requested a single file, we expect ssh_scp_request()
|
||||
to return SSH_SCP_REQUEST_NEWFILE first, then SSH_SCP_REQUEST_EOF. That's quite a
|
||||
naive approach; for example, the remote server might send a warning as well
|
||||
(return code SSH_SCP_REQUEST_WARNING) and the example would fail. A more comprehensive
|
||||
reception program would receive the requests in a loop and analyze them carefully
|
||||
until SSH_SCP_REQUEST_EOF has been received.
|
||||
|
||||
|
||||
@subsection scp_recursive_read Receiving full directory trees from the remote server
|
||||
|
||||
If you opened the SCP session in recursive mode, the remote end will be
|
||||
telling you when to change directory.
|
||||
|
||||
In that case, when ssh_scp_pull_request() answers
|
||||
SSH_SCP_REQUEST_NEWDIRECTORY, you should make that local directory (if
|
||||
it does not exist yet) and enter it. When ssh_scp_pull_request() answers
|
||||
SSH_SCP_REQUEST_ENDDIRECTORY, you should leave the current directory.
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,431 @@
|
||||
/**
|
||||
@page libssh_tutor_sftp Chapter 5: The SFTP subsystem
|
||||
@section sftp_subsystem The SFTP subsystem
|
||||
|
||||
SFTP stands for "Secure File Transfer Protocol". It enables you to safely
|
||||
transfer files between the local and the remote computer. It reminds a lot
|
||||
of the old FTP protocol.
|
||||
|
||||
SFTP is a rich protocol. It lets you do over the network almost everything
|
||||
that you can do with local files:
|
||||
- send files
|
||||
- modify only a portion of a file
|
||||
- receive files
|
||||
- receive only a portion of a file
|
||||
- get file owner and group
|
||||
- get file permissions
|
||||
- set file owner and group
|
||||
- set file permissions
|
||||
- remove files
|
||||
- rename files
|
||||
- create a directory
|
||||
- remove a directory
|
||||
- retrieve the list of files in a directory
|
||||
- get the target of a symbolic link
|
||||
- create symbolic links
|
||||
- get information about mounted filesystems.
|
||||
|
||||
The current implemented version of the SFTP protocol is version 3. All functions
|
||||
aren't implemented yet, but the most important are.
|
||||
|
||||
|
||||
@subsection sftp_session Opening and closing a SFTP session
|
||||
|
||||
Unlike with remote shells and remote commands, when you use the SFTP subsystem,
|
||||
you don't handle directly the SSH channels. Instead, you open a "SFTP session".
|
||||
|
||||
The function sftp_new() creates a new SFTP session. The function sftp_init()
|
||||
initializes it. The function sftp_free() deletes it.
|
||||
|
||||
As you see, all the SFTP-related functions start with the "sftp_" prefix
|
||||
instead of the usual "ssh_" prefix.
|
||||
|
||||
The example below shows how to use these functions:
|
||||
|
||||
@code
|
||||
#include <libssh/sftp.h>
|
||||
|
||||
int sftp_helloworld(ssh_session session)
|
||||
{
|
||||
sftp_session sftp;
|
||||
int rc;
|
||||
|
||||
sftp = sftp_new(session);
|
||||
if (sftp == NULL)
|
||||
{
|
||||
fprintf(stderr, "Error allocating SFTP session: %s\n",
|
||||
ssh_get_error(session));
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
rc = sftp_init(sftp);
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
fprintf(stderr, "Error initializing SFTP session: code %d.\n",
|
||||
sftp_get_error(sftp));
|
||||
sftp_free(sftp);
|
||||
return rc;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
sftp_free(sftp);
|
||||
return SSH_OK;
|
||||
}
|
||||
@endcode
|
||||
|
||||
|
||||
@subsection sftp_errors Analyzing SFTP errors
|
||||
|
||||
In case of a problem, the function sftp_get_error() returns a SFTP-specific
|
||||
error number, in addition to the regular SSH error number returned by
|
||||
ssh_get_error_number().
|
||||
|
||||
Possible errors are:
|
||||
- SSH_FX_OK: no error
|
||||
- SSH_FX_EOF: end-of-file encountered
|
||||
- SSH_FX_NO_SUCH_FILE: file does not exist
|
||||
- SSH_FX_PERMISSION_DENIED: permission denied
|
||||
- SSH_FX_FAILURE: generic failure
|
||||
- SSH_FX_BAD_MESSAGE: garbage received from server
|
||||
- SSH_FX_NO_CONNECTION: no connection has been set up
|
||||
- SSH_FX_CONNECTION_LOST: there was a connection, but we lost it
|
||||
- SSH_FX_OP_UNSUPPORTED: operation not supported by libssh yet
|
||||
- SSH_FX_INVALID_HANDLE: invalid file handle
|
||||
- SSH_FX_NO_SUCH_PATH: no such file or directory path exists
|
||||
- SSH_FX_FILE_ALREADY_EXISTS: an attempt to create an already existing file or directory has been made
|
||||
- SSH_FX_WRITE_PROTECT: write-protected filesystem
|
||||
- SSH_FX_NO_MEDIA: no media was in remote drive
|
||||
|
||||
|
||||
@subsection sftp_mkdir Creating a directory
|
||||
|
||||
The function sftp_mkdir() takes the "SFTP session" we just created as
|
||||
its first argument. It also needs the name of the file to create, and the
|
||||
desired permissions. The permissions are the same as for the usual mkdir()
|
||||
function. To get a comprehensive list of the available permissions, use the
|
||||
"man 2 stat" command. The desired permissions are combined with the remote
|
||||
user's mask to determine the effective permissions.
|
||||
|
||||
The code below creates a directory named "helloworld" in the current directory that
|
||||
can be read and written only by its owner:
|
||||
|
||||
@code
|
||||
#include <libssh/sftp.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
int sftp_helloworld(ssh_session session, sftp_session sftp)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = sftp_mkdir(sftp, "helloworld", S_IRWXU);
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
if (sftp_get_error(sftp) != SSH_FX_FILE_ALREADY_EXISTS)
|
||||
{
|
||||
fprintf(stderr, "Can't create directory: %s\n",
|
||||
ssh_get_error(session));
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
return SSH_OK;
|
||||
}
|
||||
@endcode
|
||||
|
||||
Unlike its equivalent in the SCP subsystem, this function does NOT change the
|
||||
current directory to the newly created subdirectory.
|
||||
|
||||
|
||||
@subsection sftp_write Copying a file to the remote computer
|
||||
|
||||
You handle the contents of a remote file just like you would do with a
|
||||
local file: you open the file in a given mode, move the file pointer in it,
|
||||
read or write data, and close the file.
|
||||
|
||||
The sftp_open() function is very similar to the regular open() function,
|
||||
excepted that it returns a file handle of type sftp_file. This file handle
|
||||
is then used by the other file manipulation functions and remains valid
|
||||
until you close the remote file with sftp_close().
|
||||
|
||||
The example below creates a new file named "helloworld.txt" in the
|
||||
newly created "helloworld" directory. If the file already exists, it will
|
||||
be truncated. It then writes the famous "Hello, World!" sentence to the
|
||||
file, followed by a new line character. Finally, the file is closed:
|
||||
|
||||
@code
|
||||
#include <libssh/sftp.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
int sftp_helloworld(ssh_session session, sftp_session sftp)
|
||||
{
|
||||
int access_type = O_WRONLY | O_CREAT | O_TRUNC;
|
||||
sftp_file file;
|
||||
const char *helloworld = "Hello, World!\n";
|
||||
int length = strlen(helloworld);
|
||||
int rc, nwritten;
|
||||
|
||||
...
|
||||
|
||||
file = sftp_open(sftp, "helloworld/helloworld.txt",
|
||||
access_type, S_IRWXU);
|
||||
if (file == NULL)
|
||||
{
|
||||
fprintf(stderr, "Can't open file for writing: %s\n",
|
||||
ssh_get_error(session));
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
nwritten = sftp_write(file, helloworld, length);
|
||||
if (nwritten != length)
|
||||
{
|
||||
fprintf(stderr, "Can't write data to file: %s\n",
|
||||
ssh_get_error(session));
|
||||
sftp_close(file);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
rc = sftp_close(file);
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
fprintf(stderr, "Can't close the written file: %s\n",
|
||||
ssh_get_error(session));
|
||||
return rc;
|
||||
}
|
||||
|
||||
return SSH_OK;
|
||||
}
|
||||
@endcode
|
||||
|
||||
|
||||
@subsection sftp_read Reading a file from the remote computer
|
||||
|
||||
The nice thing with reading a file over the network through SFTP is that it
|
||||
can be done both in a synchronous way or an asynchronous way. If you read the file
|
||||
asynchronously, your program can do something else while it waits for the
|
||||
results to come.
|
||||
|
||||
Synchronous read is done with sftp_read().
|
||||
|
||||
Files are normally transferred in chunks. A good chunk size is 16 KB. The following
|
||||
example transfers the remote file "/etc/profile" in 16 KB chunks. For each chunk we
|
||||
request, sftp_read blocks till the data has been received:
|
||||
|
||||
@code
|
||||
// Good chunk size
|
||||
#define MAX_XFER_BUF_SIZE 16384
|
||||
|
||||
int sftp_read_sync(ssh_session session, sftp_session sftp)
|
||||
{
|
||||
int access_type;
|
||||
sftp_file file;
|
||||
char buffer[MAX_XFER_BUF_SIZE];
|
||||
int nbytes, nwritten, rc;
|
||||
int fd;
|
||||
|
||||
access_type = O_RDONLY;
|
||||
file = sftp_open(sftp, "/etc/profile",
|
||||
access_type, 0);
|
||||
if (file == NULL) {
|
||||
fprintf(stderr, "Can't open file for reading: %s\n",
|
||||
ssh_get_error(session));
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
fd = open("/path/to/profile", O_CREAT);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "Can't open file for writing: %s\n",
|
||||
strerror(errno));
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
nbytes = sftp_read(file, buffer, sizeof(buffer));
|
||||
if (nbytes == 0) {
|
||||
break; // EOF
|
||||
} else if (nbytes < 0) {
|
||||
fprintf(stderr, "Error while reading file: %s\n",
|
||||
ssh_get_error(session));
|
||||
sftp_close(file);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
nwritten = write(fd, buffer, nbytes);
|
||||
if (nwritten != nbytes) {
|
||||
fprintf(stderr, "Error writing: %s\n",
|
||||
strerror(errno));
|
||||
sftp_close(file);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
rc = sftp_close(file);
|
||||
if (rc != SSH_OK) {
|
||||
fprintf(stderr, "Can't close the read file: %s\n",
|
||||
ssh_get_error(session));
|
||||
return rc;
|
||||
}
|
||||
|
||||
return SSH_OK;
|
||||
}
|
||||
@endcode
|
||||
|
||||
Asynchronous read is done in two steps, first sftp_async_read_begin(), which
|
||||
returns a "request handle", and then sftp_async_read(), which uses that request handle.
|
||||
If the file has been opened in nonblocking mode, then sftp_async_read()
|
||||
might return SSH_AGAIN, which means that the request hasn't completed yet
|
||||
and that the function should be called again later on. Otherwise,
|
||||
sftp_async_read() waits for the data to come. To open a file in nonblocking mode,
|
||||
call sftp_file_set_nonblocking() right after you opened it. Default is blocking mode.
|
||||
|
||||
The example below reads a very big file in asynchronous, nonblocking, mode. Each
|
||||
time the data is not ready yet, a counter is incremented.
|
||||
|
||||
@code
|
||||
// Good chunk size
|
||||
#define MAX_XFER_BUF_SIZE 16384
|
||||
|
||||
int sftp_read_async(ssh_session session, sftp_session sftp)
|
||||
{
|
||||
int access_type;
|
||||
sftp_file file;
|
||||
char buffer[MAX_XFER_BUF_SIZE];
|
||||
int async_request;
|
||||
int nbytes;
|
||||
long counter;
|
||||
int rc;
|
||||
|
||||
access_type = O_RDONLY;
|
||||
file = sftp_open(sftp, "some_very_big_file",
|
||||
access_type, 0);
|
||||
if (file == NULL) {
|
||||
fprintf(stderr, "Can't open file for reading: %s\n",
|
||||
ssh_get_error(session));
|
||||
return SSH_ERROR;
|
||||
}
|
||||
sftp_file_set_nonblocking(file);
|
||||
|
||||
async_request = sftp_async_read_begin(file, sizeof(buffer));
|
||||
counter = 0L;
|
||||
usleep(10000);
|
||||
if (async_request >= 0) {
|
||||
nbytes = sftp_async_read(file, buffer, sizeof(buffer),
|
||||
async_request);
|
||||
} else {
|
||||
nbytes = -1;
|
||||
}
|
||||
|
||||
while (nbytes > 0 || nbytes == SSH_AGAIN) {
|
||||
if (nbytes > 0) {
|
||||
write(1, buffer, nbytes);
|
||||
async_request = sftp_async_read_begin(file, sizeof(buffer));
|
||||
} else {
|
||||
counter++;
|
||||
}
|
||||
usleep(10000);
|
||||
|
||||
if (async_request >= 0) {
|
||||
nbytes = sftp_async_read(file, buffer, sizeof(buffer),
|
||||
async_request);
|
||||
} else {
|
||||
nbytes = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (nbytes < 0) {
|
||||
fprintf(stderr, "Error while reading file: %s\n",
|
||||
ssh_get_error(session));
|
||||
sftp_close(file);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
printf("The counter has reached value: %ld\n", counter);
|
||||
|
||||
rc = sftp_close(file);
|
||||
if (rc != SSH_OK) {
|
||||
fprintf(stderr, "Can't close the read file: %s\n",
|
||||
ssh_get_error(session));
|
||||
return rc;
|
||||
}
|
||||
|
||||
return SSH_OK;
|
||||
}
|
||||
@endcode
|
||||
|
||||
@subsection sftp_ls Listing the contents of a directory
|
||||
|
||||
The functions sftp_opendir(), sftp_readdir(), sftp_dir_eof(),
|
||||
and sftp_closedir() enable to list the contents of a directory.
|
||||
They use a new handle_type, "sftp_dir", which gives access to the
|
||||
directory being read.
|
||||
|
||||
In addition, sftp_readdir() returns a "sftp_attributes" which is a pointer
|
||||
to a structure with information about a directory entry:
|
||||
- name: the name of the file or directory
|
||||
- size: its size in bytes
|
||||
- etc.
|
||||
|
||||
sftp_readdir() might return NULL under two conditions:
|
||||
- when the end of the directory has been met
|
||||
- when an error occurred
|
||||
|
||||
To tell the difference, call sftp_dir_eof().
|
||||
|
||||
The attributes must be freed with sftp_attributes_free() when no longer
|
||||
needed.
|
||||
|
||||
The following example reads the contents of some remote directory:
|
||||
|
||||
@code
|
||||
int sftp_list_dir(ssh_session session, sftp_session sftp)
|
||||
{
|
||||
sftp_dir dir;
|
||||
sftp_attributes attributes;
|
||||
int rc;
|
||||
|
||||
dir = sftp_opendir(sftp, "/var/log");
|
||||
if (!dir)
|
||||
{
|
||||
fprintf(stderr, "Directory not opened: %s\n",
|
||||
ssh_get_error(session));
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
printf("Name Size Perms Owner\tGroup\n");
|
||||
|
||||
while ((attributes = sftp_readdir(sftp, dir)) != NULL)
|
||||
{
|
||||
printf("%-20s %10llu %.8o %s(%d)\t%s(%d)\n",
|
||||
attributes->name,
|
||||
(long long unsigned int) attributes->size,
|
||||
attributes->permissions,
|
||||
attributes->owner,
|
||||
attributes->uid,
|
||||
attributes->group,
|
||||
attributes->gid);
|
||||
|
||||
sftp_attributes_free(attributes);
|
||||
}
|
||||
|
||||
if (!sftp_dir_eof(dir))
|
||||
{
|
||||
fprintf(stderr, "Can't list directory: %s\n",
|
||||
ssh_get_error(session));
|
||||
sftp_closedir(dir);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
rc = sftp_closedir(dir);
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
fprintf(stderr, "Can't close directory: %s\n",
|
||||
ssh_get_error(session));
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,361 @@
|
||||
/**
|
||||
@page libssh_tutor_shell Chapter 3: Opening a remote shell
|
||||
@section opening_shell Opening a remote shell
|
||||
|
||||
We already mentioned that a single SSH connection can be shared
|
||||
between several "channels". Channels can be used for different purposes.
|
||||
|
||||
This chapter shows how to open one of these channels, and how to use it to
|
||||
start a command interpreter on a remote computer.
|
||||
|
||||
|
||||
@subsection open_channel Opening and closing a channel
|
||||
|
||||
The ssh_channel_new() function creates a channel. It returns the channel as
|
||||
a variable of type ssh_channel.
|
||||
|
||||
Once you have this channel, you open a SSH session that uses it with
|
||||
ssh_channel_open_session().
|
||||
|
||||
Once you don't need the channel anymore, you can send an end-of-file
|
||||
to it with ssh_channel_close(). At this point, you can destroy the channel
|
||||
with ssh_channel_free().
|
||||
|
||||
The code sample below achieves these tasks:
|
||||
|
||||
@code
|
||||
int shell_session(ssh_session session)
|
||||
{
|
||||
ssh_channel channel;
|
||||
int rc;
|
||||
|
||||
channel = ssh_channel_new(session);
|
||||
if (channel == NULL)
|
||||
return SSH_ERROR;
|
||||
|
||||
rc = ssh_channel_open_session(channel);
|
||||
if (rc != SSH_OK)
|
||||
{
|
||||
ssh_channel_free(channel);
|
||||
return rc;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_send_eof(channel);
|
||||
ssh_channel_free(channel);
|
||||
|
||||
return SSH_OK;
|
||||
}
|
||||
@endcode
|
||||
|
||||
|
||||
@subsection interactive Interactive and non-interactive sessions
|
||||
|
||||
A "shell" is a command interpreter. It is said to be "interactive"
|
||||
if there is a human user typing the commands, one after the
|
||||
other. The contrary, a non-interactive shell, is similar to
|
||||
the execution of commands in the background: there is no attached
|
||||
terminal.
|
||||
|
||||
If you plan using an interactive shell, you need to create a
|
||||
pseud-terminal on the remote side. A remote terminal is usually referred
|
||||
to as a "pty", for "pseudo-teletype". The remote processes won't see the
|
||||
difference with a real text-oriented terminal.
|
||||
|
||||
If needed, you request the pty with the function ssh_channel_request_pty().
|
||||
Then you define its dimensions (number of rows and columns)
|
||||
with ssh_channel_change_pty_size().
|
||||
|
||||
Be your session interactive or not, the next step is to request a
|
||||
shell with ssh_channel_request_shell().
|
||||
|
||||
@code
|
||||
int interactive_shell_session(ssh_channel channel)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = ssh_channel_request_pty(channel);
|
||||
if (rc != SSH_OK) return rc;
|
||||
|
||||
rc = ssh_channel_change_pty_size(channel, 80, 24);
|
||||
if (rc != SSH_OK) return rc;
|
||||
|
||||
rc = ssh_channel_request_shell(channel);
|
||||
if (rc != SSH_OK) return rc;
|
||||
|
||||
...
|
||||
|
||||
return rc;
|
||||
}
|
||||
@endcode
|
||||
|
||||
|
||||
@subsection read_data Displaying the data sent by the remote computer
|
||||
|
||||
In your program, you will usually need to receive all the data "displayed"
|
||||
into the remote pty. You will usually analyse, log, or display this data.
|
||||
|
||||
ssh_channel_read() and ssh_channel_read_nonblocking() are the simplest
|
||||
way to read data from a channel. If you only need to read from a single
|
||||
channel, they should be enough.
|
||||
|
||||
The example below shows how to wait for remote data using ssh_channel_read():
|
||||
|
||||
@code
|
||||
int interactive_shell_session(ssh_channel channel)
|
||||
{
|
||||
int rc;
|
||||
char buffer[256];
|
||||
int nbytes;
|
||||
|
||||
rc = ssh_channel_request_pty(channel);
|
||||
if (rc != SSH_OK) return rc;
|
||||
|
||||
rc = ssh_channel_change_pty_size(channel, 80, 24);
|
||||
if (rc != SSH_OK) return rc;
|
||||
|
||||
rc = ssh_channel_request_shell(channel);
|
||||
if (rc != SSH_OK) return rc;
|
||||
|
||||
while (ssh_channel_is_open(channel) &&
|
||||
!ssh_channel_is_eof(channel))
|
||||
{
|
||||
nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
|
||||
if (nbytes < 0)
|
||||
return SSH_ERROR;
|
||||
|
||||
if (nbytes > 0)
|
||||
write(1, buffer, nbytes);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
@endcode
|
||||
|
||||
Unlike ssh_channel_read(), ssh_channel_read_nonblocking() never waits for
|
||||
remote data to be ready. It returns immediately.
|
||||
|
||||
If you plan to use ssh_channel_read_nonblocking() repeatedly in a loop,
|
||||
you should use a "passive wait" function like usleep(3) in the same
|
||||
loop. Otherwise, your program will consume all the CPU time, and your
|
||||
computer might become unresponsive.
|
||||
|
||||
|
||||
@subsection write_data Sending user input to the remote computer
|
||||
|
||||
User's input is sent to the remote site with ssh_channel_write().
|
||||
|
||||
The following example shows how to combine a nonblocking read from a SSH
|
||||
channel with a nonblocking read from the keyboard. The local input is then
|
||||
sent to the remote computer:
|
||||
|
||||
@code
|
||||
/* Under Linux, this function determines whether a key has been pressed.
|
||||
Under Windows, it is a standard function, so you need not redefine it.
|
||||
*/
|
||||
int kbhit()
|
||||
{
|
||||
struct timeval tv = { 0L, 0L };
|
||||
fd_set fds;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(0, &fds);
|
||||
|
||||
return select(1, &fds, NULL, NULL, &tv);
|
||||
}
|
||||
|
||||
/* A very simple terminal emulator:
|
||||
- print data received from the remote computer
|
||||
- send keyboard input to the remote computer
|
||||
*/
|
||||
int interactive_shell_session(ssh_channel channel)
|
||||
{
|
||||
/* Session and terminal initialization skipped */
|
||||
...
|
||||
|
||||
char buffer[256];
|
||||
int nbytes, nwritten;
|
||||
|
||||
while (ssh_channel_is_open(channel) &&
|
||||
!ssh_channel_is_eof(channel))
|
||||
{
|
||||
nbytes = ssh_channel_read_nonblocking(channel, buffer, sizeof(buffer), 0);
|
||||
if (nbytes < 0) return SSH_ERROR;
|
||||
if (nbytes > 0)
|
||||
{
|
||||
nwritten = write(1, buffer, nbytes);
|
||||
if (nwritten != nbytes) return SSH_ERROR;
|
||||
|
||||
if (!kbhit())
|
||||
{
|
||||
usleep(50000L); // 0.05 second
|
||||
continue;
|
||||
}
|
||||
|
||||
nbytes = read(0, buffer, sizeof(buffer));
|
||||
if (nbytes < 0) return SSH_ERROR;
|
||||
if (nbytes > 0)
|
||||
{
|
||||
nwritten = ssh_channel_write(channel, buffer, nbytes);
|
||||
if (nwritten != nbytes) return SSH_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
@endcode
|
||||
|
||||
Of course, this is a poor terminal emulator, since the echo from the keys
|
||||
pressed should not be done locally, but should be done by the remote side.
|
||||
Also, user's input should not be sent once "Enter" key is pressed, but
|
||||
immediately after each key is pressed. This can be accomplished
|
||||
by setting the local terminal to "raw" mode with the cfmakeraw(3) function.
|
||||
cfmakeraw() is a standard function under Linux, on other systems you can
|
||||
recode it with:
|
||||
|
||||
@code
|
||||
static void cfmakeraw(struct termios *termios_p)
|
||||
{
|
||||
termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
|
||||
termios_p->c_oflag &= ~OPOST;
|
||||
termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
|
||||
termios_p->c_cflag &= ~(CSIZE|PARENB);
|
||||
termios_p->c_cflag |= CS8;
|
||||
}
|
||||
@endcode
|
||||
|
||||
If you are not using a local terminal, but some kind of graphical
|
||||
environment, the solution to this kind of "echo" problems will be different.
|
||||
|
||||
|
||||
@subsection select_loop A more elaborate way to get the remote data
|
||||
|
||||
*** Warning: ssh_select() and ssh_channel_select() are not relevant anymore,
|
||||
since libssh is about to provide an easier system for asynchronous
|
||||
communications. This subsection should be removed then. ***
|
||||
|
||||
ssh_channel_read() and ssh_channel_read_nonblocking() functions are simple,
|
||||
but they are not adapted when you expect data from more than one SSH channel,
|
||||
or from other file descriptors. Last example showed how getting data from
|
||||
the standard input (the keyboard) at the same time as data from the SSH
|
||||
channel was complicated. The functions ssh_select() and ssh_channel_select()
|
||||
provide a more elegant way to wait for data coming from many sources.
|
||||
|
||||
The functions ssh_select() and ssh_channel_select() remind of the standard
|
||||
UNIX select(2) function. The idea is to wait for "something" to happen:
|
||||
incoming data to be read, outgoing data to block, or an exception to
|
||||
occur. Both these functions do a "passive wait", i.e. you can safely use
|
||||
them repeatedly in a loop, it will not consume exaggerate processor time
|
||||
and make your computer unresponsive. It is quite common to use these
|
||||
functions in your application's main loop.
|
||||
|
||||
The difference between ssh_select() and ssh_channel_select() is that
|
||||
ssh_channel_select() is simpler, but allows you only to watch SSH channels.
|
||||
ssh_select() is more complete and enables watching regular file descriptors
|
||||
as well, in the same function call.
|
||||
|
||||
Below is an example of a function that waits both for remote SSH data to come,
|
||||
as well as standard input from the keyboard:
|
||||
|
||||
@code
|
||||
int interactive_shell_session(ssh_session session, ssh_channel channel)
|
||||
{
|
||||
/* Session and terminal initialization skipped */
|
||||
...
|
||||
|
||||
char buffer[256];
|
||||
int nbytes, nwritten;
|
||||
|
||||
while (ssh_channel_is_open(channel) &&
|
||||
!ssh_channel_is_eof(channel))
|
||||
{
|
||||
struct timeval timeout;
|
||||
ssh_channel in_channels[2], out_channels[2];
|
||||
fd_set fds;
|
||||
int maxfd;
|
||||
|
||||
timeout.tv_sec = 30;
|
||||
timeout.tv_usec = 0;
|
||||
in_channels[0] = channel;
|
||||
in_channels[1] = NULL;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(0, &fds);
|
||||
FD_SET(ssh_get_fd(session), &fds);
|
||||
maxfd = ssh_get_fd(session) + 1;
|
||||
|
||||
ssh_select(in_channels, out_channels, maxfd, &fds, &timeout);
|
||||
|
||||
if (out_channels[0] != NULL)
|
||||
{
|
||||
nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
|
||||
if (nbytes < 0) return SSH_ERROR;
|
||||
if (nbytes > 0)
|
||||
{
|
||||
nwritten = write(1, buffer, nbytes);
|
||||
if (nwritten != nbytes) return SSH_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (FD_ISSET(0, &fds))
|
||||
{
|
||||
nbytes = read(0, buffer, sizeof(buffer));
|
||||
if (nbytes < 0) return SSH_ERROR;
|
||||
if (nbytes > 0)
|
||||
{
|
||||
nwritten = ssh_channel_write(channel, buffer, nbytes);
|
||||
if (nbytes != nwritten) return SSH_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
@endcode
|
||||
|
||||
|
||||
@subsection x11 Using graphical applications on the remote side
|
||||
|
||||
If your remote application is graphical, you can forward the X11 protocol to
|
||||
your local computer.
|
||||
|
||||
To do that, you first declare that you accept X11 connections with
|
||||
ssh_channel_accept_x11(). Then you create the forwarding tunnel for
|
||||
the X11 protocol with ssh_channel_request_x11().
|
||||
|
||||
The following code performs channel initialization and shell session
|
||||
opening, and handles a parallel X11 connection:
|
||||
|
||||
@code
|
||||
int interactive_shell_session(ssh_channel channel)
|
||||
{
|
||||
int rc;
|
||||
ssh_channel x11channel;
|
||||
|
||||
rc = ssh_channel_request_pty(channel);
|
||||
if (rc != SSH_OK) return rc;
|
||||
|
||||
rc = ssh_channel_change_pty_size(channel, 80, 24);
|
||||
if (rc != SSH_OK) return rc;
|
||||
|
||||
rc = ssh_channel_request_x11(channel, 0, NULL, NULL, 0);
|
||||
if (rc != SSH_OK) return rc;
|
||||
|
||||
rc = ssh_channel_request_shell(channel);
|
||||
if (rc != SSH_OK) return rc;
|
||||
|
||||
/* Read the data sent by the remote computer here */
|
||||
...
|
||||
}
|
||||
@endcode
|
||||
|
||||
Don't forget to set the $DISPLAY environment variable on the remote
|
||||
side, or the remote applications won't try using the X11 tunnel:
|
||||
|
||||
@code
|
||||
$ export DISPLAY=:0
|
||||
$ xclock &
|
||||
@endcode
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
@page libssh_tutor_todo To be done
|
||||
|
||||
*** To be written ***
|
||||
|
||||
@section sshd Writing a libssh-based server
|
||||
|
||||
*** To be written ***
|
||||
|
||||
@section cpp The libssh C++ wrapper
|
||||
|
||||
*** To be written ***
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Jan-Lukas Wynen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,22 @@
|
||||
# that style
|
||||
A plain, more modern HTML style for Doxygen
|
||||
|
||||
## Requirements
|
||||
- Doxygen (tested with version 1.8.13)
|
||||
- *optional*: a sass/scss compiler if you want to modify the style
|
||||
|
||||
## Simple usage
|
||||
Tell Doxygen about the files for that style as shown in [doxyfile.conf](doxyfile.conf). You might need to adjust the
|
||||
paths depending on where you installed that style.
|
||||
When you run Doxygen, all files are copied into to generated HTML folder. So you don't need to keep the originals around
|
||||
unless you want to re-generate the documentation.
|
||||
|
||||
## Advanced
|
||||
that style uses a custom javascript to hack some nice stripes into some tables. It has to be loaded from HTML. Hence you need
|
||||
to use the provided custom header. Since its default content may change when Doxygen is updated, there might be syntax error in
|
||||
the generated HTML. If this is the case, you can remove the custom header (adjust your doxyfile.conf). This has no
|
||||
disadvantages other than removing the stripes.
|
||||
|
||||
[that_style.css](that_style.css) was generated from the scss files in the folder [sass](sass). If you want to change the style,
|
||||
use those files in order to have better control. For instance, you can easily change most colors by modifying the variables
|
||||
in the beginning of [that_style.scss](sass/that_style.scss).
|
||||
@@ -0,0 +1,56 @@
|
||||
<!-- HTML header for doxygen 1.8.13-->
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen $doxygenversion"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
|
||||
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="$relpath^jquery.js"></script>
|
||||
<script type="text/javascript" src="$relpath^dynsections.js"></script>
|
||||
$treeview
|
||||
$search
|
||||
$mathjax
|
||||
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
|
||||
<script src="$relpath^striped_bg.js"></script>
|
||||
$extrastylesheet
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
|
||||
<!--BEGIN TITLEAREA-->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<!--BEGIN PROJECT_LOGO-->
|
||||
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
|
||||
<!--END PROJECT_LOGO-->
|
||||
<!--BEGIN PROJECT_NAME-->
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">$projectname
|
||||
<!--BEGIN PROJECT_NUMBER--> <span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
|
||||
</div>
|
||||
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
|
||||
</td>
|
||||
<!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME-->
|
||||
<!--BEGIN PROJECT_BRIEF-->
|
||||
<td style="padding-left: 0.5em;">
|
||||
<div id="projectbrief">$projectbrief</div>
|
||||
</td>
|
||||
<!--END PROJECT_BRIEF-->
|
||||
<!--END !PROJECT_NAME-->
|
||||
<!--BEGIN DISABLE_INDEX-->
|
||||
<!--BEGIN SEARCHENGINE-->
|
||||
<td>$searchbox</td>
|
||||
<!--END SEARCHENGINE-->
|
||||
<!--END DISABLE_INDEX-->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--END TITLEAREA-->
|
||||
<!-- end header part -->
|
||||
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="22"
|
||||
viewBox="0 0 6.3499999 5.8208335"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="doc.svg"
|
||||
inkscape:version="0.92.1 r">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="11.139212"
|
||||
inkscape:cy="14.811193"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:showpageshadow="false"
|
||||
units="px"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1357"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-291.17915)">
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#4d4d4d;stroke-width:0.26458329;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 3.315043,291.8406 H 1.4552083 v 4.49792 h 3.1749999 v -3.10055 z"
|
||||
id="path5095"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#4d4d4d;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 3.1837239,291.84114 v 1.71186 h 1.4472656 v -0.31418 H 3.4473958 v -1.39768 z"
|
||||
id="path5128"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect5132"
|
||||
width="2.1166668"
|
||||
height="0.26458332"
|
||||
x="1.8520833"
|
||||
y="293.82498" />
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect5136"
|
||||
width="1.0583334"
|
||||
height="0.26458332"
|
||||
x="1.8520832"
|
||||
y="294.35416" />
|
||||
<rect
|
||||
y="294.88333"
|
||||
x="1.8520832"
|
||||
height="0.26458332"
|
||||
width="1.8520833"
|
||||
id="rect5138"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect4543"
|
||||
width="1.5875"
|
||||
height="0.26458332"
|
||||
x="1.8520832"
|
||||
y="295.41248" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.5 KiB |
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="22"
|
||||
viewBox="0 0 6.3499998 5.8208335"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.1 r"
|
||||
sodipodi:docname="folderclosed.svg"
|
||||
inkscape:export-filename="/home/jl/Prog/doxygen_style/folderclosed.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="51.113139"
|
||||
inkscape:cx="7.7057751"
|
||||
inkscape:cy="12.584171"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:snap-global="false"
|
||||
units="px"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1357"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:measure-start="0,0"
|
||||
inkscape:measure-end="0,0" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-291.17915)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 0.52916667,292.2374 -0.26458334,0.52925 v 3.43958 H 4.7625001 v -3.43958 H 2.38125 L 2.1166667,292.2374 Z"
|
||||
id="rect4498"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cccccc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.66145831;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 2.9104167,292.76665 2.38125,293.56034 H 0.26458333 v 0.26464 H 2.38125 l 0.5291667,-0.79375 h 1.8520834 v -0.26458 z"
|
||||
id="rect4500"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="22"
|
||||
viewBox="0 0 6.3499998 5.8208335"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.1 r"
|
||||
sodipodi:docname="folderopen.svg"
|
||||
inkscape:export-filename="/home/jl/Prog/doxygen_style/folderopen.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="43.725861"
|
||||
inkscape:cx="8.2043861"
|
||||
inkscape:cy="13.464183"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:snap-global="false"
|
||||
units="px"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1357"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:measure-start="0,0"
|
||||
inkscape:measure-end="0,0" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-291.17915)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.66145831;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 0.52916667,292.23748 -0.26458334,0.52917 v 3.43958 H 4.762461 l 7.8e-5,-3.43958 H 2.38125 l -0.2645833,-0.52917 z"
|
||||
id="path5228"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5279"
|
||||
d="M 1.0583333,293.5604 H 5.55625 L 4.7625,296.20603 H 0.26458333 Z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ececec;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.66145831;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5234"
|
||||
d="M 1.0583333,294.35415 H 3.175 l 0.5291667,-0.52917 H 5.55625 L 4.7625,296.20603 H 0.26458333 Z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.66145831;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 5.8208332 5.8208335"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.1 r"
|
||||
sodipodi:docname="mag_glass.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="8.961936"
|
||||
inkscape:cy="10.205344"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:snap-bbox="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1357"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-global="false" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-291.17915)">
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.99999988;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 6.9101562 2.4082031 C 3.1105656 2.4082031 -5.9211895e-16 5.5081643 0 9.3027344 C 0 13.097342 3.1105656 16.197266 6.9101562 16.197266 C 8.2869348 16.197266 9.5698699 15.787508 10.650391 15.087891 L 15.162109 19.587891 L 16.636719 18.115234 L 12.214844 13.707031 C 13.214837 12.510659 13.818359 10.974238 13.818359 9.3027344 C 13.818359 5.5081643 10.709747 2.4082031 6.9101562 2.4082031 z M 6.9101562 4.9101562 C 9.3624717 4.9101562 11.324219 6.8631249 11.324219 9.3027344 C 11.324219 11.742382 9.3624717 13.695312 6.9101562 13.695312 C 4.4578408 13.695312 2.5019531 11.742382 2.5019531 9.3027344 C 2.5019531 6.8631249 4.4578408 4.9101562 6.9101562 4.9101562 z "
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0,291.17915)"
|
||||
id="rect4524" />
|
||||
<path
|
||||
transform="matrix(0.99422295,0,0,0.68955299,-0.83134947,91.755588)"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.63466448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
inkscape:transform-center-y="0.25905895"
|
||||
d="m 5.6074138,294.49889 -1.0836583,-1.87695 2.1673165,0 z"
|
||||
id="path4491" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="10.53333"
|
||||
height="32"
|
||||
viewBox="0 0 9.8749964 30"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.92.1 r"
|
||||
sodipodi:docname="nav_edge_inter.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="8.6823304"
|
||||
inkscape:cy="16.225639"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1357"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1022.3622)">
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 0,1022.3622 v 15 15 l 8,-15 z"
|
||||
id="path4143"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#333333;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.9375px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 1.2910156,1022.3496 -0.82421872,0.4473 7.87890622,14.5527 -7.87890622,14.5527 0.82421872,0.4473 8.1210938,-15 z"
|
||||
id="path5240"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="8.5333338"
|
||||
height="32"
|
||||
viewBox="0 0 8.0000001 30"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.92.1 r"
|
||||
sodipodi:docname="nav_edge_left.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="5.3721385"
|
||||
inkscape:cy="14.16429"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="false"
|
||||
inkscape:bbox-nodes="false"
|
||||
inkscape:snap-bbox-edge-midpoints="false"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1357"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1022.3622)">
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 0 0 L 0 32 L 8.5332031 16 L 0 0 z "
|
||||
transform="matrix(0.93749998,0,0,0.93749998,0,1022.3622)"
|
||||
id="rect4586" />
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 0,1022.3622 v 15 15 l 8,-15 z"
|
||||
id="path4143"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="8"
|
||||
height="30"
|
||||
viewBox="0 0 8.0000001 30"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="nav_edge.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="5.3721385"
|
||||
inkscape:cy="14.16429"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="false"
|
||||
inkscape:bbox-nodes="false"
|
||||
inkscape:snap-bbox-edge-midpoints="false"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1357"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1022.3622)">
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 0,1022.3622 0,15 0,15 8,-15 -8,-15 z"
|
||||
id="path4143"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 1e-8,1022.3622 7.99999999,15 0,-15 -8,0 z m 7.99999999,15 -8,15 8,0 0,-15 z"
|
||||
id="rect4136"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="6"
|
||||
height="9"
|
||||
viewBox="0 0 1.5875 2.3812501"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.1 r"
|
||||
sodipodi:docname="splitbar_handle.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="8.7681488"
|
||||
inkscape:cy="-2.7929517"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:showpageshadow="false"
|
||||
showguides="false"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1357"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4487" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-294.61873)">
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect4485"
|
||||
width="0.26458335"
|
||||
height="0.26458332"
|
||||
x="0.26458332"
|
||||
y="294.8833" />
|
||||
<rect
|
||||
y="294.8833"
|
||||
x="1.0583333"
|
||||
height="0.26458332"
|
||||
width="0.26458335"
|
||||
id="rect4489"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<rect
|
||||
y="295.41248"
|
||||
x="0.26458329"
|
||||
height="0.26458332"
|
||||
width="0.26458335"
|
||||
id="rect4491"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect4493"
|
||||
width="0.26458335"
|
||||
height="0.26458332"
|
||||
x="1.0583333"
|
||||
y="295.41248" />
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect4495"
|
||||
width="0.26458335"
|
||||
height="0.26458332"
|
||||
x="0.26458332"
|
||||
y="295.94165" />
|
||||
<rect
|
||||
y="295.94165"
|
||||
x="1.0583333"
|
||||
height="0.26458332"
|
||||
width="0.26458335"
|
||||
id="rect4497"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<rect
|
||||
y="296.47079"
|
||||
x="0.26458329"
|
||||
height="0.26458332"
|
||||
width="0.26458335"
|
||||
id="rect4499"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect4501"
|
||||
width="0.26458335"
|
||||
height="0.26458332"
|
||||
x="1.0583333"
|
||||
y="296.47079" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 483 B |
|
After Width: | Height: | Size: 488 B |
@@ -0,0 +1,32 @@
|
||||
// Adds extra CSS classes "even" and "odd" to .memberdecls to allow
|
||||
// striped backgrounds.
|
||||
function MemberDeclsStriper () {
|
||||
var counter = 0;
|
||||
|
||||
this.stripe = function() {
|
||||
$(".memberdecls tbody").children().each(function(i) {
|
||||
|
||||
// reset counter at every heading -> always start with even
|
||||
if ($(this).is(".heading")) {
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
// add extra classes
|
||||
if (counter % 2 == 1) {
|
||||
$(this).addClass("odd");
|
||||
}
|
||||
else {
|
||||
$(this).addClass("even");
|
||||
}
|
||||
|
||||
// advance counter at every separator
|
||||
// this is the only way to reliably detect which table rows belong together
|
||||
if ($(this).is('[class^="separator"]')) {
|
||||
counter++;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// execute the function
|
||||
$(document).ready(new MemberDeclsStriper().stripe);
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
@page libssh_tutor_threads Chapter 8: Threads with libssh
|
||||
@section threads_with_libssh How to use libssh with threads
|
||||
|
||||
libssh may be used in multithreaded applications, but under several conditions :
|
||||
- Your system must support libpthread or, in Windows environment,
|
||||
CriticalSection based mutex control.
|
||||
- Since version 0.8.0, threads initialization is called automatically in the
|
||||
library constructor if libssh is dynamically linked. This means it is no
|
||||
longer necessary to call ssh_init()/ssh_finalize().
|
||||
- If libssh is statically linked, threading must be initialized by calling
|
||||
ssh_init() before using any of libssh provided functions. This initialization
|
||||
must be done outside of any threading context. Don't forget to call
|
||||
ssh_finalize() to avoid memory leak
|
||||
- At all times, you may use different sessions inside threads, make parallel
|
||||
connections, read/write on different sessions and so on. You *cannot* use a
|
||||
single session (or channels for a single session) in several threads at the same
|
||||
time. This will most likely lead to internal state corruption. This limitation is
|
||||
being worked out and will maybe disappear later.
|
||||
|
||||
@subsection threads_init Initialization of threads
|
||||
|
||||
Since version 0.8.0, it is no longer necessary to call ssh_init()/ssh_finalize()
|
||||
if libssh is dynamically linked.
|
||||
|
||||
If libssh is statically linked, call ssh_init() before using any of libssh
|
||||
provided functions.
|
||||
|
||||
@subsection threads_pthread Using libpthread with libssh
|
||||
|
||||
Since version 0.8.0, libpthread is the default threads library used by libssh.
|
||||
|
||||
To use libpthread, simply link it to you application.
|
||||
|
||||
If you are using libssh statically linked, don't forget to call ssh_init()
|
||||
before using any of libssh provided functions (and ssh_finalize() in the end).
|
||||
|
||||
@subsection threads_other Using another threading library
|
||||
|
||||
Since version 0.8.0, libssh does not support custom threading libraries.
|
||||
The change makes sense since the newer versions for libcrypto (OpenSSL) and
|
||||
libgcrypt don't support custom threading libraries.
|
||||
|
||||
The default used threading library is libpthread.
|
||||
Alternatively, in Windows environment, CriticalSection based mutex control can
|
||||
be used.
|
||||
|
||||
If your system does not support libpthread nor CriticalSection based mutex
|
||||
control, unfortunately, you cannot use libssh in multithreaded scenarios.
|
||||
|
||||
Good luck !
|
||||
*/
|
||||