1 /* GIO - GLib Input, Output and Streaming Library
2 *
3 * Copyright (C) 2009 Benjamin Otte <otte@gnome.org>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 *
20 * Author: Benjamin Otte <otte@gnome.org>
21 */
22
23 #include <config.h>
24
25 #include <stdio.h> /* for sscanf() */
26 #include <stdlib.h> /* for exit() */
27
28 #include <glib/gi18n.h>
29
30 #include "gvfsftptask.h"
31
32 /*** DOCS ***/
33
34 /**
35 * GVfsFtpResponseFlags:
36 * @G_VFS_FTP_PASS_100: Don't treat 1XX responses, but return them
37 * @G_VFS_FTP_PASS_300: Don't treat 3XX responses, but return them
38 * @G_VFS_FTP_PASS_400: Don't treat 4XX responses, but return them
39 * @G_VFS_FTP_PASS_500: Don't treat 5XX responses, but return them
40 * @G_VFS_FTP_PASS_550: Don't treat 550 responses, but return them
41 * @G_VFS_FTP_FAIL_200: Fail on a 2XX response
42 *
43 * These flags can be passed to gvfs_ftp_task_receive() (and in
44 * turn gvfs_ftp_task_send()) to influence the behavior of the functions.
45 */
46
47 /**
48 * G_VFS_FTP_G_VFS_FTP_GROUP:
49 * @response: a valid ftp response
50 *
51 * Determines the group the given @response belongs to. The group is the first
52 * digit of the reply.
53 *
54 * Returns: The group the response code belonged to from 1-5
55 */
56
57 /**
58 * G_VFS_FTP_TASK_INIT:
59 * @backend: the backend used by this task
60 * @job: the job that initiated the task or %NULL if none
61 *
62 * Initializes a new task structure for the given backend and job.
63 */
64
65 /**
66 * GVfsFtpErrorFunc:
67 * @task: task to handle
68 * @data: data argument provided to g_vfs_ftp_task_send_and_check()
69 *
70 * Function prototype for error checking functions used by
71 * g_vfs_ftp_task_send_and_check(). When called, these functions are supposed
72 * to check a specific error condition and if met, set an error on the passed
73 * @task.
74 */
75
76 /*** CODE ***/
77
78 gboolean
g_vfs_ftp_task_login(GVfsFtpTask * task,const char * username,const char * password)79 g_vfs_ftp_task_login (GVfsFtpTask *task,
80 const char * username,
81 const char * password)
82 {
83 guint status;
84
85 g_return_val_if_fail (task != NULL, FALSE);
86 g_return_val_if_fail (username != NULL, FALSE);
87 g_return_val_if_fail (password != NULL, FALSE);
88
89 if (g_vfs_ftp_task_is_in_error (task))
90 return FALSE;
91
92 status = g_vfs_ftp_task_send (task, G_VFS_FTP_PASS_300,
93 "USER %s", username);
94
95 if (G_VFS_FTP_RESPONSE_GROUP (status) == 3)
96 {
97 /* rationale for choosing the default password:
98 * - some ftp servers expect something that looks like an email address
99 * - we don't want to send the user's name or address, as that would be
100 * a privacy problem
101 * - we want to give ftp server administrators a chance to notify us of
102 * problems with our client.
103 * - we don't want to drown in spam.
104 */
105 if (password == NULL || password[0] == 0)
106 password = "gvfsd-ftp-" VERSION "@example.com";
107 status = g_vfs_ftp_task_send (task, 0,
108 "PASS %s", password);
109 }
110
111 return status;
112 }
113
114 /**
115 * g_vfs_ftp_task_setup_connection:
116 * @task: the task
117 *
118 * Sends all commands necessary to put the connection into a usable state,
119 * like setting the transfer mode to binary. Note that passive mode will
120 * will be set on a case-by-case basis when opening a data connection.
121 **/
122 void
g_vfs_ftp_task_setup_connection(GVfsFtpTask * task)123 g_vfs_ftp_task_setup_connection (GVfsFtpTask *task)
124 {
125 g_return_if_fail (task != NULL);
126
127 /* only binary transfers please */
128 g_vfs_ftp_task_send (task, 0, "TYPE I");
129 if (g_vfs_ftp_task_is_in_error (task))
130 return;
131
132 #if 0
133 /* RFC 2428 suggests to send this to make NAT routers happy */
134 /* XXX: Disabled for the following reasons:
135 * - most ftp clients don't use it
136 * - lots of broken ftp servers can't see the difference between
137 * "EPSV" and "EPSV ALL"
138 * - impossible to dynamically fall back to regular PASV in case
139 * EPSV doesn't work for some reason.
140 * If this makes your ftp connection fail, please file a bug and we will
141 * try to invent a way to make this all work. Until then, we'll just
142 * ignore the RFC.
143 */
144 if (g_vfs_backend_ftp_has_feature (task->backend, g_VFS_FTP_FEATURE_EPSV))
145 g_vfs_ftp_task_send (task, 0, "EPSV ALL");
146 g_vfs_ftp_task_clear_error (task);
147 #endif
148
149 /* instruct server that we'll give and assume we get utf8 */
150 if (g_vfs_backend_ftp_has_feature (task->backend, G_VFS_FTP_FEATURE_UTF8))
151 {
152 if (!g_vfs_ftp_task_send (task, 0, "OPTS UTF8 ON"))
153 g_vfs_ftp_task_clear_error (task);
154 }
155 }
156
157
158 static void
do_broadcast(GCancellable * cancellable,GCond * cond)159 do_broadcast (GCancellable *cancellable, GCond *cond)
160 {
161 g_cond_broadcast (cond);
162 }
163
164 /* Decide whether to allow verification errors for control and data connections
165 * after the initial connection. The connection is only allowed if the
166 * identity is the same as for the initial connection. */
167 static gboolean
reconnect_certificate_cb(GTlsConnection * conn,GTlsCertificate * certificate,GTlsCertificateFlags errors,gpointer user_data)168 reconnect_certificate_cb (GTlsConnection *conn,
169 GTlsCertificate *certificate,
170 GTlsCertificateFlags errors,
171 gpointer user_data)
172 {
173 GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (user_data);
174
175 /* If the verification result has changed in some way, abort the
176 * connection. */
177 if (errors != ftp->certificate_errors)
178 return FALSE;
179
180 /* Only allow the connection if the certificate presented is the same as for
181 * the initial connection which the user accepted. */
182 return ftp->certificate &&
183 g_tls_certificate_is_same (certificate, ftp->certificate);
184 }
185
186 /**
187 * g_vfs_ftp_task_acquire_connection:
188 * @task: a task without an associated connection
189 *
190 * Acquires a new connection for use by this @task. This uses the connection
191 * pool of @task's backend, so it reuses previously opened connections and
192 * does not reopen new connections unnecessarily. If all connections are busy,
193 * it waits %G_VFS_FTP_TIMEOUT_IN_SECONDS seconds for a new connection to
194 * become available. Keep in mind that a newly acquired connection might have
195 * timed out and therefore closed by the FTP server. You must account for
196 * this when sending the first command to the server.
197 *
198 * Returns: %TRUE if a connection could be acquired, %FALSE if an error
199 * occured
200 **/
201 static gboolean
g_vfs_ftp_task_acquire_connection(GVfsFtpTask * task)202 g_vfs_ftp_task_acquire_connection (GVfsFtpTask *task)
203 {
204 GVfsBackendFtp *ftp;
205 gint64 end_time;
206 gulong id;
207
208 g_return_val_if_fail (task != NULL, FALSE);
209 g_return_val_if_fail (task->conn == NULL, FALSE);
210
211 if (g_vfs_ftp_task_is_in_error (task))
212 return FALSE;
213
214 ftp = task->backend;
215 g_mutex_lock (&ftp->mutex);
216 id = g_cancellable_connect (task->cancellable,
217 G_CALLBACK (do_broadcast),
218 &ftp->cond, NULL);
219 while (task->conn == NULL && ftp->queue != NULL)
220 {
221 if (g_cancellable_is_cancelled (task->cancellable))
222 {
223 task->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED,
224 _("Operation was cancelled"));
225 break;
226 }
227
228 task->conn = g_queue_pop_head (ftp->queue);
229 if (task->conn != NULL)
230 {
231 if (g_vfs_ftp_connection_is_usable (task->conn))
232 break;
233
234 g_vfs_ftp_connection_free (task->conn);
235 task->conn = NULL;
236 }
237
238 if (ftp->connections < ftp->max_connections)
239 {
240 static GThread *last_thread = NULL;
241 /* Save current number of connections here, so we can limit maximum
242 * connections later.
243 * This is necessary for threading reasons (connections can be
244 * opened or closed while we are still in the opening process. */
245 guint maybe_max_connections = ftp->connections;
246
247 ftp->connections++;
248 last_thread = g_thread_self ();
249 g_mutex_unlock (&ftp->mutex);
250 task->conn = g_vfs_ftp_connection_new (ftp->addr, task->cancellable, &task->error);
251 if (G_LIKELY (task->conn != NULL))
252 {
253 g_vfs_ftp_task_initial_handshake (task, reconnect_certificate_cb, ftp);
254 g_vfs_ftp_task_login (task, ftp->user, ftp->password);
255 g_vfs_ftp_task_setup_connection (task);
256 if (G_LIKELY (!g_vfs_ftp_task_is_in_error (task)))
257 goto out_unlocked;
258
259 g_vfs_ftp_connection_free (task->conn);
260 task->conn = NULL;
261 }
262
263 g_mutex_lock (&ftp->mutex);
264 ftp->connections--;
265 /* If this value is still equal to our thread it means there were no races
266 * trying to open connections and the maybe_max_connections value is
267 * reliable. */
268 if (last_thread == g_thread_self () &&
269 !g_vfs_ftp_task_error_matches (task, G_IO_ERROR, G_IO_ERROR_CANCELLED))
270 {
271 g_print ("maybe: %u, max %u (due to %s)\n", maybe_max_connections, ftp->max_connections, task->error->message);
272 ftp->max_connections = MIN (ftp->max_connections, maybe_max_connections);
273 if (ftp->max_connections == 0)
274 {
275 g_debug ("no more connections left, exiting...\n");
276 /* FIXME: shut down properly */
277 exit (0);
278 }
279 }
280
281 g_vfs_ftp_task_clear_error (task);
282 continue;
283 }
284
285 end_time = g_get_monotonic_time () + G_VFS_FTP_TIMEOUT_IN_SECONDS * G_TIME_SPAN_SECOND;
286 if (ftp->busy_connections >= ftp->connections ||
287 !g_cond_wait_until (&ftp->cond, &ftp->mutex, end_time))
288 {
289 task->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_BUSY,
290 _("The FTP server is busy. Try again later"));
291 break;
292 }
293 }
294 if (!ftp->queue)
295 task->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED,
296 _("Backend currently unmounting"));
297 g_mutex_unlock (&ftp->mutex);
298
299 out_unlocked:
300 g_cancellable_disconnect (task->cancellable, id);
301
302 return task->conn != NULL;
303 }
304
305 /**
306 * g_vfs_ftp_task_release_connection:
307 * @task: a task
308 *
309 * Releases the connection in use by @task to the backend's connection pool,
310 * or frees it if it is in an error state. You must use this function to free
311 * a @task's connection, never use g_vfs_ftp_connection_free() directly. If
312 * the task does not have a current connection, this function just returns.
313 *
314 * This function also closes all potentially open data connections.
315 **/
316 static void
g_vfs_ftp_task_release_connection(GVfsFtpTask * task)317 g_vfs_ftp_task_release_connection (GVfsFtpTask *task)
318 {
319 g_return_if_fail (task != NULL);
320
321 /* we allow task->conn == NULL to ease error cases */
322 if (task->conn == NULL)
323 return;
324
325 g_vfs_ftp_task_close_data_connection (task);
326
327 g_mutex_lock (&task->backend->mutex);
328 if (task->backend->queue && g_vfs_ftp_connection_is_usable (task->conn))
329 {
330 g_queue_push_tail (task->backend->queue, task->conn);
331 g_cond_signal (&task->backend->cond);
332 }
333 else
334 {
335 task->backend->connections--;
336 g_vfs_ftp_connection_free (task->conn);
337 }
338 g_mutex_unlock (&task->backend->mutex);
339 task->conn = NULL;
340 }
341
342 /**
343 * g_vfs_ftp_task_done:
344 * @task: the task to finalize
345 *
346 * Finalizes the given task and clears all memory in use. It also marks the
347 * associated job as success or failure depending on the error state of the
348 * task.
349 **/
350 void
g_vfs_ftp_task_done(GVfsFtpTask * task)351 g_vfs_ftp_task_done (GVfsFtpTask *task)
352 {
353 g_return_if_fail (task != NULL);
354
355 g_vfs_ftp_task_release_connection (task);
356
357 if (task->job)
358 {
359 if (g_vfs_ftp_task_is_in_error (task))
360 g_vfs_job_failed_from_error (task->job, task->error);
361 else
362 g_vfs_job_succeeded (task->job);
363 }
364
365 g_vfs_ftp_task_clear_error (task);
366 }
367
368 /**
369 * g_vfs_ftp_task_set_error_from_response:
370 * @task: the task
371 * @response: the response code
372 *
373 * Sets the @task into an error state. The exact error is determined from the
374 * @response code.
375 **/
376 void
g_vfs_ftp_task_set_error_from_response(GVfsFtpTask * task,guint response)377 g_vfs_ftp_task_set_error_from_response (GVfsFtpTask *task, guint response)
378 {
379 const char *msg;
380 int code;
381
382 g_return_if_fail (task != NULL);
383 g_return_if_fail (task->error == NULL);
384
385 /* Please keep this list ordered by response code,
386 * but group responses with the same message. */
387 switch (response)
388 {
389 case 332: /* Need account for login. */
390 case 532: /* Need account for storing files. */
391 /* FIXME: implement a sane way to handle accounts. */
392 code = G_IO_ERROR_NOT_SUPPORTED;
393 msg = _("Accounts are unsupported");
394 break;
395 case 421: /* Service not available, closing control connection. */
396 code = G_IO_ERROR_FAILED;
397 msg = _("Host closed connection");
398 break;
399 case 425: /* Can't open data connection. */
400 code = G_IO_ERROR_CLOSED;
401 msg = _("Cannot open data connection. Maybe your firewall prevents this?");
402 break;
403 case 426: /* Connection closed; transfer aborted. */
404 code = G_IO_ERROR_CLOSED;
405 msg = _("Data connection closed");
406 break;
407 case 450: /* Requested file action not taken. File unavailable (e.g., file busy). */
408 case 550: /* Requested action not taken. File unavailable (e.g., file not found, no access). */
409 /* FIXME: This is a lot of different errors. So we have to pretend to
410 * be smart here. */
411 code = G_IO_ERROR_FAILED;
412 msg = _("Operation failed");
413 break;
414 case 451: /* Requested action aborted: local error in processing. */
415 code = G_IO_ERROR_FAILED;
416 msg = _("Operation failed");
417 break;
418 case 452: /* Requested action not taken. Insufficient storage space in system. */
419 case 552:
420 code = G_IO_ERROR_NO_SPACE;
421 msg = _("No space left on server");
422 break;
423 case 500: /* Syntax error, command unrecognized. */
424 case 501: /* Syntax error in parameters or arguments. */
425 case 502: /* Command not implemented. */
426 case 503: /* Bad sequence of commands. */
427 case 504: /* Command not implemented for that parameter. */
428 code = G_IO_ERROR_NOT_SUPPORTED;
429 msg = _("Operation not supported");
430 break;
431 case 522: /* EPRT: unsupported network protocol */
432 code = G_IO_ERROR_NOT_SUPPORTED;
433 msg = _("Unsupported network protocol");
434 break;
435 case 530: /* Not logged in. */
436 code = G_IO_ERROR_PERMISSION_DENIED;
437 msg = _("Permission denied");
438 break;
439 case 551: /* Requested action aborted: page type unknown. */
440 code = G_IO_ERROR_FAILED;
441 msg = _("Page type unknown");
442 break;
443 case 553: /* Requested action not taken. File name not allowed. */
444 code = G_IO_ERROR_INVALID_FILENAME;
445 msg = _("Invalid filename");
446 break;
447 default:
448 code = G_IO_ERROR_FAILED;
449 msg = _("Invalid reply");
450 break;
451 }
452
453 g_set_error_literal (&task->error, G_IO_ERROR, code, msg);
454 }
455
456 /**
457 * g_vfs_ftp_task_give_connection:
458 * @task: the task
459 * @conn: the connection that the @task should use
460 *
461 * Forces a given @task to do I/O using the given connection. The @task must
462 * not have a connection associated with itself. The @task will take
463 * ownership of @conn.
464 **/
465 void
g_vfs_ftp_task_give_connection(GVfsFtpTask * task,GVfsFtpConnection * conn)466 g_vfs_ftp_task_give_connection (GVfsFtpTask * task,
467 GVfsFtpConnection *conn)
468 {
469 g_return_if_fail (task != NULL);
470 g_return_if_fail (task->conn == NULL);
471
472 task->conn = conn;
473 /* this connection is not busy anymore */
474 g_mutex_lock (&task->backend->mutex);
475 g_assert (task->backend->busy_connections > 0);
476 task->backend->busy_connections--;
477 g_mutex_unlock (&task->backend->mutex);
478 }
479
480 /**
481 * g_vfs_ftp_task_take_connection:
482 * @task: the task
483 *
484 * Acquires the connection in use by the @task, so it can later be used with
485 * g_vfs_ftp_task_give_connection(). This or any other task will not use the
486 * connection anymore. The @task must have a connection in use.
487 *
488 * Returns: The connection that @task was using. You acquire ownership of
489 * the connection.
490 **/
491 GVfsFtpConnection *
g_vfs_ftp_task_take_connection(GVfsFtpTask * task)492 g_vfs_ftp_task_take_connection (GVfsFtpTask *task)
493 {
494 GVfsFtpConnection *conn;
495 GVfsBackendFtp *ftp;
496
497 g_return_val_if_fail (task != NULL, NULL);
498 g_return_val_if_fail (task->conn != NULL, NULL);
499
500 conn = task->conn;
501 task->conn = NULL;
502
503 ftp = task->backend;
504 /* mark this connection as busy */
505 g_mutex_lock (&ftp->mutex);
506 ftp->busy_connections++;
507 /* if all connections are busy, signal all waiting threads,
508 * so they stop waiting and return BUSY earlier */
509 if (ftp->busy_connections >= ftp->connections)
510 g_cond_broadcast (&ftp->cond);
511 g_mutex_unlock (&ftp->mutex);
512
513 return conn;
514 }
515
516 /**
517 * g_vfs_ftp_task_send:
518 * @task: the sending task
519 * @flags: response flags to use when sending
520 * @format: format string to construct command from
521 * (without trailing \r\n)
522 * @...: arguments to format string
523 *
524 * Shortcut to calling g_vfs_ftp_task_send_and_check() with the reply, funcs
525 * and data arguments set to %NULL. See that function for details.
526 *
527 * Returns: 0 on error or the received FTP code otherwise.
528 **/
529 guint
g_vfs_ftp_task_send(GVfsFtpTask * task,GVfsFtpResponseFlags flags,const char * format,...)530 g_vfs_ftp_task_send (GVfsFtpTask * task,
531 GVfsFtpResponseFlags flags,
532 const char * format,
533 ...)
534 {
535 va_list varargs;
536 guint response;
537
538 g_return_val_if_fail (task != NULL, 0);
539 g_return_val_if_fail (format != NULL, 0);
540
541 va_start (varargs, format);
542 response = g_vfs_ftp_task_sendv (task,
543 flags,
544 NULL,
545 format,
546 varargs);
547 va_end (varargs);
548 return response;
549 }
550
551 /**
552 * g_vfs_ftp_task_send_and_check:
553 * @task: the sending task
554 * @flags: response flags to use when sending
555 * @funcs: %NULL or %NULL-terminated array of functions used to determine the
556 * exact failure case upon a "550 Operation Failed" reply. This is
557 * often necessary
558 * @data: data to pass to @funcs.
559 * @reply: %NULL or pointer to take a char array containing the full reply of
560 * the ftp server upon successful reply. Use g_strfreev() to free
561 * after use.
562 * @format: format string to construct command from
563 * (without trailing \r\n)
564 * @...: arguments to format string
565 *
566 * Takes an ftp command in printf-style @format, potentially acquires a
567 * connection automatically, sends the command and waits for an answer from
568 * the ftp server. Without any @flags, FTP response codes other than 2xx cause
569 * an error. If @reply is not %NULL, the full reply will be put into a
570 * %NULL-terminated string array that must be freed with g_strfreev() after
571 * use.
572 * If @funcs is set, the 550 response code will cause all of these functions to
573 * be called in order passing them the @task and @data arguments given to this
574 * function until one of them sets an error on @task. This error will then be
575 * returned from this function. If none of those functions sets an error, the
576 * generic error for the 550 response will be used.
577 * If an error has been set on @task previously, this function will do nothing.
578 *
579 * Returns: 0 on error or the received FTP code otherwise.
580 **/
581 guint
g_vfs_ftp_task_send_and_check(GVfsFtpTask * task,GVfsFtpResponseFlags flags,const GVfsFtpErrorFunc * funcs,gpointer data,char *** reply,const char * format,...)582 g_vfs_ftp_task_send_and_check (GVfsFtpTask * task,
583 GVfsFtpResponseFlags flags,
584 const GVfsFtpErrorFunc *funcs,
585 gpointer data,
586 char *** reply,
587 const char * format,
588 ...)
589 {
590 va_list varargs;
591 guint response;
592
593 g_return_val_if_fail (task != NULL, 0);
594 g_return_val_if_fail (format != NULL, 0);
595 g_return_val_if_fail (funcs == NULL || funcs[0] != NULL, 0);
596
597 if (funcs)
598 {
599 g_return_val_if_fail ((flags & G_VFS_FTP_PASS_550) == 0, 0);
600 flags |= G_VFS_FTP_PASS_550;
601 }
602
603 va_start (varargs, format);
604 response = g_vfs_ftp_task_sendv (task,
605 flags,
606 reply,
607 format,
608 varargs);
609 va_end (varargs);
610
611 if (response == 550 && funcs)
612 {
613 /* close a potentially open data connection, the error handlers
614 * might try to open new ones and that would cause assertions */
615 g_vfs_ftp_task_close_data_connection (task);
616
617 while (*funcs && !g_vfs_ftp_task_is_in_error (task))
618 {
619 (*funcs) (task, data);
620 funcs++;
621 }
622 if (!g_vfs_ftp_task_is_in_error (task))
623 g_vfs_ftp_task_set_error_from_response (task, response);
624 response = 0;
625 }
626
627 return response;
628 }
629
630 /**
631 * g_vfs_ftp_task_sendv:
632 * @task: the sending task
633 * @flags: response flags to use when receiving the reply
634 * @reply: %NULL or pointer to char array that takes the full reply from the
635 * server
636 * @format: format string to construct command from
637 * (without trailing \r\n)
638 * @varargs: arguments to format string
639 *
640 * This is the varargs version of g_vfs_ftp_task_send(). See that function
641 * for details.
642 *
643 * Returns: the received FTP code or 0 on error.
644 **/
645 guint
g_vfs_ftp_task_sendv(GVfsFtpTask * task,GVfsFtpResponseFlags flags,char *** reply,const char * format,va_list varargs)646 g_vfs_ftp_task_sendv (GVfsFtpTask * task,
647 GVfsFtpResponseFlags flags,
648 char *** reply,
649 const char * format,
650 va_list varargs)
651 {
652 GString *command;
653 gboolean retry_on_timeout = FALSE;
654 guint response;
655
656 if (g_vfs_ftp_task_is_in_error (task))
657 return 0;
658
659 command = g_string_new ("");
660 g_string_append_vprintf (command, format, varargs);
661 g_string_append (command, "\r\n");
662
663 retry:
664 if (task->conn == NULL)
665 {
666 if (!g_vfs_ftp_task_acquire_connection (task))
667 {
668 g_string_free (command, TRUE);
669 return 0;
670 }
671 retry_on_timeout = TRUE;
672 }
673
674 g_vfs_ftp_connection_send (task->conn,
675 command->str,
676 command->len,
677 task->cancellable,
678 &task->error);
679
680 response = g_vfs_ftp_task_receive (task, flags, reply);
681
682 /* NB: requires adaption if we allow passing 4xx responses */
683 if (retry_on_timeout &&
684 g_vfs_ftp_task_is_in_error (task) &&
685 !g_vfs_ftp_connection_is_usable (task->conn))
686 {
687 g_vfs_ftp_task_clear_error (task);
688 g_vfs_ftp_task_release_connection (task);
689 goto retry;
690 }
691
692 g_string_free (command, TRUE);
693 return response;
694 }
695
696 /**
697 * g_vfs_ftp_task_receive:
698 * @task: the receiving task
699 * @flags: response flags to use
700 * @reply: %NULL or pointer to char array that takes the full reply from the
701 * server
702 *
703 * Unless @task is in an error state, this function receives a reply from
704 * the @task's connection. The @task must have a connection set, which will
705 * happen when either g_vfs_ftp_task_send() or
706 * g_vfs_ftp_task_give_connection() have been called on the @task before.
707 * Unless @flags are given, all reply codes not in the 200s cause an error.
708 * If @task is in an error state when calling this function, nothing will
709 * happen and the function will just return.
710 *
711 * Returns: the received FTP code or 0 on error.
712 **/
713 guint
g_vfs_ftp_task_receive(GVfsFtpTask * task,GVfsFtpResponseFlags flags,char *** reply)714 g_vfs_ftp_task_receive (GVfsFtpTask * task,
715 GVfsFtpResponseFlags flags,
716 char *** reply)
717 {
718 guint response;
719
720 g_return_val_if_fail (task != NULL, 0);
721 if (g_vfs_ftp_task_is_in_error (task))
722 return 0;
723 g_return_val_if_fail (task->conn != NULL, 0);
724
725 response = g_vfs_ftp_connection_receive (task->conn,
726 reply,
727 task->cancellable,
728 &task->error);
729
730 switch (G_VFS_FTP_RESPONSE_GROUP (response))
731 {
732 case 0:
733 return 0;
734 case 1:
735 if (flags & G_VFS_FTP_PASS_100)
736 break;
737 g_vfs_ftp_task_set_error_from_response (task, response);
738 break;
739 case 2:
740 if (flags & G_VFS_FTP_FAIL_200)
741 g_vfs_ftp_task_set_error_from_response (task, response);
742 break;
743 case 3:
744 if (flags & G_VFS_FTP_PASS_300)
745 break;
746 g_vfs_ftp_task_set_error_from_response (task, response);
747 break;
748 case 4:
749 g_vfs_ftp_task_set_error_from_response (task, response);
750 break;
751 case 5:
752 if ((flags & G_VFS_FTP_PASS_500) ||
753 (response == 550 && (flags & G_VFS_FTP_PASS_550)))
754 break;
755 g_vfs_ftp_task_set_error_from_response (task, response);
756 break;
757 default:
758 g_assert_not_reached ();
759 break;
760 }
761
762 if (g_vfs_ftp_task_is_in_error (task))
763 {
764 if (response != 0 && reply)
765 {
766 g_strfreev (*reply);
767 *reply = NULL;
768 }
769 response = 0;
770 }
771
772 return response;
773 }
774
775 /**
776 * g_vfs_ftp_task_close_data_connection:
777 * @task: a task potentially having an open data connection
778 *
779 * Closes any data connection @task might have opened.
780 */
781 void
g_vfs_ftp_task_close_data_connection(GVfsFtpTask * task)782 g_vfs_ftp_task_close_data_connection (GVfsFtpTask *task)
783 {
784 g_return_if_fail (task != NULL);
785
786 if (task->conn == NULL)
787 return;
788
789 g_vfs_ftp_connection_close_data_connection (task->conn);
790 }
791
792 static GSocketAddress *
g_vfs_ftp_task_create_remote_address(GVfsFtpTask * task,guint port)793 g_vfs_ftp_task_create_remote_address (GVfsFtpTask *task, guint port)
794 {
795 GSocketAddress *old, *new;
796
797 old = g_vfs_ftp_connection_get_address (task->conn, &task->error);
798 if (old == NULL)
799 return NULL;
800 g_assert (G_IS_INET_SOCKET_ADDRESS (old));
801 new = g_inet_socket_address_new (g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (old)), port);
802
803 return new;
804 }
805
806 static GVfsFtpMethod
g_vfs_ftp_task_setup_data_connection_epsv(GVfsFtpTask * task,GVfsFtpMethod method)807 g_vfs_ftp_task_setup_data_connection_epsv (GVfsFtpTask *task, GVfsFtpMethod method)
808 {
809 const char *s;
810 char **reply;
811 guint port;
812 GSocketAddress *addr;
813 guint status;
814 gboolean success;
815
816 g_assert (task->error == NULL);
817
818 status = g_vfs_ftp_task_send_and_check (task, G_VFS_FTP_PASS_500, NULL, NULL, &reply, "EPSV");
819 if (G_VFS_FTP_RESPONSE_GROUP (status) != 2)
820 return G_VFS_FTP_METHOD_ANY;
821
822 /* FIXME: parse multiple lines? */
823 s = strrchr (reply[0], '(');
824 if (!s)
825 goto fail;
826
827 s += 4;
828 port = strtoul (s, NULL, 10);
829 if (port == 0)
830 goto fail;
831
832 g_strfreev (reply);
833 addr = g_vfs_ftp_task_create_remote_address (task, port);
834 if (addr == NULL)
835 return G_VFS_FTP_METHOD_ANY;
836
837 success = g_vfs_ftp_connection_open_data_connection (task->conn,
838 addr,
839 task->cancellable,
840 &task->error);
841 g_object_unref (addr);
842 return success ? G_VFS_FTP_METHOD_EPSV : G_VFS_FTP_METHOD_ANY;
843
844 fail:
845 g_strfreev (reply);
846 return G_VFS_FTP_METHOD_ANY;
847 }
848
849 static GVfsFtpMethod
g_vfs_ftp_task_setup_data_connection_pasv(GVfsFtpTask * task,GVfsFtpMethod method)850 g_vfs_ftp_task_setup_data_connection_pasv (GVfsFtpTask *task, GVfsFtpMethod method)
851 {
852 guint ip1, ip2, ip3, ip4, port1, port2;
853 char **reply;
854 const char *s;
855 GSocketAddress *addr;
856 guint status;
857 gboolean success;
858
859 status = g_vfs_ftp_task_send_and_check (task, 0, NULL, NULL, &reply, "PASV");
860 if (status == 0)
861 return G_VFS_FTP_METHOD_ANY;
862
863 /* parse response and try to find the address to connect to.
864 * This code does the same as curl.
865 */
866 for (s = reply[0]; *s; s++)
867 {
868 if (sscanf (s, "%u,%u,%u,%u,%u,%u",
869 &ip1, &ip2, &ip3, &ip4,
870 &port1, &port2) == 6)
871 break;
872 }
873 if (*s == 0)
874 {
875 g_strfreev (reply);
876 g_set_error_literal (&task->error, G_IO_ERROR, G_IO_ERROR_FAILED,
877 _("Invalid reply"));
878 return G_VFS_FTP_METHOD_ANY;
879 }
880 g_strfreev (reply);
881
882 if (method == G_VFS_FTP_METHOD_PASV || method == G_VFS_FTP_METHOD_ANY)
883 {
884 guint8 ip[4];
885 GInetAddress *inet_addr;
886
887 ip[0] = ip1;
888 ip[1] = ip2;
889 ip[2] = ip3;
890 ip[3] = ip4;
891 inet_addr = g_inet_address_new_from_bytes (ip, G_SOCKET_FAMILY_IPV4);
892 addr = g_inet_socket_address_new (inet_addr, port1 << 8 | port2);
893 g_object_unref (inet_addr);
894
895 success = g_vfs_ftp_connection_open_data_connection (task->conn,
896 addr,
897 task->cancellable,
898 &task->error);
899 g_object_unref (addr);
900 if (success)
901 return G_VFS_FTP_METHOD_PASV;
902 if (g_vfs_ftp_task_is_in_error (task) && method != G_VFS_FTP_METHOD_ANY)
903 return G_VFS_FTP_METHOD_ANY;
904
905 g_vfs_ftp_task_clear_error (task);
906 }
907
908 if (method == G_VFS_FTP_METHOD_PASV_ADDR || method == G_VFS_FTP_METHOD_ANY)
909 {
910 /* Workaround code:
911 * Various ftp servers aren't setup correctly when behind a NAT. They report
912 * their own IP address (like 10.0.0.4) and not the address in front of the
913 * NAT. But this is likely the same address that we connected to with our
914 * command connetion. So if the address given by PASV fails, we fall back
915 * to the address of the command stream.
916 */
917 addr = g_vfs_ftp_task_create_remote_address (task, port1 << 8 | port2);
918 if (addr == NULL)
919 return G_VFS_FTP_METHOD_ANY;
920 success = g_vfs_ftp_connection_open_data_connection (task->conn,
921 addr,
922 task->cancellable,
923 &task->error);
924 g_object_unref (addr);
925 if (success)
926 return G_VFS_FTP_METHOD_PASV_ADDR;
927 }
928
929 return G_VFS_FTP_METHOD_ANY;
930 }
931
932 static GVfsFtpMethod
g_vfs_ftp_task_setup_data_connection_eprt(GVfsFtpTask * task,GVfsFtpMethod unused)933 g_vfs_ftp_task_setup_data_connection_eprt (GVfsFtpTask *task, GVfsFtpMethod unused)
934 {
935 GSocketAddress *addr;
936 guint status, port, family;
937 char *ip_string;
938
939 /* workaround for the task not having a connection yet */
940 if (task->conn == NULL &&
941 g_vfs_ftp_task_send (task, 0, "NOOP") == 0)
942 return G_VFS_FTP_METHOD_ANY;
943
944 addr = g_vfs_ftp_connection_listen_data_connection (task->conn, &task->error);
945 if (addr == NULL)
946 return G_VFS_FTP_METHOD_ANY;
947 switch (g_socket_address_get_family (addr))
948 {
949 case G_SOCKET_FAMILY_IPV4:
950 family = 1;
951 break;
952 case G_SOCKET_FAMILY_IPV6:
953 family = 2;
954 break;
955 default:
956 g_object_unref (addr);
957 return G_VFS_FTP_METHOD_ANY;
958 }
959
960 ip_string = g_inet_address_to_string (g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (addr)));
961 /* if this ever happens (and it must not for IP4 and IP6 addresses),
962 * we need to add support for using a different separator */
963 g_assert (strchr (ip_string, '|') == NULL);
964 port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr));
965
966 /* we could handle the 522 response here, (unsupported network family),
967 * but I don't think that will buy us anything */
968 status = g_vfs_ftp_task_send (task, 0, "EPRT |%u|%s|%u|", family, ip_string, port);
969 g_free (ip_string);
970 g_object_unref (addr);
971 if (status == 0)
972 return G_VFS_FTP_METHOD_ANY;
973
974 return G_VFS_FTP_METHOD_EPRT;
975 }
976
977 static GVfsFtpMethod
g_vfs_ftp_task_setup_data_connection_port(GVfsFtpTask * task,GVfsFtpMethod unused)978 g_vfs_ftp_task_setup_data_connection_port (GVfsFtpTask *task, GVfsFtpMethod unused)
979 {
980 GSocketAddress *addr;
981 guint status, i, port;
982 char *ip_string;
983
984 /* workaround for the task not having a connection yet */
985 if (task->conn == NULL &&
986 g_vfs_ftp_task_send (task, 0, "NOOP") == 0)
987 return G_VFS_FTP_METHOD_ANY;
988
989 addr = g_vfs_ftp_connection_listen_data_connection (task->conn, &task->error);
990 if (addr == NULL)
991 return G_VFS_FTP_METHOD_ANY;
992 /* the PORT command only supports IPv4 */
993 if (g_socket_address_get_family (addr) != G_SOCKET_FAMILY_IPV4)
994 {
995 g_object_unref (addr);
996 return G_VFS_FTP_METHOD_ANY;
997 }
998
999 ip_string = g_inet_address_to_string (g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (addr)));
1000 for (i = 0; ip_string[i]; i++)
1001 {
1002 if (ip_string[i] == '.')
1003 ip_string[i] = ',';
1004 }
1005 port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr));
1006
1007 status = g_vfs_ftp_task_send (task, 0, "PORT %s,%u,%u", ip_string, port >> 8, port & 0xFF);
1008 g_free (ip_string);
1009 g_object_unref (addr);
1010 if (status == 0)
1011 return G_VFS_FTP_METHOD_ANY;
1012
1013 return G_VFS_FTP_METHOD_PORT;
1014 }
1015
1016 typedef GVfsFtpMethod (* GVfsFtpOpenDataConnectionFunc) (GVfsFtpTask *task, GVfsFtpMethod method);
1017 typedef struct _GVfsFtpOpenDataConnectionMethod GVfsFtpOpenDataConnectionMethod;
1018 struct _GVfsFtpOpenDataConnectionMethod {
1019 GVfsFtpFeature required_feature;
1020 GSocketFamily required_family;
1021 GVfsFtpOpenDataConnectionFunc func;
1022 };
1023
1024 static gboolean
g_vfs_ftp_task_open_data_connection_method_is_supported(const GVfsFtpOpenDataConnectionMethod * method,GVfsFtpTask * task,GSocketFamily family)1025 g_vfs_ftp_task_open_data_connection_method_is_supported (const GVfsFtpOpenDataConnectionMethod *method,
1026 GVfsFtpTask * task,
1027 GSocketFamily family)
1028 {
1029 if (method->required_feature &&
1030 !g_vfs_backend_ftp_has_feature (task->backend, method->required_feature))
1031 return FALSE;
1032
1033 if (method->required_family != G_SOCKET_FAMILY_INVALID &&
1034 method->required_family != family)
1035 return FALSE;
1036
1037 return TRUE;
1038 }
1039
1040 static GSocketFamily
g_vfs_ftp_task_get_socket_family(GVfsFtpTask * task)1041 g_vfs_ftp_task_get_socket_family (GVfsFtpTask *task)
1042 {
1043 GSocketAddress *addr;
1044 GSocketFamily family;
1045
1046 /* workaround for the task not having a connection yet */
1047 if (task->conn == NULL &&
1048 g_vfs_ftp_task_send (task, 0, "NOOP") == 0)
1049 {
1050 g_vfs_ftp_task_clear_error (task);
1051 return G_SOCKET_FAMILY_INVALID;
1052 }
1053
1054 addr = g_vfs_ftp_connection_get_address (task->conn, NULL);
1055 if (addr == NULL)
1056 return G_SOCKET_FAMILY_INVALID;
1057
1058 family = g_socket_address_get_family (addr);
1059 g_object_unref (addr);
1060 return family;
1061 }
1062
1063 static GVfsFtpMethod
g_vfs_ftp_task_setup_data_connection_any(GVfsFtpTask * task,GVfsFtpMethod unused)1064 g_vfs_ftp_task_setup_data_connection_any (GVfsFtpTask *task, GVfsFtpMethod unused)
1065 {
1066 static const GVfsFtpOpenDataConnectionMethod funcs_ordered[] = {
1067 { 0, G_SOCKET_FAMILY_IPV4, g_vfs_ftp_task_setup_data_connection_pasv },
1068 { G_VFS_FTP_FEATURE_EPSV, G_SOCKET_FAMILY_INVALID, g_vfs_ftp_task_setup_data_connection_epsv },
1069 { 0, G_SOCKET_FAMILY_IPV4, g_vfs_ftp_task_setup_data_connection_port },
1070 { G_VFS_FTP_FEATURE_EPRT, G_SOCKET_FAMILY_INVALID, g_vfs_ftp_task_setup_data_connection_eprt }
1071 };
1072 GVfsFtpMethod method;
1073 GSocketFamily family;
1074 guint i;
1075
1076 family = g_vfs_ftp_task_get_socket_family (task);
1077
1078 /* first try all advertised features */
1079 for (i = 0; i < G_N_ELEMENTS (funcs_ordered); i++)
1080 {
1081 if (!g_vfs_ftp_task_open_data_connection_method_is_supported (&funcs_ordered[i], task, family))
1082 continue;
1083 method = funcs_ordered[i].func (task, G_VFS_FTP_METHOD_ANY);
1084 if (method != G_VFS_FTP_METHOD_ANY)
1085 return method;
1086
1087 g_vfs_ftp_task_clear_error (task);
1088 }
1089
1090 /* then try if the non-advertised features work */
1091 for (i = 0; i < G_N_ELEMENTS (funcs_ordered); i++)
1092 {
1093 if (g_vfs_ftp_task_open_data_connection_method_is_supported (&funcs_ordered[i], task, family))
1094 continue;
1095 method = funcs_ordered[i].func (task, G_VFS_FTP_METHOD_ANY);
1096 if (method != G_VFS_FTP_METHOD_ANY)
1097 return method;
1098
1099 g_vfs_ftp_task_clear_error (task);
1100 }
1101
1102 /* finally, just give up */
1103 return G_VFS_FTP_METHOD_ANY;
1104 }
1105
1106 /**
1107 * g_vfs_ftp_task_setup_data_connection:
1108 * @task: a task not having an open data connection
1109 *
1110 * Sets up a data connection to the ftp server with using the best method for
1111 * this task. If the operation fails, @task will be set into an error state.
1112 * You must call g_vfs_ftp_task_open_data_connection() to finish setup and
1113 * ensure the data connection actually gets opened. Usually, this requires
1114 * sending an FTP command down the stream.
1115 **/
1116 void
g_vfs_ftp_task_setup_data_connection(GVfsFtpTask * task)1117 g_vfs_ftp_task_setup_data_connection (GVfsFtpTask *task)
1118 {
1119 static const GVfsFtpOpenDataConnectionFunc connect_funcs[] = {
1120 [G_VFS_FTP_METHOD_ANY] = g_vfs_ftp_task_setup_data_connection_any,
1121 [G_VFS_FTP_METHOD_EPSV] = g_vfs_ftp_task_setup_data_connection_epsv,
1122 [G_VFS_FTP_METHOD_PASV] = g_vfs_ftp_task_setup_data_connection_pasv,
1123 [G_VFS_FTP_METHOD_PASV_ADDR] = g_vfs_ftp_task_setup_data_connection_pasv,
1124 [G_VFS_FTP_METHOD_EPRT] = g_vfs_ftp_task_setup_data_connection_eprt,
1125 [G_VFS_FTP_METHOD_PORT] = g_vfs_ftp_task_setup_data_connection_port
1126 };
1127 GVfsFtpMethod method, result;
1128
1129 g_return_if_fail (task != NULL);
1130
1131 task->method = G_VFS_FTP_METHOD_ANY;
1132
1133 method = g_atomic_int_get (&task->backend->method);
1134 g_assert (method < G_N_ELEMENTS (connect_funcs) && connect_funcs[method]);
1135
1136 if (g_vfs_ftp_task_is_in_error (task))
1137 return;
1138
1139 result = connect_funcs[method] (task, method);
1140
1141 /* be sure to try all possibilities if one failed */
1142 if (result == G_VFS_FTP_METHOD_ANY &&
1143 method != G_VFS_FTP_METHOD_ANY &&
1144 !g_vfs_ftp_task_is_in_error (task))
1145 result = g_vfs_ftp_task_setup_data_connection_any (task, G_VFS_FTP_METHOD_ANY);
1146
1147 g_assert (result < G_N_ELEMENTS (connect_funcs) && connect_funcs[result]);
1148 if (result != method)
1149 {
1150 static const char *methods[] = {
1151 [G_VFS_FTP_METHOD_ANY] = "any",
1152 [G_VFS_FTP_METHOD_EPSV] = "EPSV",
1153 [G_VFS_FTP_METHOD_PASV] = "PASV",
1154 [G_VFS_FTP_METHOD_PASV_ADDR] = "PASV with workaround",
1155 [G_VFS_FTP_METHOD_EPRT] = "EPRT",
1156 [G_VFS_FTP_METHOD_PORT] = "PORT"
1157 };
1158 g_atomic_int_set (&task->backend->method, result);
1159 g_debug ("# set default data connection method from %s to %s\n",
1160 methods[method], methods[result]);
1161 }
1162 task->method = result;
1163 }
1164
1165 /**
1166 * g_vfs_ftp_task_open_data_connection:
1167 * @task: a task
1168 *
1169 * Tries to open a data connection to the ftp server and secures it if
1170 * necessary. If the operation fails, @task will be set into an error state.
1171 **/
1172 void
g_vfs_ftp_task_open_data_connection(GVfsFtpTask * task)1173 g_vfs_ftp_task_open_data_connection (GVfsFtpTask *task)
1174 {
1175 g_return_if_fail (task != NULL);
1176
1177 if (g_vfs_ftp_task_is_in_error (task))
1178 return;
1179
1180 if (task->method == G_VFS_FTP_METHOD_EPRT ||
1181 task->method == G_VFS_FTP_METHOD_PORT)
1182 g_vfs_ftp_connection_accept_data_connection (task->conn,
1183 task->cancellable,
1184 &task->error);
1185
1186 if (g_vfs_ftp_task_is_in_error (task))
1187 return;
1188
1189 if (task->backend->tls_mode != G_VFS_FTP_TLS_MODE_NONE)
1190 g_vfs_ftp_connection_data_connection_enable_tls (task->conn,
1191 task->backend->server_identity,
1192 reconnect_certificate_cb,
1193 task->backend,
1194 task->cancellable,
1195 &task->error);
1196 }
1197
1198 /**
1199 * g_vfs_ftp_task_initial_handshake:
1200 * @task: a task
1201 * @cb: callback called if there's a verification error
1202 * @user_data: user data passed to @cb
1203 *
1204 * Performs the initial handshake with an FTP server, including reading the
1205 * greeting and activating TLS. If the operation fails, @task will be set into
1206 * an error state.
1207 **/
1208 gboolean
g_vfs_ftp_task_initial_handshake(GVfsFtpTask * task,CertificateCallback cb,gpointer user_data)1209 g_vfs_ftp_task_initial_handshake (GVfsFtpTask *task,
1210 CertificateCallback cb,
1211 gpointer user_data)
1212 {
1213 if (g_vfs_ftp_task_is_in_error (task))
1214 return FALSE;
1215
1216 /* In implicit mode, we do the TLS handshake first, then receive the FTP
1217 * greeting. Explicit mode is practically the opposite.
1218 */
1219
1220 if (task->backend->tls_mode == G_VFS_FTP_TLS_MODE_IMPLICIT)
1221 {
1222 if (!g_vfs_ftp_connection_enable_tls (task->conn,
1223 task->backend->server_identity,
1224 TRUE,
1225 cb,
1226 user_data,
1227 task->cancellable,
1228 &task->error))
1229 return FALSE;
1230
1231 if (!g_vfs_ftp_task_receive (task, 0, NULL))
1232 return FALSE;
1233 }
1234 else if (task->backend->tls_mode == G_VFS_FTP_TLS_MODE_EXPLICIT)
1235 {
1236 if (!g_vfs_ftp_task_receive (task, 0, NULL))
1237 return FALSE;
1238
1239 if (!g_vfs_ftp_task_send (task, 0, "AUTH TLS"))
1240 return FALSE;
1241
1242 if (!g_vfs_ftp_connection_enable_tls (task->conn,
1243 task->backend->server_identity,
1244 FALSE,
1245 cb,
1246 user_data,
1247 task->cancellable,
1248 &task->error))
1249 return FALSE;
1250 }
1251 else
1252 {
1253 if (!g_vfs_ftp_task_receive (task, 0, NULL))
1254 return FALSE;
1255 }
1256
1257 /* Request TLS also for the data channels. */
1258
1259 if (task->backend->tls_mode != G_VFS_FTP_TLS_MODE_NONE)
1260 {
1261 if (!g_vfs_ftp_task_send (task, 0, "PBSZ 0"))
1262 return FALSE;
1263
1264 if (!g_vfs_ftp_task_send (task, 0, "PROT P"))
1265 return FALSE;
1266 }
1267
1268 return TRUE;
1269 }
1270