1 | /*************************************************************************** |
2 | * _ _ ____ _ |
3 | * Project ___| | | | _ \| | |
4 | * / __| | | | |_) | | |
5 | * | (__| |_| | _ <| |___ |
6 | * \___|\___/|_| \_\_____| |
7 | * |
8 | * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. |
9 | * |
10 | * This software is licensed as described in the file COPYING, which |
11 | * you should have received as part of this distribution. The terms |
12 | * are also available at https://curl.se/docs/copyright.html. |
13 | * |
14 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | * copies of the Software, and permit persons to whom the Software is |
16 | * furnished to do so, under the terms of the COPYING file. |
17 | * |
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | * KIND, either express or implied. |
20 | * |
21 | * SPDX-License-Identifier: curl |
22 | * |
23 | ***************************************************************************/ |
24 | |
25 | #include "curl_setup.h" |
26 | |
27 | #ifdef USE_WOLFSSH |
28 | |
29 | #include <limits.h> |
30 | |
31 | #include <wolfssh/ssh.h> |
32 | #include <wolfssh/wolfsftp.h> |
33 | #include "urldata.h" |
34 | #include "cfilters.h" |
35 | #include "connect.h" |
36 | #include "sendf.h" |
37 | #include "progress.h" |
38 | #include "curl_path.h" |
39 | #include "strtoofft.h" |
40 | #include "transfer.h" |
41 | #include "speedcheck.h" |
42 | #include "select.h" |
43 | #include "multiif.h" |
44 | #include "warnless.h" |
45 | |
46 | /* The last 3 #include files should be in this order */ |
47 | #include "curl_printf.h" |
48 | #include "curl_memory.h" |
49 | #include "memdebug.h" |
50 | |
51 | static CURLcode wssh_connect(struct Curl_easy *data, bool *done); |
52 | static CURLcode wssh_multi_statemach(struct Curl_easy *data, bool *done); |
53 | static CURLcode wssh_do(struct Curl_easy *data, bool *done); |
54 | #if 0 |
55 | static CURLcode wscp_done(struct Curl_easy *data, |
56 | CURLcode, bool premature); |
57 | static CURLcode wscp_doing(struct Curl_easy *data, |
58 | bool *dophase_done); |
59 | static CURLcode wscp_disconnect(struct Curl_easy *data, |
60 | struct connectdata *conn, |
61 | bool dead_connection); |
62 | #endif |
63 | static CURLcode wsftp_done(struct Curl_easy *data, |
64 | CURLcode, bool premature); |
65 | static CURLcode wsftp_doing(struct Curl_easy *data, |
66 | bool *dophase_done); |
67 | static CURLcode wsftp_disconnect(struct Curl_easy *data, |
68 | struct connectdata *conn, |
69 | bool dead); |
70 | static int wssh_getsock(struct Curl_easy *data, |
71 | struct connectdata *conn, |
72 | curl_socket_t *sock); |
73 | static CURLcode wssh_setup_connection(struct Curl_easy *data, |
74 | struct connectdata *conn); |
75 | |
76 | #if 0 |
77 | /* |
78 | * SCP protocol handler. |
79 | */ |
80 | |
81 | const struct Curl_handler Curl_handler_scp = { |
82 | "SCP" , /* scheme */ |
83 | wssh_setup_connection, /* setup_connection */ |
84 | wssh_do, /* do_it */ |
85 | wscp_done, /* done */ |
86 | ZERO_NULL, /* do_more */ |
87 | wssh_connect, /* connect_it */ |
88 | wssh_multi_statemach, /* connecting */ |
89 | wscp_doing, /* doing */ |
90 | wssh_getsock, /* proto_getsock */ |
91 | wssh_getsock, /* doing_getsock */ |
92 | ZERO_NULL, /* domore_getsock */ |
93 | wssh_getsock, /* perform_getsock */ |
94 | wscp_disconnect, /* disconnect */ |
95 | ZERO_NULL, /* readwrite */ |
96 | ZERO_NULL, /* connection_check */ |
97 | ZERO_NULL, /* attach connection */ |
98 | PORT_SSH, /* defport */ |
99 | CURLPROTO_SCP, /* protocol */ |
100 | PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION |
101 | | PROTOPT_NOURLQUERY /* flags */ |
102 | }; |
103 | |
104 | #endif |
105 | |
106 | /* |
107 | * SFTP protocol handler. |
108 | */ |
109 | |
110 | const struct Curl_handler Curl_handler_sftp = { |
111 | "SFTP" , /* scheme */ |
112 | wssh_setup_connection, /* setup_connection */ |
113 | wssh_do, /* do_it */ |
114 | wsftp_done, /* done */ |
115 | ZERO_NULL, /* do_more */ |
116 | wssh_connect, /* connect_it */ |
117 | wssh_multi_statemach, /* connecting */ |
118 | wsftp_doing, /* doing */ |
119 | wssh_getsock, /* proto_getsock */ |
120 | wssh_getsock, /* doing_getsock */ |
121 | ZERO_NULL, /* domore_getsock */ |
122 | wssh_getsock, /* perform_getsock */ |
123 | wsftp_disconnect, /* disconnect */ |
124 | ZERO_NULL, /* readwrite */ |
125 | ZERO_NULL, /* connection_check */ |
126 | ZERO_NULL, /* attach connection */ |
127 | PORT_SSH, /* defport */ |
128 | CURLPROTO_SFTP, /* protocol */ |
129 | CURLPROTO_SFTP, /* family */ |
130 | PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION |
131 | | PROTOPT_NOURLQUERY /* flags */ |
132 | }; |
133 | |
134 | /* |
135 | * SSH State machine related code |
136 | */ |
137 | /* This is the ONLY way to change SSH state! */ |
138 | static void state(struct Curl_easy *data, sshstate nowstate) |
139 | { |
140 | struct connectdata *conn = data->conn; |
141 | struct ssh_conn *sshc = &conn->proto.sshc; |
142 | #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) |
143 | /* for debug purposes */ |
144 | static const char * const names[] = { |
145 | "SSH_STOP" , |
146 | "SSH_INIT" , |
147 | "SSH_S_STARTUP" , |
148 | "SSH_HOSTKEY" , |
149 | "SSH_AUTHLIST" , |
150 | "SSH_AUTH_PKEY_INIT" , |
151 | "SSH_AUTH_PKEY" , |
152 | "SSH_AUTH_PASS_INIT" , |
153 | "SSH_AUTH_PASS" , |
154 | "SSH_AUTH_AGENT_INIT" , |
155 | "SSH_AUTH_AGENT_LIST" , |
156 | "SSH_AUTH_AGENT" , |
157 | "SSH_AUTH_HOST_INIT" , |
158 | "SSH_AUTH_HOST" , |
159 | "SSH_AUTH_KEY_INIT" , |
160 | "SSH_AUTH_KEY" , |
161 | "SSH_AUTH_GSSAPI" , |
162 | "SSH_AUTH_DONE" , |
163 | "SSH_SFTP_INIT" , |
164 | "SSH_SFTP_REALPATH" , |
165 | "SSH_SFTP_QUOTE_INIT" , |
166 | "SSH_SFTP_POSTQUOTE_INIT" , |
167 | "SSH_SFTP_QUOTE" , |
168 | "SSH_SFTP_NEXT_QUOTE" , |
169 | "SSH_SFTP_QUOTE_STAT" , |
170 | "SSH_SFTP_QUOTE_SETSTAT" , |
171 | "SSH_SFTP_QUOTE_SYMLINK" , |
172 | "SSH_SFTP_QUOTE_MKDIR" , |
173 | "SSH_SFTP_QUOTE_RENAME" , |
174 | "SSH_SFTP_QUOTE_RMDIR" , |
175 | "SSH_SFTP_QUOTE_UNLINK" , |
176 | "SSH_SFTP_QUOTE_STATVFS" , |
177 | "SSH_SFTP_GETINFO" , |
178 | "SSH_SFTP_FILETIME" , |
179 | "SSH_SFTP_TRANS_INIT" , |
180 | "SSH_SFTP_UPLOAD_INIT" , |
181 | "SSH_SFTP_CREATE_DIRS_INIT" , |
182 | "SSH_SFTP_CREATE_DIRS" , |
183 | "SSH_SFTP_CREATE_DIRS_MKDIR" , |
184 | "SSH_SFTP_READDIR_INIT" , |
185 | "SSH_SFTP_READDIR" , |
186 | "SSH_SFTP_READDIR_LINK" , |
187 | "SSH_SFTP_READDIR_BOTTOM" , |
188 | "SSH_SFTP_READDIR_DONE" , |
189 | "SSH_SFTP_DOWNLOAD_INIT" , |
190 | "SSH_SFTP_DOWNLOAD_STAT" , |
191 | "SSH_SFTP_CLOSE" , |
192 | "SSH_SFTP_SHUTDOWN" , |
193 | "SSH_SCP_TRANS_INIT" , |
194 | "SSH_SCP_UPLOAD_INIT" , |
195 | "SSH_SCP_DOWNLOAD_INIT" , |
196 | "SSH_SCP_DOWNLOAD" , |
197 | "SSH_SCP_DONE" , |
198 | "SSH_SCP_SEND_EOF" , |
199 | "SSH_SCP_WAIT_EOF" , |
200 | "SSH_SCP_WAIT_CLOSE" , |
201 | "SSH_SCP_CHANNEL_FREE" , |
202 | "SSH_SESSION_DISCONNECT" , |
203 | "SSH_SESSION_FREE" , |
204 | "QUIT" |
205 | }; |
206 | |
207 | /* a precaution to make sure the lists are in sync */ |
208 | DEBUGASSERT(sizeof(names)/sizeof(names[0]) == SSH_LAST); |
209 | |
210 | if(sshc->state != nowstate) { |
211 | infof(data, "wolfssh %p state change from %s to %s" , |
212 | (void *)sshc, names[sshc->state], names[nowstate]); |
213 | } |
214 | #endif |
215 | |
216 | sshc->state = nowstate; |
217 | } |
218 | |
219 | static ssize_t wscp_send(struct Curl_easy *data, int sockindex, |
220 | const void *mem, size_t len, CURLcode *err) |
221 | { |
222 | ssize_t nwrite = 0; |
223 | (void)data; |
224 | (void)sockindex; /* we only support SCP on the fixed known primary socket */ |
225 | (void)mem; |
226 | (void)len; |
227 | (void)err; |
228 | |
229 | return nwrite; |
230 | } |
231 | |
232 | static ssize_t wscp_recv(struct Curl_easy *data, int sockindex, |
233 | char *mem, size_t len, CURLcode *err) |
234 | { |
235 | ssize_t nread = 0; |
236 | (void)data; |
237 | (void)sockindex; /* we only support SCP on the fixed known primary socket */ |
238 | (void)mem; |
239 | (void)len; |
240 | (void)err; |
241 | |
242 | return nread; |
243 | } |
244 | |
245 | /* return number of sent bytes */ |
246 | static ssize_t wsftp_send(struct Curl_easy *data, int sockindex, |
247 | const void *mem, size_t len, CURLcode *err) |
248 | { |
249 | struct connectdata *conn = data->conn; |
250 | struct ssh_conn *sshc = &conn->proto.sshc; |
251 | word32 offset[2]; |
252 | int rc; |
253 | (void)sockindex; |
254 | |
255 | offset[0] = (word32)sshc->offset&0xFFFFFFFF; |
256 | offset[1] = (word32)(sshc->offset>>32)&0xFFFFFFFF; |
257 | |
258 | rc = wolfSSH_SFTP_SendWritePacket(sshc->ssh_session, sshc->handle, |
259 | sshc->handleSz, |
260 | &offset[0], |
261 | (byte *)mem, (word32)len); |
262 | |
263 | if(rc == WS_FATAL_ERROR) |
264 | rc = wolfSSH_get_error(sshc->ssh_session); |
265 | if(rc == WS_WANT_READ) { |
266 | conn->waitfor = KEEP_RECV; |
267 | *err = CURLE_AGAIN; |
268 | return -1; |
269 | } |
270 | else if(rc == WS_WANT_WRITE) { |
271 | conn->waitfor = KEEP_SEND; |
272 | *err = CURLE_AGAIN; |
273 | return -1; |
274 | } |
275 | if(rc < 0) { |
276 | failf(data, "wolfSSH_SFTP_SendWritePacket returned %d" , rc); |
277 | return -1; |
278 | } |
279 | DEBUGASSERT(rc == (int)len); |
280 | infof(data, "sent %zu bytes SFTP from offset %" CURL_FORMAT_CURL_OFF_T, |
281 | len, sshc->offset); |
282 | sshc->offset += len; |
283 | return (ssize_t)rc; |
284 | } |
285 | |
286 | /* |
287 | * Return number of received (decrypted) bytes |
288 | * or <0 on error |
289 | */ |
290 | static ssize_t wsftp_recv(struct Curl_easy *data, int sockindex, |
291 | char *mem, size_t len, CURLcode *err) |
292 | { |
293 | int rc; |
294 | struct connectdata *conn = data->conn; |
295 | struct ssh_conn *sshc = &conn->proto.sshc; |
296 | word32 offset[2]; |
297 | (void)sockindex; |
298 | |
299 | offset[0] = (word32)sshc->offset&0xFFFFFFFF; |
300 | offset[1] = (word32)(sshc->offset>>32)&0xFFFFFFFF; |
301 | |
302 | rc = wolfSSH_SFTP_SendReadPacket(sshc->ssh_session, sshc->handle, |
303 | sshc->handleSz, |
304 | &offset[0], |
305 | (byte *)mem, (word32)len); |
306 | if(rc == WS_FATAL_ERROR) |
307 | rc = wolfSSH_get_error(sshc->ssh_session); |
308 | if(rc == WS_WANT_READ) { |
309 | conn->waitfor = KEEP_RECV; |
310 | *err = CURLE_AGAIN; |
311 | return -1; |
312 | } |
313 | else if(rc == WS_WANT_WRITE) { |
314 | conn->waitfor = KEEP_SEND; |
315 | *err = CURLE_AGAIN; |
316 | return -1; |
317 | } |
318 | |
319 | DEBUGASSERT(rc <= (int)len); |
320 | |
321 | if(rc < 0) { |
322 | failf(data, "wolfSSH_SFTP_SendReadPacket returned %d" , rc); |
323 | return -1; |
324 | } |
325 | sshc->offset += len; |
326 | |
327 | return (ssize_t)rc; |
328 | } |
329 | |
330 | /* |
331 | * SSH setup and connection |
332 | */ |
333 | static CURLcode wssh_setup_connection(struct Curl_easy *data, |
334 | struct connectdata *conn) |
335 | { |
336 | struct SSHPROTO *ssh; |
337 | (void)conn; |
338 | |
339 | data->req.p.ssh = ssh = calloc(1, sizeof(struct SSHPROTO)); |
340 | if(!ssh) |
341 | return CURLE_OUT_OF_MEMORY; |
342 | |
343 | return CURLE_OK; |
344 | } |
345 | |
346 | static Curl_recv wscp_recv, wsftp_recv; |
347 | static Curl_send wscp_send, wsftp_send; |
348 | |
349 | static int userauth(byte authtype, |
350 | WS_UserAuthData* authdata, |
351 | void *ctx) |
352 | { |
353 | struct Curl_easy *data = ctx; |
354 | DEBUGF(infof(data, "wolfssh callback: type %s" , |
355 | authtype == WOLFSSH_USERAUTH_PASSWORD ? "PASSWORD" : |
356 | "PUBLICCKEY" )); |
357 | if(authtype == WOLFSSH_USERAUTH_PASSWORD) { |
358 | authdata->sf.password.password = (byte *)data->conn->passwd; |
359 | authdata->sf.password.passwordSz = (word32) strlen(data->conn->passwd); |
360 | } |
361 | |
362 | return 0; |
363 | } |
364 | |
365 | static CURLcode wssh_connect(struct Curl_easy *data, bool *done) |
366 | { |
367 | struct connectdata *conn = data->conn; |
368 | struct ssh_conn *sshc; |
369 | curl_socket_t sock = conn->sock[FIRSTSOCKET]; |
370 | int rc; |
371 | |
372 | /* initialize per-handle data if not already */ |
373 | if(!data->req.p.ssh) |
374 | wssh_setup_connection(data, conn); |
375 | |
376 | /* We default to persistent connections. We set this already in this connect |
377 | function to make the reuse checks properly be able to check this bit. */ |
378 | connkeep(conn, "SSH default" ); |
379 | |
380 | if(conn->handler->protocol & CURLPROTO_SCP) { |
381 | conn->recv[FIRSTSOCKET] = wscp_recv; |
382 | conn->send[FIRSTSOCKET] = wscp_send; |
383 | } |
384 | else { |
385 | conn->recv[FIRSTSOCKET] = wsftp_recv; |
386 | conn->send[FIRSTSOCKET] = wsftp_send; |
387 | } |
388 | sshc = &conn->proto.sshc; |
389 | sshc->ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); |
390 | if(!sshc->ctx) { |
391 | failf(data, "No wolfSSH context" ); |
392 | goto error; |
393 | } |
394 | |
395 | sshc->ssh_session = wolfSSH_new(sshc->ctx); |
396 | if(!sshc->ssh_session) { |
397 | failf(data, "No wolfSSH session" ); |
398 | goto error; |
399 | } |
400 | |
401 | rc = wolfSSH_SetUsername(sshc->ssh_session, conn->user); |
402 | if(rc != WS_SUCCESS) { |
403 | failf(data, "wolfSSH failed to set user name" ); |
404 | goto error; |
405 | } |
406 | |
407 | /* set callback for authentication */ |
408 | wolfSSH_SetUserAuth(sshc->ctx, userauth); |
409 | wolfSSH_SetUserAuthCtx(sshc->ssh_session, data); |
410 | |
411 | rc = wolfSSH_set_fd(sshc->ssh_session, (int)sock); |
412 | if(rc) { |
413 | failf(data, "wolfSSH failed to set socket" ); |
414 | goto error; |
415 | } |
416 | |
417 | #if 0 |
418 | wolfSSH_Debugging_ON(); |
419 | #endif |
420 | |
421 | *done = TRUE; |
422 | if(conn->handler->protocol & CURLPROTO_SCP) |
423 | state(data, SSH_INIT); |
424 | else |
425 | state(data, SSH_SFTP_INIT); |
426 | |
427 | return wssh_multi_statemach(data, done); |
428 | error: |
429 | wolfSSH_free(sshc->ssh_session); |
430 | wolfSSH_CTX_free(sshc->ctx); |
431 | return CURLE_FAILED_INIT; |
432 | } |
433 | |
434 | /* |
435 | * wssh_statemach_act() runs the SSH state machine as far as it can without |
436 | * blocking and without reaching the end. The data the pointer 'block' points |
437 | * to will be set to TRUE if the wolfssh function returns EAGAIN meaning it |
438 | * wants to be called again when the socket is ready |
439 | */ |
440 | |
441 | static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) |
442 | { |
443 | CURLcode result = CURLE_OK; |
444 | struct connectdata *conn = data->conn; |
445 | struct ssh_conn *sshc = &conn->proto.sshc; |
446 | struct SSHPROTO *sftp_scp = data->req.p.ssh; |
447 | WS_SFTPNAME *name; |
448 | int rc = 0; |
449 | *block = FALSE; /* we're not blocking by default */ |
450 | |
451 | do { |
452 | switch(sshc->state) { |
453 | case SSH_INIT: |
454 | state(data, SSH_S_STARTUP); |
455 | break; |
456 | |
457 | case SSH_S_STARTUP: |
458 | rc = wolfSSH_connect(sshc->ssh_session); |
459 | if(rc != WS_SUCCESS) |
460 | rc = wolfSSH_get_error(sshc->ssh_session); |
461 | if(rc == WS_WANT_READ) { |
462 | *block = TRUE; |
463 | conn->waitfor = KEEP_RECV; |
464 | return CURLE_OK; |
465 | } |
466 | else if(rc == WS_WANT_WRITE) { |
467 | *block = TRUE; |
468 | conn->waitfor = KEEP_SEND; |
469 | return CURLE_OK; |
470 | } |
471 | else if(rc != WS_SUCCESS) { |
472 | state(data, SSH_STOP); |
473 | return CURLE_SSH; |
474 | } |
475 | infof(data, "wolfssh connected" ); |
476 | state(data, SSH_STOP); |
477 | break; |
478 | case SSH_STOP: |
479 | break; |
480 | |
481 | case SSH_SFTP_INIT: |
482 | rc = wolfSSH_SFTP_connect(sshc->ssh_session); |
483 | if(rc != WS_SUCCESS) |
484 | rc = wolfSSH_get_error(sshc->ssh_session); |
485 | if(rc == WS_WANT_READ) { |
486 | *block = TRUE; |
487 | conn->waitfor = KEEP_RECV; |
488 | return CURLE_OK; |
489 | } |
490 | else if(rc == WS_WANT_WRITE) { |
491 | *block = TRUE; |
492 | conn->waitfor = KEEP_SEND; |
493 | return CURLE_OK; |
494 | } |
495 | else if(rc == WS_SUCCESS) { |
496 | infof(data, "wolfssh SFTP connected" ); |
497 | state(data, SSH_SFTP_REALPATH); |
498 | } |
499 | else { |
500 | failf(data, "wolfssh SFTP connect error %d" , rc); |
501 | return CURLE_SSH; |
502 | } |
503 | break; |
504 | case SSH_SFTP_REALPATH: |
505 | name = wolfSSH_SFTP_RealPath(sshc->ssh_session, (char *)"." ); |
506 | rc = wolfSSH_get_error(sshc->ssh_session); |
507 | if(rc == WS_WANT_READ) { |
508 | *block = TRUE; |
509 | conn->waitfor = KEEP_RECV; |
510 | return CURLE_OK; |
511 | } |
512 | else if(rc == WS_WANT_WRITE) { |
513 | *block = TRUE; |
514 | conn->waitfor = KEEP_SEND; |
515 | return CURLE_OK; |
516 | } |
517 | else if(name && (rc == WS_SUCCESS)) { |
518 | sshc->homedir = malloc(name->fSz + 1); |
519 | if(!sshc->homedir) { |
520 | sshc->actualcode = CURLE_OUT_OF_MEMORY; |
521 | } |
522 | else { |
523 | memcpy(sshc->homedir, name->fName, name->fSz); |
524 | sshc->homedir[name->fSz] = 0; |
525 | infof(data, "wolfssh SFTP realpath succeeded" ); |
526 | } |
527 | wolfSSH_SFTPNAME_list_free(name); |
528 | state(data, SSH_STOP); |
529 | return CURLE_OK; |
530 | } |
531 | failf(data, "wolfssh SFTP realpath %d" , rc); |
532 | return CURLE_SSH; |
533 | |
534 | case SSH_SFTP_QUOTE_INIT: |
535 | result = Curl_getworkingpath(data, sshc->homedir, &sftp_scp->path); |
536 | if(result) { |
537 | sshc->actualcode = result; |
538 | state(data, SSH_STOP); |
539 | break; |
540 | } |
541 | |
542 | if(data->set.quote) { |
543 | infof(data, "Sending quote commands" ); |
544 | sshc->quote_item = data->set.quote; |
545 | state(data, SSH_SFTP_QUOTE); |
546 | } |
547 | else { |
548 | state(data, SSH_SFTP_GETINFO); |
549 | } |
550 | break; |
551 | case SSH_SFTP_GETINFO: |
552 | if(data->set.get_filetime) { |
553 | state(data, SSH_SFTP_FILETIME); |
554 | } |
555 | else { |
556 | state(data, SSH_SFTP_TRANS_INIT); |
557 | } |
558 | break; |
559 | case SSH_SFTP_TRANS_INIT: |
560 | if(data->state.upload) |
561 | state(data, SSH_SFTP_UPLOAD_INIT); |
562 | else { |
563 | if(sftp_scp->path[strlen(sftp_scp->path)-1] == '/') |
564 | state(data, SSH_SFTP_READDIR_INIT); |
565 | else |
566 | state(data, SSH_SFTP_DOWNLOAD_INIT); |
567 | } |
568 | break; |
569 | case SSH_SFTP_UPLOAD_INIT: { |
570 | word32 flags; |
571 | WS_SFTP_FILEATRB createattrs; |
572 | if(data->state.resume_from) { |
573 | WS_SFTP_FILEATRB attrs; |
574 | if(data->state.resume_from < 0) { |
575 | rc = wolfSSH_SFTP_STAT(sshc->ssh_session, sftp_scp->path, |
576 | &attrs); |
577 | if(rc != WS_SUCCESS) |
578 | break; |
579 | |
580 | if(rc) { |
581 | data->state.resume_from = 0; |
582 | } |
583 | else { |
584 | curl_off_t size = ((curl_off_t)attrs.sz[1] << 32) | attrs.sz[0]; |
585 | if(size < 0) { |
586 | failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")" , size); |
587 | return CURLE_BAD_DOWNLOAD_RESUME; |
588 | } |
589 | data->state.resume_from = size; |
590 | } |
591 | } |
592 | } |
593 | |
594 | if(data->set.remote_append) |
595 | /* Try to open for append, but create if nonexisting */ |
596 | flags = WOLFSSH_FXF_WRITE|WOLFSSH_FXF_CREAT|WOLFSSH_FXF_APPEND; |
597 | else if(data->state.resume_from > 0) |
598 | /* If we have restart position then open for append */ |
599 | flags = WOLFSSH_FXF_WRITE|WOLFSSH_FXF_APPEND; |
600 | else |
601 | /* Clear file before writing (normal behavior) */ |
602 | flags = WOLFSSH_FXF_WRITE|WOLFSSH_FXF_CREAT|WOLFSSH_FXF_TRUNC; |
603 | |
604 | memset(&createattrs, 0, sizeof(createattrs)); |
605 | createattrs.per = (word32)data->set.new_file_perms; |
606 | sshc->handleSz = sizeof(sshc->handle); |
607 | rc = wolfSSH_SFTP_Open(sshc->ssh_session, sftp_scp->path, |
608 | flags, &createattrs, |
609 | sshc->handle, &sshc->handleSz); |
610 | if(rc == WS_FATAL_ERROR) |
611 | rc = wolfSSH_get_error(sshc->ssh_session); |
612 | if(rc == WS_WANT_READ) { |
613 | *block = TRUE; |
614 | conn->waitfor = KEEP_RECV; |
615 | return CURLE_OK; |
616 | } |
617 | else if(rc == WS_WANT_WRITE) { |
618 | *block = TRUE; |
619 | conn->waitfor = KEEP_SEND; |
620 | return CURLE_OK; |
621 | } |
622 | else if(rc == WS_SUCCESS) { |
623 | infof(data, "wolfssh SFTP open succeeded" ); |
624 | } |
625 | else { |
626 | failf(data, "wolfssh SFTP upload open failed: %d" , rc); |
627 | return CURLE_SSH; |
628 | } |
629 | state(data, SSH_SFTP_DOWNLOAD_STAT); |
630 | |
631 | /* If we have a restart point then we need to seek to the correct |
632 | position. */ |
633 | if(data->state.resume_from > 0) { |
634 | /* Let's read off the proper amount of bytes from the input. */ |
635 | int seekerr = CURL_SEEKFUNC_OK; |
636 | if(conn->seek_func) { |
637 | Curl_set_in_callback(data, true); |
638 | seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, |
639 | SEEK_SET); |
640 | Curl_set_in_callback(data, false); |
641 | } |
642 | |
643 | if(seekerr != CURL_SEEKFUNC_OK) { |
644 | curl_off_t passed = 0; |
645 | |
646 | if(seekerr != CURL_SEEKFUNC_CANTSEEK) { |
647 | failf(data, "Could not seek stream" ); |
648 | return CURLE_FTP_COULDNT_USE_REST; |
649 | } |
650 | /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ |
651 | do { |
652 | size_t readthisamountnow = |
653 | (data->state.resume_from - passed > data->set.buffer_size) ? |
654 | (size_t)data->set.buffer_size : |
655 | curlx_sotouz(data->state.resume_from - passed); |
656 | |
657 | size_t actuallyread; |
658 | Curl_set_in_callback(data, true); |
659 | actuallyread = data->state.fread_func(data->state.buffer, 1, |
660 | readthisamountnow, |
661 | data->state.in); |
662 | Curl_set_in_callback(data, false); |
663 | |
664 | passed += actuallyread; |
665 | if((actuallyread == 0) || (actuallyread > readthisamountnow)) { |
666 | /* this checks for greater-than only to make sure that the |
667 | CURL_READFUNC_ABORT return code still aborts */ |
668 | failf(data, "Failed to read data" ); |
669 | return CURLE_FTP_COULDNT_USE_REST; |
670 | } |
671 | } while(passed < data->state.resume_from); |
672 | } |
673 | |
674 | /* now, decrease the size of the read */ |
675 | if(data->state.infilesize > 0) { |
676 | data->state.infilesize -= data->state.resume_from; |
677 | data->req.size = data->state.infilesize; |
678 | Curl_pgrsSetUploadSize(data, data->state.infilesize); |
679 | } |
680 | |
681 | sshc->offset += data->state.resume_from; |
682 | } |
683 | if(data->state.infilesize > 0) { |
684 | data->req.size = data->state.infilesize; |
685 | Curl_pgrsSetUploadSize(data, data->state.infilesize); |
686 | } |
687 | /* upload data */ |
688 | Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); |
689 | |
690 | /* not set by Curl_setup_transfer to preserve keepon bits */ |
691 | conn->sockfd = conn->writesockfd; |
692 | |
693 | if(result) { |
694 | state(data, SSH_SFTP_CLOSE); |
695 | sshc->actualcode = result; |
696 | } |
697 | else { |
698 | /* store this original bitmask setup to use later on if we can't |
699 | figure out a "real" bitmask */ |
700 | sshc->orig_waitfor = data->req.keepon; |
701 | |
702 | /* we want to use the _sending_ function even when the socket turns |
703 | out readable as the underlying libssh2 sftp send function will deal |
704 | with both accordingly */ |
705 | conn->cselect_bits = CURL_CSELECT_OUT; |
706 | |
707 | /* since we don't really wait for anything at this point, we want the |
708 | state machine to move on as soon as possible so we set a very short |
709 | timeout here */ |
710 | Curl_expire(data, 0, EXPIRE_RUN_NOW); |
711 | |
712 | state(data, SSH_STOP); |
713 | } |
714 | break; |
715 | } |
716 | case SSH_SFTP_DOWNLOAD_INIT: |
717 | sshc->handleSz = sizeof(sshc->handle); |
718 | rc = wolfSSH_SFTP_Open(sshc->ssh_session, sftp_scp->path, |
719 | WOLFSSH_FXF_READ, NULL, |
720 | sshc->handle, &sshc->handleSz); |
721 | if(rc == WS_FATAL_ERROR) |
722 | rc = wolfSSH_get_error(sshc->ssh_session); |
723 | if(rc == WS_WANT_READ) { |
724 | *block = TRUE; |
725 | conn->waitfor = KEEP_RECV; |
726 | return CURLE_OK; |
727 | } |
728 | else if(rc == WS_WANT_WRITE) { |
729 | *block = TRUE; |
730 | conn->waitfor = KEEP_SEND; |
731 | return CURLE_OK; |
732 | } |
733 | else if(rc == WS_SUCCESS) { |
734 | infof(data, "wolfssh SFTP open succeeded" ); |
735 | state(data, SSH_SFTP_DOWNLOAD_STAT); |
736 | return CURLE_OK; |
737 | } |
738 | |
739 | failf(data, "wolfssh SFTP open failed: %d" , rc); |
740 | return CURLE_SSH; |
741 | |
742 | case SSH_SFTP_DOWNLOAD_STAT: { |
743 | WS_SFTP_FILEATRB attrs; |
744 | curl_off_t size; |
745 | |
746 | rc = wolfSSH_SFTP_STAT(sshc->ssh_session, sftp_scp->path, &attrs); |
747 | if(rc == WS_FATAL_ERROR) |
748 | rc = wolfSSH_get_error(sshc->ssh_session); |
749 | if(rc == WS_WANT_READ) { |
750 | *block = TRUE; |
751 | conn->waitfor = KEEP_RECV; |
752 | return CURLE_OK; |
753 | } |
754 | else if(rc == WS_WANT_WRITE) { |
755 | *block = TRUE; |
756 | conn->waitfor = KEEP_SEND; |
757 | return CURLE_OK; |
758 | } |
759 | else if(rc == WS_SUCCESS) { |
760 | infof(data, "wolfssh STAT succeeded" ); |
761 | } |
762 | else { |
763 | failf(data, "wolfssh SFTP open failed: %d" , rc); |
764 | data->req.size = -1; |
765 | data->req.maxdownload = -1; |
766 | Curl_pgrsSetDownloadSize(data, -1); |
767 | return CURLE_SSH; |
768 | } |
769 | |
770 | size = ((curl_off_t)attrs.sz[1] <<32) | attrs.sz[0]; |
771 | |
772 | data->req.size = size; |
773 | data->req.maxdownload = size; |
774 | Curl_pgrsSetDownloadSize(data, size); |
775 | |
776 | infof(data, "SFTP download %" CURL_FORMAT_CURL_OFF_T " bytes" , size); |
777 | |
778 | /* We cannot seek with wolfSSH so resuming and range requests are not |
779 | possible */ |
780 | if(data->state.use_range || data->state.resume_from) { |
781 | infof(data, "wolfSSH cannot do range/seek on SFTP" ); |
782 | return CURLE_BAD_DOWNLOAD_RESUME; |
783 | } |
784 | |
785 | /* Setup the actual download */ |
786 | if(data->req.size == 0) { |
787 | /* no data to transfer */ |
788 | Curl_setup_transfer(data, -1, -1, FALSE, -1); |
789 | infof(data, "File already completely downloaded" ); |
790 | state(data, SSH_STOP); |
791 | break; |
792 | } |
793 | Curl_setup_transfer(data, FIRSTSOCKET, data->req.size, FALSE, -1); |
794 | |
795 | /* not set by Curl_setup_transfer to preserve keepon bits */ |
796 | conn->writesockfd = conn->sockfd; |
797 | |
798 | /* we want to use the _receiving_ function even when the socket turns |
799 | out writableable as the underlying libssh2 recv function will deal |
800 | with both accordingly */ |
801 | conn->cselect_bits = CURL_CSELECT_IN; |
802 | |
803 | if(result) { |
804 | /* this should never occur; the close state should be entered |
805 | at the time the error occurs */ |
806 | state(data, SSH_SFTP_CLOSE); |
807 | sshc->actualcode = result; |
808 | } |
809 | else { |
810 | state(data, SSH_STOP); |
811 | } |
812 | break; |
813 | } |
814 | case SSH_SFTP_CLOSE: |
815 | if(sshc->handleSz) |
816 | rc = wolfSSH_SFTP_Close(sshc->ssh_session, sshc->handle, |
817 | sshc->handleSz); |
818 | else |
819 | rc = WS_SUCCESS; /* directory listing */ |
820 | if(rc == WS_WANT_READ) { |
821 | *block = TRUE; |
822 | conn->waitfor = KEEP_RECV; |
823 | return CURLE_OK; |
824 | } |
825 | else if(rc == WS_WANT_WRITE) { |
826 | *block = TRUE; |
827 | conn->waitfor = KEEP_SEND; |
828 | return CURLE_OK; |
829 | } |
830 | else if(rc == WS_SUCCESS) { |
831 | state(data, SSH_STOP); |
832 | return CURLE_OK; |
833 | } |
834 | |
835 | failf(data, "wolfssh SFTP CLOSE failed: %d" , rc); |
836 | return CURLE_SSH; |
837 | |
838 | case SSH_SFTP_READDIR_INIT: |
839 | Curl_pgrsSetDownloadSize(data, -1); |
840 | if(data->req.no_body) { |
841 | state(data, SSH_STOP); |
842 | break; |
843 | } |
844 | state(data, SSH_SFTP_READDIR); |
845 | break; |
846 | |
847 | case SSH_SFTP_READDIR: |
848 | name = wolfSSH_SFTP_LS(sshc->ssh_session, sftp_scp->path); |
849 | if(!name) |
850 | rc = wolfSSH_get_error(sshc->ssh_session); |
851 | else |
852 | rc = WS_SUCCESS; |
853 | |
854 | if(rc == WS_WANT_READ) { |
855 | *block = TRUE; |
856 | conn->waitfor = KEEP_RECV; |
857 | return CURLE_OK; |
858 | } |
859 | else if(rc == WS_WANT_WRITE) { |
860 | *block = TRUE; |
861 | conn->waitfor = KEEP_SEND; |
862 | return CURLE_OK; |
863 | } |
864 | else if(name && (rc == WS_SUCCESS)) { |
865 | WS_SFTPNAME *origname = name; |
866 | result = CURLE_OK; |
867 | while(name) { |
868 | char *line = aprintf("%s\n" , |
869 | data->set.list_only ? |
870 | name->fName : name->lName); |
871 | if(!line) { |
872 | state(data, SSH_SFTP_CLOSE); |
873 | sshc->actualcode = CURLE_OUT_OF_MEMORY; |
874 | break; |
875 | } |
876 | result = Curl_client_write(data, CLIENTWRITE_BODY, |
877 | line, strlen(line)); |
878 | free(line); |
879 | if(result) { |
880 | sshc->actualcode = result; |
881 | break; |
882 | } |
883 | name = name->next; |
884 | } |
885 | wolfSSH_SFTPNAME_list_free(origname); |
886 | state(data, SSH_STOP); |
887 | return result; |
888 | } |
889 | failf(data, "wolfssh SFTP ls failed: %d" , rc); |
890 | return CURLE_SSH; |
891 | |
892 | case SSH_SFTP_SHUTDOWN: |
893 | Curl_safefree(sshc->homedir); |
894 | wolfSSH_free(sshc->ssh_session); |
895 | wolfSSH_CTX_free(sshc->ctx); |
896 | state(data, SSH_STOP); |
897 | return CURLE_OK; |
898 | default: |
899 | break; |
900 | } |
901 | } while(!rc && (sshc->state != SSH_STOP)); |
902 | return result; |
903 | } |
904 | |
905 | /* called repeatedly until done from multi.c */ |
906 | static CURLcode wssh_multi_statemach(struct Curl_easy *data, bool *done) |
907 | { |
908 | struct connectdata *conn = data->conn; |
909 | struct ssh_conn *sshc = &conn->proto.sshc; |
910 | CURLcode result = CURLE_OK; |
911 | bool block; /* we store the status and use that to provide a ssh_getsock() |
912 | implementation */ |
913 | do { |
914 | result = wssh_statemach_act(data, &block); |
915 | *done = (sshc->state == SSH_STOP) ? TRUE : FALSE; |
916 | /* if there's no error, it isn't done and it didn't EWOULDBLOCK, then |
917 | try again */ |
918 | if(*done) { |
919 | DEBUGF(infof(data, "wssh_statemach_act says DONE" )); |
920 | } |
921 | } while(!result && !*done && !block); |
922 | |
923 | return result; |
924 | } |
925 | |
926 | static |
927 | CURLcode wscp_perform(struct Curl_easy *data, |
928 | bool *connected, |
929 | bool *dophase_done) |
930 | { |
931 | (void)data; |
932 | (void)connected; |
933 | (void)dophase_done; |
934 | return CURLE_OK; |
935 | } |
936 | |
937 | static |
938 | CURLcode wsftp_perform(struct Curl_easy *data, |
939 | bool *connected, |
940 | bool *dophase_done) |
941 | { |
942 | CURLcode result = CURLE_OK; |
943 | |
944 | DEBUGF(infof(data, "DO phase starts" )); |
945 | |
946 | *dophase_done = FALSE; /* not done yet */ |
947 | |
948 | /* start the first command in the DO phase */ |
949 | state(data, SSH_SFTP_QUOTE_INIT); |
950 | |
951 | /* run the state-machine */ |
952 | result = wssh_multi_statemach(data, dophase_done); |
953 | |
954 | *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET); |
955 | |
956 | if(*dophase_done) { |
957 | DEBUGF(infof(data, "DO phase is complete" )); |
958 | } |
959 | |
960 | return result; |
961 | } |
962 | |
963 | /* |
964 | * The DO function is generic for both protocols. |
965 | */ |
966 | static CURLcode wssh_do(struct Curl_easy *data, bool *done) |
967 | { |
968 | CURLcode result; |
969 | bool connected = 0; |
970 | struct connectdata *conn = data->conn; |
971 | struct ssh_conn *sshc = &conn->proto.sshc; |
972 | |
973 | *done = FALSE; /* default to false */ |
974 | data->req.size = -1; /* make sure this is unknown at this point */ |
975 | sshc->actualcode = CURLE_OK; /* reset error code */ |
976 | sshc->secondCreateDirs = 0; /* reset the create dir attempt state |
977 | variable */ |
978 | |
979 | Curl_pgrsSetUploadCounter(data, 0); |
980 | Curl_pgrsSetDownloadCounter(data, 0); |
981 | Curl_pgrsSetUploadSize(data, -1); |
982 | Curl_pgrsSetDownloadSize(data, -1); |
983 | |
984 | if(conn->handler->protocol & CURLPROTO_SCP) |
985 | result = wscp_perform(data, &connected, done); |
986 | else |
987 | result = wsftp_perform(data, &connected, done); |
988 | |
989 | return result; |
990 | } |
991 | |
992 | static CURLcode wssh_block_statemach(struct Curl_easy *data, |
993 | bool disconnect) |
994 | { |
995 | struct connectdata *conn = data->conn; |
996 | struct ssh_conn *sshc = &conn->proto.sshc; |
997 | CURLcode result = CURLE_OK; |
998 | |
999 | while((sshc->state != SSH_STOP) && !result) { |
1000 | bool block; |
1001 | timediff_t left = 1000; |
1002 | struct curltime now = Curl_now(); |
1003 | |
1004 | result = wssh_statemach_act(data, &block); |
1005 | if(result) |
1006 | break; |
1007 | |
1008 | if(!disconnect) { |
1009 | if(Curl_pgrsUpdate(data)) |
1010 | return CURLE_ABORTED_BY_CALLBACK; |
1011 | |
1012 | result = Curl_speedcheck(data, now); |
1013 | if(result) |
1014 | break; |
1015 | |
1016 | left = Curl_timeleft(data, NULL, FALSE); |
1017 | if(left < 0) { |
1018 | failf(data, "Operation timed out" ); |
1019 | return CURLE_OPERATION_TIMEDOUT; |
1020 | } |
1021 | } |
1022 | |
1023 | if(!result) { |
1024 | int dir = conn->waitfor; |
1025 | curl_socket_t sock = conn->sock[FIRSTSOCKET]; |
1026 | curl_socket_t fd_read = CURL_SOCKET_BAD; |
1027 | curl_socket_t fd_write = CURL_SOCKET_BAD; |
1028 | if(dir == KEEP_RECV) |
1029 | fd_read = sock; |
1030 | else if(dir == KEEP_SEND) |
1031 | fd_write = sock; |
1032 | |
1033 | /* wait for the socket to become ready */ |
1034 | (void)Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write, |
1035 | left>1000?1000:left); /* ignore result */ |
1036 | } |
1037 | } |
1038 | |
1039 | return result; |
1040 | } |
1041 | |
1042 | /* generic done function for both SCP and SFTP called from their specific |
1043 | done functions */ |
1044 | static CURLcode wssh_done(struct Curl_easy *data, CURLcode status) |
1045 | { |
1046 | CURLcode result = CURLE_OK; |
1047 | struct SSHPROTO *sftp_scp = data->req.p.ssh; |
1048 | |
1049 | if(!status) { |
1050 | /* run the state-machine */ |
1051 | result = wssh_block_statemach(data, FALSE); |
1052 | } |
1053 | else |
1054 | result = status; |
1055 | |
1056 | if(sftp_scp) |
1057 | Curl_safefree(sftp_scp->path); |
1058 | if(Curl_pgrsDone(data)) |
1059 | return CURLE_ABORTED_BY_CALLBACK; |
1060 | |
1061 | data->req.keepon = 0; /* clear all bits */ |
1062 | return result; |
1063 | } |
1064 | |
1065 | #if 0 |
1066 | static CURLcode wscp_done(struct Curl_easy *data, |
1067 | CURLcode code, bool premature) |
1068 | { |
1069 | CURLcode result = CURLE_OK; |
1070 | (void)conn; |
1071 | (void)code; |
1072 | (void)premature; |
1073 | |
1074 | return result; |
1075 | } |
1076 | |
1077 | static CURLcode wscp_doing(struct Curl_easy *data, |
1078 | bool *dophase_done) |
1079 | { |
1080 | CURLcode result = CURLE_OK; |
1081 | (void)conn; |
1082 | (void)dophase_done; |
1083 | |
1084 | return result; |
1085 | } |
1086 | |
1087 | static CURLcode wscp_disconnect(struct Curl_easy *data, |
1088 | struct connectdata *conn, bool dead_connection) |
1089 | { |
1090 | CURLcode result = CURLE_OK; |
1091 | (void)data; |
1092 | (void)conn; |
1093 | (void)dead_connection; |
1094 | |
1095 | return result; |
1096 | } |
1097 | #endif |
1098 | |
1099 | static CURLcode wsftp_done(struct Curl_easy *data, |
1100 | CURLcode code, bool premature) |
1101 | { |
1102 | (void)premature; |
1103 | state(data, SSH_SFTP_CLOSE); |
1104 | |
1105 | return wssh_done(data, code); |
1106 | } |
1107 | |
1108 | static CURLcode wsftp_doing(struct Curl_easy *data, |
1109 | bool *dophase_done) |
1110 | { |
1111 | CURLcode result = wssh_multi_statemach(data, dophase_done); |
1112 | |
1113 | if(*dophase_done) { |
1114 | DEBUGF(infof(data, "DO phase is complete" )); |
1115 | } |
1116 | return result; |
1117 | } |
1118 | |
1119 | static CURLcode wsftp_disconnect(struct Curl_easy *data, |
1120 | struct connectdata *conn, |
1121 | bool dead) |
1122 | { |
1123 | CURLcode result = CURLE_OK; |
1124 | (void)dead; |
1125 | |
1126 | DEBUGF(infof(data, "SSH DISCONNECT starts now" )); |
1127 | |
1128 | if(conn->proto.sshc.ssh_session) { |
1129 | /* only if there's a session still around to use! */ |
1130 | state(data, SSH_SFTP_SHUTDOWN); |
1131 | result = wssh_block_statemach(data, TRUE); |
1132 | } |
1133 | |
1134 | DEBUGF(infof(data, "SSH DISCONNECT is done" )); |
1135 | return result; |
1136 | } |
1137 | |
1138 | static int wssh_getsock(struct Curl_easy *data, |
1139 | struct connectdata *conn, |
1140 | curl_socket_t *sock) |
1141 | { |
1142 | int bitmap = GETSOCK_BLANK; |
1143 | int dir = conn->waitfor; |
1144 | (void)data; |
1145 | sock[0] = conn->sock[FIRSTSOCKET]; |
1146 | |
1147 | if(dir == KEEP_RECV) |
1148 | bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); |
1149 | else if(dir == KEEP_SEND) |
1150 | bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); |
1151 | |
1152 | return bitmap; |
1153 | } |
1154 | |
1155 | void Curl_ssh_version(char *buffer, size_t buflen) |
1156 | { |
1157 | (void)msnprintf(buffer, buflen, "wolfssh/%s" , LIBWOLFSSH_VERSION_STRING); |
1158 | } |
1159 | |
1160 | CURLcode Curl_ssh_init(void) |
1161 | { |
1162 | if(WS_SUCCESS != wolfSSH_Init()) { |
1163 | DEBUGF(fprintf(stderr, "Error: wolfSSH_Init failed\n" )); |
1164 | return CURLE_FAILED_INIT; |
1165 | } |
1166 | |
1167 | return CURLE_OK; |
1168 | } |
1169 | void Curl_ssh_cleanup(void) |
1170 | { |
1171 | (void)wolfSSH_Cleanup(); |
1172 | } |
1173 | |
1174 | #endif /* USE_WOLFSSH */ |
1175 | |