in libs/curl/lib/vssh/libssh2.c [968:3049]
static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block)
{
CURLcode result = CURLE_OK;
struct connectdata *conn = data->conn;
struct SSHPROTO *sshp = data->req.p.ssh;
struct ssh_conn *sshc = &conn->proto.sshc;
curl_socket_t sock = conn->sock[FIRSTSOCKET];
int rc = LIBSSH2_ERROR_NONE;
int ssherr;
unsigned long sftperr;
int seekerr = CURL_SEEKFUNC_OK;
size_t readdir_len;
*block = 0; /* we are not blocking by default */
do {
switch(sshc->state) {
case SSH_INIT:
sshc->secondCreateDirs = 0;
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_OK;
/* Set libssh2 to non-blocking, since everything internally is
non-blocking */
libssh2_session_set_blocking(sshc->ssh_session, 0);
result = ssh_force_knownhost_key_type(data);
if(result) {
state(data, SSH_SESSION_FREE);
sshc->actualcode = result;
break;
}
state(data, SSH_S_STARTUP);
FALLTHROUGH();
case SSH_S_STARTUP:
rc = session_startup(sshc->ssh_session, sock);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session, &err_msg, NULL, 0);
failf(data, "Failure establishing ssh session: %d, %s", rc, err_msg);
state(data, SSH_SESSION_FREE);
sshc->actualcode = CURLE_FAILED_INIT;
break;
}
state(data, SSH_HOSTKEY);
FALLTHROUGH();
case SSH_HOSTKEY:
/*
* Before we authenticate we should check the hostkey's fingerprint
* against our known hosts. How that is handled (reading from file,
* whatever) is up to us.
*/
result = ssh_check_fingerprint(data);
if(!result)
state(data, SSH_AUTHLIST);
/* ssh_check_fingerprint sets state appropriately on error */
break;
case SSH_AUTHLIST:
/*
* Figure out authentication methods
* NB: As soon as we have provided a username to an openssh server we
* must never change it later. Thus, always specify the correct username
* here, even though the libssh2 docs kind of indicate that it should be
* possible to get a 'generic' list (not user-specific) of authentication
* methods, presumably with a blank username. That will not work in my
* experience.
* So always specify it here.
*/
sshc->authlist = libssh2_userauth_list(sshc->ssh_session,
conn->user,
curlx_uztoui(strlen(conn->user)));
if(!sshc->authlist) {
if(libssh2_userauth_authenticated(sshc->ssh_session)) {
sshc->authed = TRUE;
infof(data, "SSH user accepted with no authentication");
state(data, SSH_AUTH_DONE);
break;
}
ssherr = libssh2_session_last_errno(sshc->ssh_session);
if(ssherr == LIBSSH2_ERROR_EAGAIN)
rc = LIBSSH2_ERROR_EAGAIN;
else {
state(data, SSH_SESSION_FREE);
sshc->actualcode = libssh2_session_error_to_CURLE(ssherr);
}
break;
}
infof(data, "SSH authentication methods available: %s",
sshc->authlist);
state(data, SSH_AUTH_PKEY_INIT);
break;
case SSH_AUTH_PKEY_INIT:
/*
* Check the supported auth types in the order I feel is most secure
* with the requested type of authentication
*/
sshc->authed = FALSE;
if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) &&
(strstr(sshc->authlist, "publickey") != NULL)) {
bool out_of_memory = FALSE;
sshc->rsa_pub = sshc->rsa = NULL;
if(data->set.str[STRING_SSH_PRIVATE_KEY])
sshc->rsa = strdup(data->set.str[STRING_SSH_PRIVATE_KEY]);
else {
/* To ponder about: should really the lib be messing about with the
HOME environment variable etc? */
char *home = curl_getenv("HOME");
struct_stat sbuf;
/* If no private key file is specified, try some common paths. */
if(home) {
/* Try ~/.ssh first. */
sshc->rsa = aprintf("%s/.ssh/id_rsa", home);
if(!sshc->rsa)
out_of_memory = TRUE;
else if(stat(sshc->rsa, &sbuf)) {
Curl_safefree(sshc->rsa);
sshc->rsa = aprintf("%s/.ssh/id_dsa", home);
if(!sshc->rsa)
out_of_memory = TRUE;
else if(stat(sshc->rsa, &sbuf)) {
Curl_safefree(sshc->rsa);
}
}
free(home);
}
if(!out_of_memory && !sshc->rsa) {
/* Nothing found; try the current dir. */
sshc->rsa = strdup("id_rsa");
if(sshc->rsa && stat(sshc->rsa, &sbuf)) {
Curl_safefree(sshc->rsa);
sshc->rsa = strdup("id_dsa");
if(sshc->rsa && stat(sshc->rsa, &sbuf)) {
Curl_safefree(sshc->rsa);
/* Out of guesses. Set to the empty string to avoid
* surprising info messages. */
sshc->rsa = strdup("");
}
}
}
}
/*
* Unless the user explicitly specifies a public key file, let
* libssh2 extract the public key from the private key file.
* This is done by simply passing sshc->rsa_pub = NULL.
*/
if(data->set.str[STRING_SSH_PUBLIC_KEY]
/* treat empty string the same way as NULL */
&& data->set.str[STRING_SSH_PUBLIC_KEY][0]) {
sshc->rsa_pub = strdup(data->set.str[STRING_SSH_PUBLIC_KEY]);
if(!sshc->rsa_pub)
out_of_memory = TRUE;
}
if(out_of_memory || !sshc->rsa) {
Curl_safefree(sshc->rsa);
Curl_safefree(sshc->rsa_pub);
state(data, SSH_SESSION_FREE);
sshc->actualcode = CURLE_OUT_OF_MEMORY;
break;
}
sshc->passphrase = data->set.ssl.key_passwd;
if(!sshc->passphrase)
sshc->passphrase = "";
if(sshc->rsa_pub)
infof(data, "Using SSH public key file '%s'", sshc->rsa_pub);
infof(data, "Using SSH private key file '%s'", sshc->rsa);
state(data, SSH_AUTH_PKEY);
}
else {
state(data, SSH_AUTH_PASS_INIT);
}
break;
case SSH_AUTH_PKEY:
/* The function below checks if the files exists, no need to stat() here.
*/
rc = libssh2_userauth_publickey_fromfile_ex(sshc->ssh_session,
conn->user,
curlx_uztoui(
strlen(conn->user)),
sshc->rsa_pub,
sshc->rsa, sshc->passphrase);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
Curl_safefree(sshc->rsa_pub);
Curl_safefree(sshc->rsa);
if(rc == 0) {
sshc->authed = TRUE;
infof(data, "Initialized SSH public key authentication");
state(data, SSH_AUTH_DONE);
}
else {
char *err_msg = NULL;
char unknown[] = "Reason unknown (-1)";
if(rc == -1) {
/* No error message has been set and the last set error message, if
any, is from a previous error so ignore it. #11837 */
err_msg = unknown;
}
else {
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
}
infof(data, "SSH public key authentication failed: %s", err_msg);
state(data, SSH_AUTH_PASS_INIT);
rc = 0; /* clear rc and continue */
}
break;
case SSH_AUTH_PASS_INIT:
if((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) &&
(strstr(sshc->authlist, "password") != NULL)) {
state(data, SSH_AUTH_PASS);
}
else {
state(data, SSH_AUTH_HOST_INIT);
rc = 0; /* clear rc and continue */
}
break;
case SSH_AUTH_PASS:
rc = libssh2_userauth_password_ex(sshc->ssh_session, conn->user,
curlx_uztoui(strlen(conn->user)),
conn->passwd,
curlx_uztoui(strlen(conn->passwd)),
NULL);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc == 0) {
sshc->authed = TRUE;
infof(data, "Initialized password authentication");
state(data, SSH_AUTH_DONE);
}
else {
state(data, SSH_AUTH_HOST_INIT);
rc = 0; /* clear rc and continue */
}
break;
case SSH_AUTH_HOST_INIT:
if((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) &&
(strstr(sshc->authlist, "hostbased") != NULL)) {
state(data, SSH_AUTH_HOST);
}
else {
state(data, SSH_AUTH_AGENT_INIT);
}
break;
case SSH_AUTH_HOST:
state(data, SSH_AUTH_AGENT_INIT);
break;
case SSH_AUTH_AGENT_INIT:
#ifdef HAVE_LIBSSH2_AGENT_API
if((data->set.ssh_auth_types & CURLSSH_AUTH_AGENT)
&& (strstr(sshc->authlist, "publickey") != NULL)) {
/* Connect to the ssh-agent */
/* The agent could be shared by a curl thread i believe
but nothing obvious as keys can be added/removed at any time */
if(!sshc->ssh_agent) {
sshc->ssh_agent = libssh2_agent_init(sshc->ssh_session);
if(!sshc->ssh_agent) {
infof(data, "Could not create agent object");
state(data, SSH_AUTH_KEY_INIT);
break;
}
}
rc = libssh2_agent_connect(sshc->ssh_agent);
if(rc == LIBSSH2_ERROR_EAGAIN)
break;
if(rc < 0) {
infof(data, "Failure connecting to agent");
state(data, SSH_AUTH_KEY_INIT);
rc = 0; /* clear rc and continue */
}
else {
state(data, SSH_AUTH_AGENT_LIST);
}
}
else
#endif /* HAVE_LIBSSH2_AGENT_API */
state(data, SSH_AUTH_KEY_INIT);
break;
case SSH_AUTH_AGENT_LIST:
#ifdef HAVE_LIBSSH2_AGENT_API
rc = libssh2_agent_list_identities(sshc->ssh_agent);
if(rc == LIBSSH2_ERROR_EAGAIN)
break;
if(rc < 0) {
infof(data, "Failure requesting identities to agent");
state(data, SSH_AUTH_KEY_INIT);
rc = 0; /* clear rc and continue */
}
else {
state(data, SSH_AUTH_AGENT);
sshc->sshagent_prev_identity = NULL;
}
#endif
break;
case SSH_AUTH_AGENT:
#ifdef HAVE_LIBSSH2_AGENT_API
/* as prev_identity evolves only after an identity user auth finished we
can safely request it again as long as EAGAIN is returned here or by
libssh2_agent_userauth */
rc = libssh2_agent_get_identity(sshc->ssh_agent,
&sshc->sshagent_identity,
sshc->sshagent_prev_identity);
if(rc == LIBSSH2_ERROR_EAGAIN)
break;
if(rc == 0) {
rc = libssh2_agent_userauth(sshc->ssh_agent, conn->user,
sshc->sshagent_identity);
if(rc < 0) {
if(rc != LIBSSH2_ERROR_EAGAIN) {
/* tried and failed? go to next identity */
sshc->sshagent_prev_identity = sshc->sshagent_identity;
}
break;
}
}
if(rc < 0)
infof(data, "Failure requesting identities to agent");
else if(rc == 1)
infof(data, "No identity would match");
if(rc == LIBSSH2_ERROR_NONE) {
sshc->authed = TRUE;
infof(data, "Agent based authentication successful");
state(data, SSH_AUTH_DONE);
}
else {
state(data, SSH_AUTH_KEY_INIT);
rc = 0; /* clear rc and continue */
}
#endif
break;
case SSH_AUTH_KEY_INIT:
if((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD)
&& (strstr(sshc->authlist, "keyboard-interactive") != NULL)) {
state(data, SSH_AUTH_KEY);
}
else {
state(data, SSH_AUTH_DONE);
}
break;
case SSH_AUTH_KEY:
/* Authentication failed. Continue with keyboard-interactive now. */
rc = libssh2_userauth_keyboard_interactive_ex(sshc->ssh_session,
conn->user,
curlx_uztoui(
strlen(conn->user)),
&kbd_callback);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc == 0) {
sshc->authed = TRUE;
infof(data, "Initialized keyboard interactive authentication");
}
state(data, SSH_AUTH_DONE);
break;
case SSH_AUTH_DONE:
if(!sshc->authed) {
failf(data, "Authentication failure");
state(data, SSH_SESSION_FREE);
sshc->actualcode = CURLE_LOGIN_DENIED;
break;
}
/*
* At this point we have an authenticated ssh session.
*/
infof(data, "Authentication complete");
Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSH is connected */
conn->sockfd = sock;
conn->writesockfd = CURL_SOCKET_BAD;
if(conn->handler->protocol == CURLPROTO_SFTP) {
state(data, SSH_SFTP_INIT);
break;
}
infof(data, "SSH CONNECT phase done");
state(data, SSH_STOP);
break;
case SSH_SFTP_INIT:
/*
* Start the libssh2 sftp session
*/
sshc->sftp_session = libssh2_sftp_init(sshc->ssh_session);
if(!sshc->sftp_session) {
char *err_msg = NULL;
if(libssh2_session_last_errno(sshc->ssh_session) ==
LIBSSH2_ERROR_EAGAIN) {
rc = LIBSSH2_ERROR_EAGAIN;
break;
}
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
failf(data, "Failure initializing sftp session: %s", err_msg);
state(data, SSH_SESSION_FREE);
sshc->actualcode = CURLE_FAILED_INIT;
break;
}
state(data, SSH_SFTP_REALPATH);
break;
case SSH_SFTP_REALPATH:
{
char tempHome[PATH_MAX];
/*
* Get the "home" directory
*/
rc = sftp_libssh2_realpath(sshc->sftp_session, ".",
tempHome, PATH_MAX-1);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc > 0) {
/* It seems that this string is not always NULL terminated */
tempHome[rc] = '\0';
sshc->homedir = strdup(tempHome);
if(!sshc->homedir) {
state(data, SSH_SFTP_CLOSE);
sshc->actualcode = CURLE_OUT_OF_MEMORY;
break;
}
data->state.most_recent_ftp_entrypath = sshc->homedir;
}
else {
/* Return the error type */
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
if(sftperr)
result = sftp_libssh2_error_to_CURLE(sftperr);
else
/* in this case, the error was not in the SFTP level but for example
a time-out or similar */
result = CURLE_SSH;
sshc->actualcode = result;
DEBUGF(infof(data, "error = %lu makes libcurl = %d",
sftperr, (int)result));
state(data, SSH_STOP);
break;
}
}
/* This is the last step in the SFTP connect phase. Do note that while
we get the homedir here, we get the "workingpath" in the DO action
since the homedir will remain the same between request but the
working path will not. */
DEBUGF(infof(data, "SSH CONNECT phase done"));
state(data, SSH_STOP);
break;
case SSH_SFTP_QUOTE_INIT:
result = Curl_getworkingpath(data, sshc->homedir, &sshp->path);
if(result) {
sshc->actualcode = result;
state(data, SSH_STOP);
break;
}
if(data->set.quote) {
infof(data, "Sending quote commands");
sshc->quote_item = data->set.quote;
state(data, SSH_SFTP_QUOTE);
}
else {
state(data, SSH_SFTP_GETINFO);
}
break;
case SSH_SFTP_POSTQUOTE_INIT:
if(data->set.postquote) {
infof(data, "Sending quote commands");
sshc->quote_item = data->set.postquote;
state(data, SSH_SFTP_QUOTE);
}
else {
state(data, SSH_STOP);
}
break;
case SSH_SFTP_QUOTE:
/* Send any quote commands */
{
const char *cp;
/*
* Support some of the "FTP" commands
*
* 'sshc->quote_item' is already verified to be non-NULL before it
* switched to this state.
*/
char *cmd = sshc->quote_item->data;
sshc->acceptfail = FALSE;
/* if a command starts with an asterisk, which a legal SFTP command never
can, the command will be allowed to fail without it causing any
aborts or cancels etc. It will cause libcurl to act as if the command
is successful, whatever the server responds. */
if(cmd[0] == '*') {
cmd++;
sshc->acceptfail = TRUE;
}
if(strcasecompare("pwd", cmd)) {
/* output debug output if that is requested */
char *tmp = aprintf("257 \"%s\" is current directory.\n",
sshp->path);
if(!tmp) {
result = CURLE_OUT_OF_MEMORY;
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
break;
}
Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"PWD\n", 4);
Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp));
/* this sends an FTP-like "header" to the header callback so that the
current directory can be read very similar to how it is read when
using ordinary FTP. */
result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp));
free(tmp);
if(result) {
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = result;
}
else
state(data, SSH_SFTP_NEXT_QUOTE);
break;
}
/*
* the arguments following the command must be separated from the
* command with a space so we can check for it unconditionally
*/
cp = strchr(cmd, ' ');
if(!cp) {
failf(data, "Syntax error command '%s', missing parameter",
cmd);
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
/*
* also, every command takes at least one argument so we get that
* first argument right now
*/
result = Curl_get_pathname(&cp, &sshc->quote_path1, sshc->homedir);
if(result) {
if(result == CURLE_OUT_OF_MEMORY)
failf(data, "Out of memory");
else
failf(data, "Syntax error: Bad first parameter to '%s'", cmd);
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = result;
break;
}
/*
* SFTP is a binary protocol, so we do not send text commands
* to the server. Instead, we scan for commands used by
* OpenSSH's sftp program and call the appropriate libssh2
* functions.
*/
if(strncasecompare(cmd, "chgrp ", 6) ||
strncasecompare(cmd, "chmod ", 6) ||
strncasecompare(cmd, "chown ", 6) ||
strncasecompare(cmd, "atime ", 6) ||
strncasecompare(cmd, "mtime ", 6)) {
/* attribute change */
/* sshc->quote_path1 contains the mode to set */
/* get the destination */
result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir);
if(result) {
if(result == CURLE_OUT_OF_MEMORY)
failf(data, "Out of memory");
else
failf(data, "Syntax error in %s: Bad second parameter", cmd);
Curl_safefree(sshc->quote_path1);
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = result;
break;
}
memset(&sshp->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES));
state(data, SSH_SFTP_QUOTE_STAT);
break;
}
if(strncasecompare(cmd, "ln ", 3) ||
strncasecompare(cmd, "symlink ", 8)) {
/* symbolic linking */
/* sshc->quote_path1 is the source */
/* get the destination */
result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir);
if(result) {
if(result == CURLE_OUT_OF_MEMORY)
failf(data, "Out of memory");
else
failf(data,
"Syntax error in ln/symlink: Bad second parameter");
Curl_safefree(sshc->quote_path1);
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = result;
break;
}
state(data, SSH_SFTP_QUOTE_SYMLINK);
break;
}
else if(strncasecompare(cmd, "mkdir ", 6)) {
/* create dir */
state(data, SSH_SFTP_QUOTE_MKDIR);
break;
}
else if(strncasecompare(cmd, "rename ", 7)) {
/* rename file */
/* first param is the source path */
/* second param is the dest. path */
result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir);
if(result) {
if(result == CURLE_OUT_OF_MEMORY)
failf(data, "Out of memory");
else
failf(data, "Syntax error in rename: Bad second parameter");
Curl_safefree(sshc->quote_path1);
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = result;
break;
}
state(data, SSH_SFTP_QUOTE_RENAME);
break;
}
else if(strncasecompare(cmd, "rmdir ", 6)) {
/* delete dir */
state(data, SSH_SFTP_QUOTE_RMDIR);
break;
}
else if(strncasecompare(cmd, "rm ", 3)) {
state(data, SSH_SFTP_QUOTE_UNLINK);
break;
}
#ifdef HAS_STATVFS_SUPPORT
else if(strncasecompare(cmd, "statvfs ", 8)) {
state(data, SSH_SFTP_QUOTE_STATVFS);
break;
}
#endif
failf(data, "Unknown SFTP command");
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
case SSH_SFTP_NEXT_QUOTE:
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
sshc->quote_item = sshc->quote_item->next;
if(sshc->quote_item) {
state(data, SSH_SFTP_QUOTE);
}
else {
if(sshc->nextstate != SSH_NO_STATE) {
state(data, sshc->nextstate);
sshc->nextstate = SSH_NO_STATE;
}
else {
state(data, SSH_SFTP_GETINFO);
}
}
break;
case SSH_SFTP_QUOTE_STAT:
{
char *cmd = sshc->quote_item->data;
sshc->acceptfail = FALSE;
/* if a command starts with an asterisk, which a legal SFTP command never
can, the command will be allowed to fail without it causing any
aborts or cancels etc. It will cause libcurl to act as if the command
is successful, whatever the server responds. */
if(cmd[0] == '*') {
cmd++;
sshc->acceptfail = TRUE;
}
if(!strncasecompare(cmd, "chmod", 5)) {
/* Since chown and chgrp only set owner OR group but libssh2 wants to
* set them both at once, we need to obtain the current ownership
* first. This takes an extra protocol round trip.
*/
rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2,
curlx_uztoui(strlen(sshc->quote_path2)),
LIBSSH2_SFTP_STAT,
&sshp->quote_attrs);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc && !sshc->acceptfail) { /* get those attributes */
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "Attempt to get SFTP stats failed: %s",
sftp_libssh2_strerror(sftperr));
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
}
/* Now set the new attributes... */
if(strncasecompare(cmd, "chgrp", 5)) {
sshp->quote_attrs.gid = strtoul(sshc->quote_path1, NULL, 10);
sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID;
if(sshp->quote_attrs.gid == 0 && !ISDIGIT(sshc->quote_path1[0]) &&
!sshc->acceptfail) {
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "Syntax error: chgrp gid not a number");
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
}
else if(strncasecompare(cmd, "chmod", 5)) {
sshp->quote_attrs.permissions = strtoul(sshc->quote_path1, NULL, 8);
sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS;
/* permissions are octal */
if(sshp->quote_attrs.permissions == 0 &&
!ISDIGIT(sshc->quote_path1[0])) {
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "Syntax error: chmod permissions not a number");
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
}
else if(strncasecompare(cmd, "chown", 5)) {
sshp->quote_attrs.uid = strtoul(sshc->quote_path1, NULL, 10);
sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID;
if(sshp->quote_attrs.uid == 0 && !ISDIGIT(sshc->quote_path1[0]) &&
!sshc->acceptfail) {
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "Syntax error: chown uid not a number");
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
}
else if(strncasecompare(cmd, "atime", 5) ||
strncasecompare(cmd, "mtime", 5)) {
time_t date = Curl_getdate_capped(sshc->quote_path1);
bool fail = FALSE;
if(date == -1) {
failf(data, "incorrect date format for %.*s", 5, cmd);
fail = TRUE;
}
#if SIZEOF_TIME_T > SIZEOF_LONG
if(date > 0xffffffff) {
/* if 'long' cannot old >32bit, this date cannot be sent */
failf(data, "date overflow");
fail = TRUE;
}
#endif
if(fail) {
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
if(strncasecompare(cmd, "atime", 5))
sshp->quote_attrs.atime = (unsigned long)date;
else /* mtime */
sshp->quote_attrs.mtime = (unsigned long)date;
sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_ACMODTIME;
}
/* Now send the completed structure... */
state(data, SSH_SFTP_QUOTE_SETSTAT);
break;
}
case SSH_SFTP_QUOTE_SETSTAT:
rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2,
curlx_uztoui(strlen(sshc->quote_path2)),
LIBSSH2_SFTP_SETSTAT,
&sshp->quote_attrs);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc && !sshc->acceptfail) {
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "Attempt to set SFTP stats failed: %s",
sftp_libssh2_strerror(sftperr));
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
state(data, SSH_SFTP_NEXT_QUOTE);
break;
case SSH_SFTP_QUOTE_SYMLINK:
rc = libssh2_sftp_symlink_ex(sshc->sftp_session, sshc->quote_path1,
curlx_uztoui(strlen(sshc->quote_path1)),
sshc->quote_path2,
curlx_uztoui(strlen(sshc->quote_path2)),
LIBSSH2_SFTP_SYMLINK);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc && !sshc->acceptfail) {
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "symlink command failed: %s",
sftp_libssh2_strerror(sftperr));
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
state(data, SSH_SFTP_NEXT_QUOTE);
break;
case SSH_SFTP_QUOTE_MKDIR:
rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshc->quote_path1,
curlx_uztoui(strlen(sshc->quote_path1)),
(long)data->set.new_directory_perms);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc && !sshc->acceptfail) {
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
failf(data, "mkdir command failed: %s",
sftp_libssh2_strerror(sftperr));
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
state(data, SSH_SFTP_NEXT_QUOTE);
break;
case SSH_SFTP_QUOTE_RENAME:
rc = libssh2_sftp_rename_ex(sshc->sftp_session, sshc->quote_path1,
curlx_uztoui(strlen(sshc->quote_path1)),
sshc->quote_path2,
curlx_uztoui(strlen(sshc->quote_path2)),
LIBSSH2_SFTP_RENAME_OVERWRITE |
LIBSSH2_SFTP_RENAME_ATOMIC |
LIBSSH2_SFTP_RENAME_NATIVE);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc && !sshc->acceptfail) {
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "rename command failed: %s",
sftp_libssh2_strerror(sftperr));
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
state(data, SSH_SFTP_NEXT_QUOTE);
break;
case SSH_SFTP_QUOTE_RMDIR:
rc = libssh2_sftp_rmdir_ex(sshc->sftp_session, sshc->quote_path1,
curlx_uztoui(strlen(sshc->quote_path1)));
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc && !sshc->acceptfail) {
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
failf(data, "rmdir command failed: %s",
sftp_libssh2_strerror(sftperr));
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
state(data, SSH_SFTP_NEXT_QUOTE);
break;
case SSH_SFTP_QUOTE_UNLINK:
rc = libssh2_sftp_unlink_ex(sshc->sftp_session, sshc->quote_path1,
curlx_uztoui(strlen(sshc->quote_path1)));
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc && !sshc->acceptfail) {
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
failf(data, "rm command failed: %s", sftp_libssh2_strerror(sftperr));
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
state(data, SSH_SFTP_NEXT_QUOTE);
break;
#ifdef HAS_STATVFS_SUPPORT
case SSH_SFTP_QUOTE_STATVFS:
{
LIBSSH2_SFTP_STATVFS statvfs;
rc = libssh2_sftp_statvfs(sshc->sftp_session, sshc->quote_path1,
curlx_uztoui(strlen(sshc->quote_path1)),
&statvfs);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc && !sshc->acceptfail) {
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
failf(data, "statvfs command failed: %s",
sftp_libssh2_strerror(sftperr));
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
else if(rc == 0) {
#ifdef _MSC_VER
#define CURL_LIBSSH2_VFS_SIZE_MASK "I64u"
#else
#define CURL_LIBSSH2_VFS_SIZE_MASK "llu"
#endif
char *tmp = aprintf("statvfs:\n"
"f_bsize: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_frsize: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_blocks: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_bfree: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_bavail: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_files: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_ffree: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_favail: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_fsid: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_flag: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_namemax: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n",
statvfs.f_bsize, statvfs.f_frsize,
statvfs.f_blocks, statvfs.f_bfree,
statvfs.f_bavail, statvfs.f_files,
statvfs.f_ffree, statvfs.f_favail,
statvfs.f_fsid, statvfs.f_flag,
statvfs.f_namemax);
if(!tmp) {
result = CURLE_OUT_OF_MEMORY;
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
break;
}
result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp));
free(tmp);
if(result) {
state(data, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = result;
}
}
state(data, SSH_SFTP_NEXT_QUOTE);
break;
}
#endif
case SSH_SFTP_GETINFO:
{
if(data->set.get_filetime) {
state(data, SSH_SFTP_FILETIME);
}
else {
state(data, SSH_SFTP_TRANS_INIT);
}
break;
}
case SSH_SFTP_FILETIME:
{
LIBSSH2_SFTP_ATTRIBUTES attrs;
rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path,
curlx_uztoui(strlen(sshp->path)),
LIBSSH2_SFTP_STAT, &attrs);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc == 0) {
data->info.filetime = (time_t)attrs.mtime;
}
state(data, SSH_SFTP_TRANS_INIT);
break;
}
case SSH_SFTP_TRANS_INIT:
if(data->state.upload)
state(data, SSH_SFTP_UPLOAD_INIT);
else {
if(sshp->path[strlen(sshp->path)-1] == '/')
state(data, SSH_SFTP_READDIR_INIT);
else
state(data, SSH_SFTP_DOWNLOAD_INIT);
}
break;
case SSH_SFTP_UPLOAD_INIT:
{
unsigned long flags;
/*
* NOTE!!! libssh2 requires that the destination path is a full path
* that includes the destination file and name OR ends in a "/"
* If this is not done the destination file will be named the
* same name as the last directory in the path.
*/
if(data->state.resume_from) {
LIBSSH2_SFTP_ATTRIBUTES attrs;
if(data->state.resume_from < 0) {
rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path,
curlx_uztoui(strlen(sshp->path)),
LIBSSH2_SFTP_STAT, &attrs);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc) {
data->state.resume_from = 0;
}
else {
curl_off_t size = attrs.filesize;
if(size < 0) {
failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size);
return CURLE_BAD_DOWNLOAD_RESUME;
}
data->state.resume_from = attrs.filesize;
}
}
}
if(data->set.remote_append)
/* Try to open for append, but create if nonexisting */
flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_APPEND;
else if(data->state.resume_from > 0)
/* If we have restart position then open for append */
flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_APPEND;
else
/* Clear file before writing (normal behavior) */
flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC;
sshc->sftp_handle =
libssh2_sftp_open_ex(sshc->sftp_session, sshp->path,
curlx_uztoui(strlen(sshp->path)),
flags, (long)data->set.new_file_perms,
LIBSSH2_SFTP_OPENFILE);
if(!sshc->sftp_handle) {
rc = libssh2_session_last_errno(sshc->ssh_session);
if(LIBSSH2_ERROR_EAGAIN == rc)
break;
if(LIBSSH2_ERROR_SFTP_PROTOCOL == rc)
/* only when there was an SFTP protocol error can we extract
the sftp error! */
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
else
sftperr = LIBSSH2_FX_OK; /* not an sftp error at all */
if(sshc->secondCreateDirs) {
state(data, SSH_SFTP_CLOSE);
sshc->actualcode = sftperr != LIBSSH2_FX_OK ?
sftp_libssh2_error_to_CURLE(sftperr):CURLE_SSH;
failf(data, "Creating the dir/file failed: %s",
sftp_libssh2_strerror(sftperr));
break;
}
if(((sftperr == LIBSSH2_FX_NO_SUCH_FILE) ||
(sftperr == LIBSSH2_FX_FAILURE) ||
(sftperr == LIBSSH2_FX_NO_SUCH_PATH)) &&
(data->set.ftp_create_missing_dirs &&
(strlen(sshp->path) > 1))) {
/* try to create the path remotely */
rc = 0; /* clear rc and continue */
sshc->secondCreateDirs = 1;
state(data, SSH_SFTP_CREATE_DIRS_INIT);
break;
}
state(data, SSH_SFTP_CLOSE);
sshc->actualcode = sftperr != LIBSSH2_FX_OK ?
sftp_libssh2_error_to_CURLE(sftperr):CURLE_SSH;
if(!sshc->actualcode) {
/* Sometimes, for some reason libssh2_sftp_last_error() returns zero
even though libssh2_sftp_open() failed previously! We need to
work around that! */
sshc->actualcode = CURLE_SSH;
sftperr = LIBSSH2_FX_OK;
}
failf(data, "Upload failed: %s (%lu/%d)",
sftperr != LIBSSH2_FX_OK ?
sftp_libssh2_strerror(sftperr):"ssh error",
sftperr, rc);
break;
}
/* If we have a restart point then we need to seek to the correct
position. */
if(data->state.resume_from > 0) {
/* Let's read off the proper amount of bytes from the input. */
if(data->set.seek_func) {
Curl_set_in_callback(data, true);
seekerr = data->set.seek_func(data->set.seek_client,
data->state.resume_from, SEEK_SET);
Curl_set_in_callback(data, false);
}
if(seekerr != CURL_SEEKFUNC_OK) {
curl_off_t passed = 0;
if(seekerr != CURL_SEEKFUNC_CANTSEEK) {
failf(data, "Could not seek stream");
return CURLE_FTP_COULDNT_USE_REST;
}
/* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */
do {
char scratch[4*1024];
size_t readthisamountnow =
(data->state.resume_from - passed >
(curl_off_t)sizeof(scratch)) ?
sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed);
size_t actuallyread;
Curl_set_in_callback(data, true);
actuallyread = data->state.fread_func(scratch, 1,
readthisamountnow,
data->state.in);
Curl_set_in_callback(data, false);
passed += actuallyread;
if((actuallyread == 0) || (actuallyread > readthisamountnow)) {
/* this checks for greater-than only to make sure that the
CURL_READFUNC_ABORT return code still aborts */
failf(data, "Failed to read data");
return CURLE_FTP_COULDNT_USE_REST;
}
} while(passed < data->state.resume_from);
}
/* now, decrease the size of the read */
if(data->state.infilesize > 0) {
data->state.infilesize -= data->state.resume_from;
data->req.size = data->state.infilesize;
Curl_pgrsSetUploadSize(data, data->state.infilesize);
}
SFTP_SEEK(sshc->sftp_handle, data->state.resume_from);
}
if(data->state.infilesize > 0) {
data->req.size = data->state.infilesize;
Curl_pgrsSetUploadSize(data, data->state.infilesize);
}
/* upload data */
Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE);
/* not set by Curl_xfer_setup to preserve keepon bits */
conn->sockfd = conn->writesockfd;
if(result) {
state(data, SSH_SFTP_CLOSE);
sshc->actualcode = result;
}
else {
/* store this original bitmask setup to use later on if we cannot
figure out a "real" bitmask */
sshc->orig_waitfor = data->req.keepon;
/* we want to use the _sending_ function even when the socket turns
out readable as the underlying libssh2 sftp send function will deal
with both accordingly */
data->state.select_bits = CURL_CSELECT_OUT;
/* since we do not really wait for anything at this point, we want the
state machine to move on as soon as possible so we set a very short
timeout here */
Curl_expire(data, 0, EXPIRE_RUN_NOW);
state(data, SSH_STOP);
}
break;
}
case SSH_SFTP_CREATE_DIRS_INIT:
if(strlen(sshp->path) > 1) {
sshc->slash_pos = sshp->path + 1; /* ignore the leading '/' */
state(data, SSH_SFTP_CREATE_DIRS);
}
else {
state(data, SSH_SFTP_UPLOAD_INIT);
}
break;
case SSH_SFTP_CREATE_DIRS:
sshc->slash_pos = strchr(sshc->slash_pos, '/');
if(sshc->slash_pos) {
*sshc->slash_pos = 0;
infof(data, "Creating directory '%s'", sshp->path);
state(data, SSH_SFTP_CREATE_DIRS_MKDIR);
break;
}
state(data, SSH_SFTP_UPLOAD_INIT);
break;
case SSH_SFTP_CREATE_DIRS_MKDIR:
/* 'mode' - parameter is preliminary - default to 0644 */
rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshp->path,
curlx_uztoui(strlen(sshp->path)),
(long)data->set.new_directory_perms);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
*sshc->slash_pos = '/';
++sshc->slash_pos;
if(rc < 0) {
/*
* Abort if failure was not that the dir already exists or the
* permission was denied (creation might succeed further down the
* path) - retry on unspecific FAILURE also
*/
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
if((sftperr != LIBSSH2_FX_FILE_ALREADY_EXISTS) &&
(sftperr != LIBSSH2_FX_FAILURE) &&
(sftperr != LIBSSH2_FX_PERMISSION_DENIED)) {
result = sftp_libssh2_error_to_CURLE(sftperr);
state(data, SSH_SFTP_CLOSE);
sshc->actualcode = result?result:CURLE_SSH;
break;
}
rc = 0; /* clear rc and continue */
}
state(data, SSH_SFTP_CREATE_DIRS);
break;
case SSH_SFTP_READDIR_INIT:
Curl_pgrsSetDownloadSize(data, -1);
if(data->req.no_body) {
state(data, SSH_STOP);
break;
}
/*
* This is a directory that we are trying to get, so produce a directory
* listing
*/
sshc->sftp_handle = libssh2_sftp_open_ex(sshc->sftp_session,
sshp->path,
curlx_uztoui(
strlen(sshp->path)),
0, 0, LIBSSH2_SFTP_OPENDIR);
if(!sshc->sftp_handle) {
if(libssh2_session_last_errno(sshc->ssh_session) ==
LIBSSH2_ERROR_EAGAIN) {
rc = LIBSSH2_ERROR_EAGAIN;
break;
}
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
failf(data, "Could not open directory for reading: %s",
sftp_libssh2_strerror(sftperr));
state(data, SSH_SFTP_CLOSE);
result = sftp_libssh2_error_to_CURLE(sftperr);
sshc->actualcode = result?result:CURLE_SSH;
break;
}
sshp->readdir_filename = malloc(PATH_MAX + 1);
if(!sshp->readdir_filename) {
state(data, SSH_SFTP_CLOSE);
sshc->actualcode = CURLE_OUT_OF_MEMORY;
break;
}
sshp->readdir_longentry = malloc(PATH_MAX + 1);
if(!sshp->readdir_longentry) {
Curl_safefree(sshp->readdir_filename);
state(data, SSH_SFTP_CLOSE);
sshc->actualcode = CURLE_OUT_OF_MEMORY;
break;
}
Curl_dyn_init(&sshp->readdir, PATH_MAX * 2);
state(data, SSH_SFTP_READDIR);
break;
case SSH_SFTP_READDIR:
rc = libssh2_sftp_readdir_ex(sshc->sftp_handle,
sshp->readdir_filename,
PATH_MAX,
sshp->readdir_longentry,
PATH_MAX,
&sshp->readdir_attrs);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc > 0) {
readdir_len = (size_t) rc;
sshp->readdir_filename[readdir_len] = '\0';
if(data->set.list_only) {
result = Curl_client_write(data, CLIENTWRITE_BODY,
sshp->readdir_filename,
readdir_len);
if(!result)
result = Curl_client_write(data, CLIENTWRITE_BODY,
(char *)"\n", 1);
if(result) {
state(data, SSH_STOP);
break;
}
}
else {
result = Curl_dyn_add(&sshp->readdir, sshp->readdir_longentry);
if(!result) {
if((sshp->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) &&
((sshp->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
LIBSSH2_SFTP_S_IFLNK)) {
Curl_dyn_init(&sshp->readdir_link, PATH_MAX);
result = Curl_dyn_addf(&sshp->readdir_link, "%s%s", sshp->path,
sshp->readdir_filename);
state(data, SSH_SFTP_READDIR_LINK);
if(!result)
break;
}
else {
state(data, SSH_SFTP_READDIR_BOTTOM);
break;
}
}
sshc->actualcode = result;
state(data, SSH_SFTP_CLOSE);
break;
}
}
else if(rc == 0) {
Curl_safefree(sshp->readdir_filename);
Curl_safefree(sshp->readdir_longentry);
state(data, SSH_SFTP_READDIR_DONE);
break;
}
else if(rc < 0) {
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
result = sftp_libssh2_error_to_CURLE(sftperr);
sshc->actualcode = result?result:CURLE_SSH;
failf(data, "Could not open remote file for reading: %s :: %d",
sftp_libssh2_strerror(sftperr),
libssh2_session_last_errno(sshc->ssh_session));
Curl_safefree(sshp->readdir_filename);
Curl_safefree(sshp->readdir_longentry);
state(data, SSH_SFTP_CLOSE);
break;
}
break;
case SSH_SFTP_READDIR_LINK:
rc =
libssh2_sftp_symlink_ex(sshc->sftp_session,
Curl_dyn_ptr(&sshp->readdir_link),
(unsigned int)
Curl_dyn_len(&sshp->readdir_link),
sshp->readdir_filename,
PATH_MAX, LIBSSH2_SFTP_READLINK);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
Curl_dyn_free(&sshp->readdir_link);
/* append filename and extra output */
result = Curl_dyn_addf(&sshp->readdir, " -> %s", sshp->readdir_filename);
if(result) {
Curl_safefree(sshp->readdir_filename);
Curl_safefree(sshp->readdir_longentry);
state(data, SSH_SFTP_CLOSE);
sshc->actualcode = result;
break;
}
state(data, SSH_SFTP_READDIR_BOTTOM);
break;
case SSH_SFTP_READDIR_BOTTOM:
result = Curl_dyn_addn(&sshp->readdir, "\n", 1);
if(!result)
result = Curl_client_write(data, CLIENTWRITE_BODY,
Curl_dyn_ptr(&sshp->readdir),
Curl_dyn_len(&sshp->readdir));
if(result) {
Curl_dyn_free(&sshp->readdir);
state(data, SSH_STOP);
}
else {
Curl_dyn_reset(&sshp->readdir);
state(data, SSH_SFTP_READDIR);
}
break;
case SSH_SFTP_READDIR_DONE:
if(libssh2_sftp_closedir(sshc->sftp_handle) ==
LIBSSH2_ERROR_EAGAIN) {
rc = LIBSSH2_ERROR_EAGAIN;
break;
}
sshc->sftp_handle = NULL;
Curl_safefree(sshp->readdir_filename);
Curl_safefree(sshp->readdir_longentry);
/* no data to transfer */
Curl_xfer_setup_nop(data);
state(data, SSH_STOP);
break;
case SSH_SFTP_DOWNLOAD_INIT:
/*
* Work on getting the specified file
*/
sshc->sftp_handle =
libssh2_sftp_open_ex(sshc->sftp_session, sshp->path,
curlx_uztoui(strlen(sshp->path)),
LIBSSH2_FXF_READ, (long)data->set.new_file_perms,
LIBSSH2_SFTP_OPENFILE);
if(!sshc->sftp_handle) {
if(libssh2_session_last_errno(sshc->ssh_session) ==
LIBSSH2_ERROR_EAGAIN) {
rc = LIBSSH2_ERROR_EAGAIN;
break;
}
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
failf(data, "Could not open remote file for reading: %s",
sftp_libssh2_strerror(sftperr));
state(data, SSH_SFTP_CLOSE);
result = sftp_libssh2_error_to_CURLE(sftperr);
sshc->actualcode = result?result:CURLE_SSH;
break;
}
state(data, SSH_SFTP_DOWNLOAD_STAT);
break;
case SSH_SFTP_DOWNLOAD_STAT:
{
LIBSSH2_SFTP_ATTRIBUTES attrs;
rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path,
curlx_uztoui(strlen(sshp->path)),
LIBSSH2_SFTP_STAT, &attrs);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc ||
!(attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) ||
(attrs.filesize == 0)) {
/*
* libssh2_sftp_open() did not return an error, so maybe the server
* just does not support stat()
* OR the server does not return a file size with a stat()
* OR file size is 0
*/
data->req.size = -1;
data->req.maxdownload = -1;
Curl_pgrsSetDownloadSize(data, -1);
}
else {
curl_off_t size = attrs.filesize;
if(size < 0) {
failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size);
return CURLE_BAD_DOWNLOAD_RESUME;
}
if(data->state.use_range) {
curl_off_t from, to;
char *ptr;
char *ptr2;
CURLofft to_t;
CURLofft from_t;
from_t = curlx_strtoofft(data->state.range, &ptr, 10, &from);
if(from_t == CURL_OFFT_FLOW)
return CURLE_RANGE_ERROR;
while(*ptr && (ISBLANK(*ptr) || (*ptr == '-')))
ptr++;
to_t = curlx_strtoofft(ptr, &ptr2, 10, &to);
if(to_t == CURL_OFFT_FLOW)
return CURLE_RANGE_ERROR;
if((to_t == CURL_OFFT_INVAL) /* no "to" value given */
|| (to >= size)) {
to = size - 1;
}
if(from_t) {
/* from is relative to end of file */
from = size - to;
to = size - 1;
}
if(from > size) {
failf(data, "Offset (%"
CURL_FORMAT_CURL_OFF_T ") was beyond file size (%"
CURL_FORMAT_CURL_OFF_T ")", from,
(curl_off_t)attrs.filesize);
return CURLE_BAD_DOWNLOAD_RESUME;
}
if(from > to) {
from = to;
size = 0;
}
else {
if((to - from) == CURL_OFF_T_MAX)
return CURLE_RANGE_ERROR;
size = to - from + 1;
}
SFTP_SEEK(sshc->sftp_handle, from);
}
data->req.size = size;
data->req.maxdownload = size;
Curl_pgrsSetDownloadSize(data, size);
}
/* We can resume if we can seek to the resume position */
if(data->state.resume_from) {
if(data->state.resume_from < 0) {
/* We are supposed to download the last abs(from) bytes */
if((curl_off_t)attrs.filesize < -data->state.resume_from) {
failf(data, "Offset (%"
CURL_FORMAT_CURL_OFF_T ") was beyond file size (%"
CURL_FORMAT_CURL_OFF_T ")",
data->state.resume_from, (curl_off_t)attrs.filesize);
return CURLE_BAD_DOWNLOAD_RESUME;
}
/* download from where? */
data->state.resume_from += attrs.filesize;
}
else {
if((curl_off_t)attrs.filesize < data->state.resume_from) {
failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T
") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")",
data->state.resume_from, (curl_off_t)attrs.filesize);
return CURLE_BAD_DOWNLOAD_RESUME;
}
}
/* Now store the number of bytes we are expected to download */
data->req.size = attrs.filesize - data->state.resume_from;
data->req.maxdownload = attrs.filesize - data->state.resume_from;
Curl_pgrsSetDownloadSize(data,
attrs.filesize - data->state.resume_from);
SFTP_SEEK(sshc->sftp_handle, data->state.resume_from);
}
}
/* Setup the actual download */
if(data->req.size == 0) {
/* no data to transfer */
Curl_xfer_setup_nop(data);
infof(data, "File already completely downloaded");
state(data, SSH_STOP);
break;
}
Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE);
/* not set by Curl_xfer_setup to preserve keepon bits */
conn->writesockfd = conn->sockfd;
/* we want to use the _receiving_ function even when the socket turns
out writableable as the underlying libssh2 recv function will deal
with both accordingly */
data->state.select_bits = CURL_CSELECT_IN;
if(result) {
/* this should never occur; the close state should be entered
at the time the error occurs */
state(data, SSH_SFTP_CLOSE);
sshc->actualcode = result;
}
else {
state(data, SSH_STOP);
}
break;
case SSH_SFTP_CLOSE:
if(sshc->sftp_handle) {
rc = libssh2_sftp_close(sshc->sftp_handle);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc < 0) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg);
}
sshc->sftp_handle = NULL;
}
Curl_safefree(sshp->path);
DEBUGF(infof(data, "SFTP DONE done"));
/* Check if nextstate is set and move .nextstate could be POSTQUOTE_INIT
After nextstate is executed, the control should come back to
SSH_SFTP_CLOSE to pass the correct result back */
if(sshc->nextstate != SSH_NO_STATE &&
sshc->nextstate != SSH_SFTP_CLOSE) {
state(data, sshc->nextstate);
sshc->nextstate = SSH_SFTP_CLOSE;
}
else {
state(data, SSH_STOP);
result = sshc->actualcode;
}
break;
case SSH_SFTP_SHUTDOWN:
/* during times we get here due to a broken transfer and then the
sftp_handle might not have been taken down so make sure that is done
before we proceed */
if(sshc->sftp_handle) {
rc = libssh2_sftp_close(sshc->sftp_handle);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc < 0) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session, &err_msg,
NULL, 0);
infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg);
}
sshc->sftp_handle = NULL;
}
if(sshc->sftp_session) {
rc = libssh2_sftp_shutdown(sshc->sftp_session);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc < 0) {
infof(data, "Failed to stop libssh2 sftp subsystem");
}
sshc->sftp_session = NULL;
}
Curl_safefree(sshc->homedir);
data->state.most_recent_ftp_entrypath = NULL;
state(data, SSH_SESSION_DISCONNECT);
break;
case SSH_SCP_TRANS_INIT:
result = Curl_getworkingpath(data, sshc->homedir, &sshp->path);
if(result) {
sshc->actualcode = result;
state(data, SSH_STOP);
break;
}
if(data->state.upload) {
if(data->state.infilesize < 0) {
failf(data, "SCP requires a known file size for upload");
sshc->actualcode = CURLE_UPLOAD_FAILED;
state(data, SSH_SCP_CHANNEL_FREE);
break;
}
state(data, SSH_SCP_UPLOAD_INIT);
}
else {
state(data, SSH_SCP_DOWNLOAD_INIT);
}
break;
case SSH_SCP_UPLOAD_INIT:
/*
* libssh2 requires that the destination path is a full path that
* includes the destination file and name OR ends in a "/" . If this is
* not done the destination file will be named the same name as the last
* directory in the path.
*/
sshc->ssh_channel =
SCP_SEND(sshc->ssh_session, sshp->path, data->set.new_file_perms,
data->state.infilesize);
if(!sshc->ssh_channel) {
int ssh_err;
char *err_msg = NULL;
if(libssh2_session_last_errno(sshc->ssh_session) ==
LIBSSH2_ERROR_EAGAIN) {
rc = LIBSSH2_ERROR_EAGAIN;
break;
}
ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0));
failf(data, "%s", err_msg);
state(data, SSH_SCP_CHANNEL_FREE);
sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err);
/* Map generic errors to upload failed */
if(sshc->actualcode == CURLE_SSH ||
sshc->actualcode == CURLE_REMOTE_FILE_NOT_FOUND)
sshc->actualcode = CURLE_UPLOAD_FAILED;
break;
}
/* upload data */
data->req.size = data->state.infilesize;
Curl_pgrsSetUploadSize(data, data->state.infilesize);
Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE);
/* not set by Curl_xfer_setup to preserve keepon bits */
conn->sockfd = conn->writesockfd;
if(result) {
state(data, SSH_SCP_CHANNEL_FREE);
sshc->actualcode = result;
}
else {
/* store this original bitmask setup to use later on if we cannot
figure out a "real" bitmask */
sshc->orig_waitfor = data->req.keepon;
/* we want to use the _sending_ function even when the socket turns
out readable as the underlying libssh2 scp send function will deal
with both accordingly */
data->state.select_bits = CURL_CSELECT_OUT;
state(data, SSH_STOP);
}
break;
case SSH_SCP_DOWNLOAD_INIT:
{
curl_off_t bytecount;
/*
* We must check the remote file; if it is a directory no values will
* be set in sb
*/
/*
* If support for >2GB files exists, use it.
*/
/* get a fresh new channel from the ssh layer */
#if LIBSSH2_VERSION_NUM < 0x010700
struct stat sb;
memset(&sb, 0, sizeof(struct stat));
sshc->ssh_channel = libssh2_scp_recv(sshc->ssh_session,
sshp->path, &sb);
#else
libssh2_struct_stat sb;
memset(&sb, 0, sizeof(libssh2_struct_stat));
sshc->ssh_channel = libssh2_scp_recv2(sshc->ssh_session,
sshp->path, &sb);
#endif
if(!sshc->ssh_channel) {
int ssh_err;
char *err_msg = NULL;
if(libssh2_session_last_errno(sshc->ssh_session) ==
LIBSSH2_ERROR_EAGAIN) {
rc = LIBSSH2_ERROR_EAGAIN;
break;
}
ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0));
failf(data, "%s", err_msg);
state(data, SSH_SCP_CHANNEL_FREE);
sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err);
break;
}
/* download data */
bytecount = (curl_off_t)sb.st_size;
data->req.maxdownload = (curl_off_t)sb.st_size;
Curl_xfer_setup1(data, CURL_XFER_RECV, bytecount, FALSE);
/* not set by Curl_xfer_setup to preserve keepon bits */
conn->writesockfd = conn->sockfd;
/* we want to use the _receiving_ function even when the socket turns
out writableable as the underlying libssh2 recv function will deal
with both accordingly */
data->state.select_bits = CURL_CSELECT_IN;
if(result) {
state(data, SSH_SCP_CHANNEL_FREE);
sshc->actualcode = result;
}
else
state(data, SSH_STOP);
}
break;
case SSH_SCP_DONE:
if(data->state.upload)
state(data, SSH_SCP_SEND_EOF);
else
state(data, SSH_SCP_CHANNEL_FREE);
break;
case SSH_SCP_SEND_EOF:
if(sshc->ssh_channel) {
rc = libssh2_channel_send_eof(sshc->ssh_channel);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to send libssh2 channel EOF: %d %s",
rc, err_msg);
}
}
state(data, SSH_SCP_WAIT_EOF);
break;
case SSH_SCP_WAIT_EOF:
if(sshc->ssh_channel) {
rc = libssh2_channel_wait_eof(sshc->ssh_channel);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to get channel EOF: %d %s", rc, err_msg);
}
}
state(data, SSH_SCP_WAIT_CLOSE);
break;
case SSH_SCP_WAIT_CLOSE:
if(sshc->ssh_channel) {
rc = libssh2_channel_wait_closed(sshc->ssh_channel);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Channel failed to close: %d %s", rc, err_msg);
}
}
state(data, SSH_SCP_CHANNEL_FREE);
break;
case SSH_SCP_CHANNEL_FREE:
if(sshc->ssh_channel) {
rc = libssh2_channel_free(sshc->ssh_channel);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc < 0) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to free libssh2 scp subsystem: %d %s",
rc, err_msg);
}
sshc->ssh_channel = NULL;
}
DEBUGF(infof(data, "SCP DONE phase complete"));
#if 0 /* PREV */
state(data, SSH_SESSION_DISCONNECT);
#endif
state(data, SSH_STOP);
result = sshc->actualcode;
break;
case SSH_SESSION_DISCONNECT:
/* during weird times when we have been prematurely aborted, the channel
is still alive when we reach this state and we MUST kill the channel
properly first */
if(sshc->ssh_channel) {
rc = libssh2_channel_free(sshc->ssh_channel);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc < 0) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to free libssh2 scp subsystem: %d %s",
rc, err_msg);
}
sshc->ssh_channel = NULL;
}
if(sshc->ssh_session) {
rc = libssh2_session_disconnect(sshc->ssh_session, "Shutdown");
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc < 0) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to disconnect libssh2 session: %d %s",
rc, err_msg);
}
}
Curl_safefree(sshc->homedir);
data->state.most_recent_ftp_entrypath = NULL;
state(data, SSH_SESSION_FREE);
break;
case SSH_SESSION_FREE:
#ifdef HAVE_LIBSSH2_KNOWNHOST_API
if(sshc->kh) {
libssh2_knownhost_free(sshc->kh);
sshc->kh = NULL;
}
#endif
#ifdef HAVE_LIBSSH2_AGENT_API
if(sshc->ssh_agent) {
rc = libssh2_agent_disconnect(sshc->ssh_agent);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc < 0) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to disconnect from libssh2 agent: %d %s",
rc, err_msg);
}
libssh2_agent_free(sshc->ssh_agent);
sshc->ssh_agent = NULL;
/* NB: there is no need to free identities, they are part of internal
agent stuff */
sshc->sshagent_identity = NULL;
sshc->sshagent_prev_identity = NULL;
}
#endif
if(sshc->ssh_session) {
rc = libssh2_session_free(sshc->ssh_session);
if(rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
if(rc < 0) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to free libssh2 session: %d %s", rc, err_msg);
}
sshc->ssh_session = NULL;
}
/* worst-case scenario cleanup */
DEBUGASSERT(sshc->ssh_session == NULL);
DEBUGASSERT(sshc->ssh_channel == NULL);
DEBUGASSERT(sshc->sftp_session == NULL);
DEBUGASSERT(sshc->sftp_handle == NULL);
#ifdef HAVE_LIBSSH2_KNOWNHOST_API
DEBUGASSERT(sshc->kh == NULL);
#endif
#ifdef HAVE_LIBSSH2_AGENT_API
DEBUGASSERT(sshc->ssh_agent == NULL);
#endif
Curl_safefree(sshc->rsa_pub);
Curl_safefree(sshc->rsa);
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
Curl_safefree(sshc->homedir);
/* the code we are about to return */
result = sshc->actualcode;
memset(sshc, 0, sizeof(struct ssh_conn));
connclose(conn, "SSH session free");
sshc->state = SSH_SESSION_FREE; /* current */
sshc->nextstate = SSH_NO_STATE;
state(data, SSH_STOP);
break;
case SSH_QUIT:
default:
/* internal error */
sshc->nextstate = SSH_NO_STATE;
state(data, SSH_STOP);
break;
}
} while(!rc && (sshc->state != SSH_STOP));
if(rc == LIBSSH2_ERROR_EAGAIN) {
/* we would block, we need to wait for the socket to be ready (in the
right direction too)! */
*block = TRUE;
}
return result;
}