/* GIO - GLib Input, Output and Streaming Library * * Copyright (C) Carl-Anton Ingmarsson 2011 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * Author: Carl-Anton Ingmarsson */ #include #include #include #include #ifdef HAVE_GCRYPT #include #endif #include "gvfskeyring.h" #include "gvfsafpvolume.h" #include "gvfsafpserver.h" struct _GvfsAfpServerPrivate { GNetworkAddress *addr; GVfsAfpConnection *conn; GVfsAfpServerInfo info; gint32 time_diff; guint32 user_id; guint32 group_id; guint64 uuid; }; G_DEFINE_TYPE_WITH_PRIVATE (GVfsAfpServer, g_vfs_afp_server, G_TYPE_OBJECT); #define AFP_UAM_NO_USER "No User Authent" #define AFP_UAM_DHX "DHCAST128" #define AFP_UAM_DHX2 "DHX2" GVfsAfpServer * g_vfs_afp_server_new (GNetworkAddress *addr) { GVfsAfpServer *server; server = g_object_new (G_VFS_TYPE_AFP_SERVER, NULL); server->priv->addr = addr; return server; } static void g_vfs_afp_server_init (GVfsAfpServer *server) { GVfsAfpServerPrivate *priv; server->priv = priv = g_vfs_afp_server_get_instance_private (server); priv->info.machine_type = NULL; priv->info.server_name = NULL; priv->info.utf8_server_name = NULL; priv->info.uams = NULL; priv->info.version = AFP_VERSION_INVALID; } static void g_vfs_afp_server_finalize (GObject *object) { GVfsAfpServer *server = G_VFS_AFP_SERVER (object); GVfsAfpServerPrivate *priv = server->priv; g_clear_object (&priv->addr); g_clear_object (&priv->conn); g_free (priv->info.machine_type); g_free (priv->info.server_name); g_free (priv->info.utf8_server_name); g_slist_free_full (priv->info.uams, g_free); G_OBJECT_CLASS (g_vfs_afp_server_parent_class)->finalize (object); } static void g_vfs_afp_server_class_init (GVfsAfpServerClass *klass) { GObjectClass* object_class = G_OBJECT_CLASS (klass); object_class->finalize = g_vfs_afp_server_finalize; } static const char * afp_version_to_string (AfpVersion afp_version) { const char *version_strings[] = { "AFPX03", "AFP3.1", "AFP3.2", "AFP3.3" }; return version_strings[afp_version - 1]; } static AfpVersion string_to_afp_version (const char *str) { gint i; const char *version_strings[] = { "AFPX03", "AFP3.1", "AFP3.2", "AFP3.3" }; for (i = 0; i < G_N_ELEMENTS (version_strings); i++) { if (g_str_equal (str, version_strings[i])) return i + 1; } return AFP_VERSION_INVALID; } #ifdef HAVE_GCRYPT static gboolean dhx2_login (GVfsAfpServer *server, const char *username, const char *password, GCancellable *cancellable, GError **error) { GVfsAfpServerPrivate *priv = server->priv; gboolean res; gcry_error_t gcry_err; GVfsAfpCommand *comm; GVfsAfpReply *reply; AfpResultCode res_code; guint8 C2SIV[] = { 0x4c, 0x57, 0x61, 0x6c, 0x6c, 0x61, 0x63, 0x65 }; guint8 S2CIV[] = { 0x43, 0x4a, 0x61, 0x6c, 0x62, 0x65, 0x72, 0x74 }; /* reply 1 */ guint16 id; guint16 len; guint32 bits; guint8 *tmp_buf, *buf; gcry_mpi_t g, p, Ma, Mb, Ra, key; gcry_cipher_hd_t cipher; gcry_mpi_t clientNonce; guint8 clientNonce_buf[16]; guint8 key_md5_buf[16]; /* reply 2 */ guint8 reply2_buf[32]; gcry_mpi_t clientNonce1, serverNonce; /* request 3 */ guint8 answer_buf[272] = {0}; size_t nonce_len; /* initialize for easy cleanup */ g = NULL, p = NULL; Ma = NULL; Mb = NULL; Ra = NULL; key = NULL; clientNonce = NULL; clientNonce1 = NULL; serverNonce = NULL; buf = NULL; /* setup cipher */ gcry_err = gcry_cipher_open (&cipher, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0); g_assert (gcry_err == 0); if (strlen (password) > 256) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, /* Translators: %d is a constant, currently hardcoded to 256 */ ngettext ("The server doesn’t support passwords longer than %d character.", "The server doesn’t support passwords longer than %d characters.", 256), 256); goto error; } /* Request 1 */ comm = g_vfs_afp_command_new (AFP_COMMAND_LOGIN); g_vfs_afp_command_put_pascal (comm, afp_version_to_string (priv->info.version)); g_vfs_afp_command_put_pascal (comm, AFP_UAM_DHX2); g_vfs_afp_command_put_pascal (comm, username); g_vfs_afp_command_pad_to_even (comm); reply = g_vfs_afp_connection_send_command_sync (priv->conn, comm, cancellable, error); g_object_unref (comm); if (!reply) goto error; res_code = g_vfs_afp_reply_get_result_code (reply); if (res_code != AFP_RESULT_AUTH_CONTINUE) { g_object_unref (reply); if (res_code == AFP_RESULT_USER_NOT_AUTH) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("An invalid username was provided.")); goto error; } else goto generic_error; } /* Get data from reply */ REPLY_READ_UINT16 (reply, &id); /* read g */ REPLY_GET_DATA (reply, 4, &tmp_buf); gcry_err = gcry_mpi_scan (&g, GCRYMPI_FMT_USG, tmp_buf, 4, NULL); g_assert (gcry_err == 0); REPLY_READ_UINT16 (reply, &len); bits = len * 8; /* read p */ REPLY_GET_DATA (reply, len, &tmp_buf); gcry_err = gcry_mpi_scan (&p, GCRYMPI_FMT_USG, tmp_buf, len, NULL); g_assert (gcry_err == 0); /* read Mb */ REPLY_GET_DATA (reply, len, &tmp_buf); gcry_err = gcry_mpi_scan (&Mb, GCRYMPI_FMT_USG, tmp_buf, len, NULL); g_assert (gcry_err == 0); g_object_unref (reply); /* generate random number Ra != 0 */ Ra = gcry_mpi_new (bits); while (gcry_mpi_cmp_ui (Ra, 0) == 0) gcry_mpi_randomize (Ra, bits, GCRY_STRONG_RANDOM); /* Secret key value must be less than half of prime */ if (gcry_mpi_get_nbits (Ra) > bits - 1) gcry_mpi_clear_highbit (Ra, bits - 1); /* generate Ma */ Ma = gcry_mpi_new (bits); gcry_mpi_powm (Ma, g, Ra, p); /* derive Key */ key = gcry_mpi_new (bits); gcry_mpi_powm (key, Mb, Ra, p); buf = g_malloc0 (len); gcry_err = gcry_mpi_print (GCRYMPI_FMT_USG, buf, len, NULL, key); g_assert (gcry_err == 0); gcry_md_hash_buffer (GCRY_MD_MD5, key_md5_buf, buf, len); /* generate random clientNonce != 0 */ clientNonce = gcry_mpi_new (128); while (gcry_mpi_cmp_ui (clientNonce, 0) == 0) gcry_mpi_randomize (clientNonce, 128, GCRY_STRONG_RANDOM); gcry_err = gcry_mpi_print (GCRYMPI_FMT_USG, clientNonce_buf, 16, &nonce_len, clientNonce); g_assert (gcry_err == 0); if (nonce_len < 16) { memmove(clientNonce_buf + 16 - nonce_len, clientNonce_buf, nonce_len); memset(clientNonce_buf, 0, 16 - nonce_len); } gcry_cipher_setiv (cipher, C2SIV, G_N_ELEMENTS (C2SIV)); gcry_cipher_setkey (cipher, key_md5_buf, 16); gcry_err = gcry_cipher_encrypt (cipher, clientNonce_buf, 16, NULL, 0); g_assert (gcry_err == 0); /* Create Request 2 */ comm = g_vfs_afp_command_new (AFP_COMMAND_LOGIN_CONT); /* pad byte */ g_data_output_stream_put_byte (G_DATA_OUTPUT_STREAM (comm), 0, NULL, NULL); /* Id */ g_data_output_stream_put_uint16 (G_DATA_OUTPUT_STREAM (comm), id, NULL, NULL); /* Ma */ memset (buf, 0, len); gcry_err = gcry_mpi_print (GCRYMPI_FMT_USG, buf, len, NULL, Ma); g_assert (gcry_err == 0); g_output_stream_write_all (G_OUTPUT_STREAM (comm), buf, len, NULL, NULL, NULL); /* clientNonce */ g_output_stream_write_all (G_OUTPUT_STREAM (comm), clientNonce_buf, 16, NULL, NULL, NULL); reply = g_vfs_afp_connection_send_command_sync (priv->conn, comm, cancellable, error); g_object_unref (comm); if (!reply) goto error; res_code = g_vfs_afp_reply_get_result_code (reply); if (res_code != AFP_RESULT_AUTH_CONTINUE) { g_object_unref (reply); goto generic_error; } /* read data from reply 2 */ REPLY_READ_UINT16 (reply, &id); REPLY_GET_DATA (reply, 32, &tmp_buf); memcpy (reply2_buf, tmp_buf, 32); g_object_unref (reply); /* decrypt */ gcry_cipher_setiv (cipher, S2CIV, G_N_ELEMENTS (S2CIV)); gcry_err = gcry_cipher_decrypt (cipher, reply2_buf, 32, NULL, 0); g_assert (gcry_err == 0); /* check clientNonce + 1 */ gcry_err = gcry_mpi_scan (&clientNonce1, GCRYMPI_FMT_USG, reply2_buf, 16, NULL); g_assert (gcry_err == 0); gcry_mpi_add_ui (clientNonce, clientNonce, 1); if (gcry_mpi_cmp (clientNonce, clientNonce1) != 0) goto generic_error; gcry_err = gcry_mpi_scan (&serverNonce, GCRYMPI_FMT_USG, reply2_buf + 16, 16, NULL); g_assert (gcry_err == 0); gcry_mpi_add_ui (serverNonce, serverNonce, 1); /* create encrypted answer */ gcry_err = gcry_mpi_print (GCRYMPI_FMT_USG, answer_buf, 16, &nonce_len, serverNonce); g_assert (gcry_err == 0); if (nonce_len < 16) { memmove(answer_buf + 16 - nonce_len, answer_buf, nonce_len); memset(answer_buf, 0, 16 - nonce_len); } memcpy (answer_buf + 16, password, strlen (password)); gcry_cipher_setiv (cipher, C2SIV, G_N_ELEMENTS (C2SIV)); gcry_err = gcry_cipher_encrypt (cipher, answer_buf, G_N_ELEMENTS (answer_buf), NULL, 0); g_assert (gcry_err == 0); /* Create request 3 */ comm = g_vfs_afp_command_new (AFP_COMMAND_LOGIN_CONT); /* pad byte */ g_data_output_stream_put_byte (G_DATA_OUTPUT_STREAM (comm), 0, NULL, NULL); /* id */ g_data_output_stream_put_uint16 (G_DATA_OUTPUT_STREAM (comm), id, NULL, NULL); g_output_stream_write_all (G_OUTPUT_STREAM (comm), answer_buf, G_N_ELEMENTS (answer_buf), NULL, NULL, NULL); reply = g_vfs_afp_connection_send_command_sync (priv->conn, comm, cancellable, error); g_object_unref (comm); if (!reply) goto error; res_code = g_vfs_afp_reply_get_result_code (reply); g_object_unref (reply); if (res_code != AFP_RESULT_NO_ERROR) { if (res_code == AFP_RESULT_USER_NOT_AUTH) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("Unable to login to the server “%s” with the given password."), priv->info.server_name); goto error; } else goto generic_error; } res = TRUE; cleanup: gcry_mpi_release (g); gcry_mpi_release (p); gcry_mpi_release (Ma); gcry_mpi_release (Mb); gcry_mpi_release (Ra); gcry_mpi_release (key); gcry_mpi_release (clientNonce); gcry_mpi_release (clientNonce1); gcry_mpi_release (serverNonce); gcry_cipher_close (cipher); g_free (buf); return res; error: res = FALSE; goto cleanup; generic_error: g_propagate_error (error, afp_result_code_to_gerror (res_code)); goto error; invalid_reply: g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to connect to the server “%s”. A communication problem occurred."), priv->info.server_name); goto error; } static gboolean dhx_login (GVfsAfpServer *server, const char *username, const char *password, GCancellable *cancellable, GError **error) { GVfsAfpServerPrivate *priv = server->priv; gcry_error_t gcry_err; gcry_mpi_t prime, base; gcry_mpi_t ra; /* Ma */ gcry_mpi_t ma; guint8 ma_buf[16]; GVfsAfpCommand *comm; GVfsAfpReply *reply; AfpResultCode res_code; gboolean res; guint16 id; guint8 *tmp_buf; /* Mb */ gcry_mpi_t mb; /* Nonce */ guint8 nonce_buf[32]; gcry_mpi_t nonce; /* Key */ gcry_mpi_t key; guint8 key_buf[16]; gcry_cipher_hd_t cipher; guint8 answer_buf[80] = { 0 }; size_t len; static const guint8 C2SIV[] = { 0x4c, 0x57, 0x61, 0x6c, 0x6c, 0x61, 0x63, 0x65 }; static const guint8 S2CIV[] = { 0x43, 0x4a, 0x61, 0x6c, 0x62, 0x65, 0x72, 0x74 }; static const guint8 p[] = { 0xBA, 0x28, 0x73, 0xDF, 0xB0, 0x60, 0x57, 0xD4, 0x3F, 0x20, 0x24, 0x74, 0x4C, 0xEE, 0xE7, 0x5B }; static const guint8 g[] = { 0x07 }; if (strlen (password) > 64) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, ngettext ("The server doesn’t support passwords longer than %d character.", "The server doesn’t support passwords longer than %d characters.", 64), 64); return FALSE; } /* create prime and base from vectors */ gcry_err = gcry_mpi_scan (&prime, GCRYMPI_FMT_USG, p, G_N_ELEMENTS (p), NULL); g_assert (gcry_err == 0); gcry_err = gcry_mpi_scan (&base, GCRYMPI_FMT_USG, g, G_N_ELEMENTS (g), NULL); g_assert (gcry_err == 0); /* generate random number ra != 0 */ ra = gcry_mpi_new (256); while (gcry_mpi_cmp_ui (ra, 0) == 0) gcry_mpi_randomize (ra, 256, GCRY_STRONG_RANDOM); /* Secret key value must be less than half of prime */ if (gcry_mpi_get_nbits (ra) > 255) gcry_mpi_clear_highbit (ra, 255); /* generate ma */ ma = gcry_mpi_new (128); gcry_mpi_powm (ma, base, ra, prime); gcry_mpi_release (base); gcry_err = gcry_mpi_print (GCRYMPI_FMT_USG, ma_buf, G_N_ELEMENTS (ma_buf), NULL, ma); g_assert (gcry_err == 0); gcry_mpi_release (ma); /* Create login command */ comm = g_vfs_afp_command_new (AFP_COMMAND_LOGIN); g_vfs_afp_command_put_pascal (comm, afp_version_to_string (priv->info.version)); g_vfs_afp_command_put_pascal (comm, AFP_UAM_DHX); g_vfs_afp_command_put_pascal (comm, username); g_vfs_afp_command_pad_to_even (comm); g_output_stream_write_all (G_OUTPUT_STREAM(comm), ma_buf, G_N_ELEMENTS (ma_buf), NULL, NULL, NULL); reply = g_vfs_afp_connection_send_command_sync (priv->conn, comm, cancellable, error); g_object_unref (comm); if (!reply) goto error; res_code = g_vfs_afp_reply_get_result_code (reply); if (res_code != AFP_RESULT_AUTH_CONTINUE) { g_object_unref (reply); if (res_code == AFP_RESULT_USER_NOT_AUTH) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("An invalid username was provided.")); goto error; } else goto generic_error; } REPLY_READ_UINT16 (reply, &id); /* read Mb */ REPLY_GET_DATA (reply, 16, &tmp_buf); gcry_err = gcry_mpi_scan (&mb, GCRYMPI_FMT_USG, tmp_buf, 16, NULL); g_assert (gcry_err == 0); /* read Nonce */ REPLY_GET_DATA (reply, 32, &tmp_buf); memcpy (nonce_buf, tmp_buf, 32); g_object_unref (reply); /* derive key */ key = gcry_mpi_new (128); gcry_mpi_powm (key, mb, ra, prime); gcry_mpi_release (mb); gcry_err = gcry_mpi_print (GCRYMPI_FMT_USG, key_buf, G_N_ELEMENTS (key_buf), NULL, key); g_assert (gcry_err == 0); gcry_mpi_release (key); /* setup decrypt cipher */ gcry_err = gcry_cipher_open (&cipher, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0); g_assert (gcry_err == 0); gcry_cipher_setiv (cipher, S2CIV, G_N_ELEMENTS (S2CIV)); gcry_cipher_setkey (cipher, key_buf, G_N_ELEMENTS (key_buf)); /* decrypt Nonce */ gcry_err = gcry_cipher_decrypt (cipher, nonce_buf, G_N_ELEMENTS (nonce_buf), NULL, 0); g_assert (gcry_err == 0); gcry_err = gcry_mpi_scan (&nonce, GCRYMPI_FMT_USG, nonce_buf, 16, NULL); g_assert (gcry_err == 0); /* add one to nonce */ gcry_mpi_add_ui (nonce, nonce, 1); /* set client->server initialization vector */ gcry_cipher_setiv (cipher, C2SIV, G_N_ELEMENTS (C2SIV)); /* create encrypted answer */ gcry_err = gcry_mpi_print (GCRYMPI_FMT_USG, answer_buf, 16, &len, nonce); g_assert (gcry_err == 0); gcry_mpi_release (nonce); if (len < 16) { memmove(answer_buf + 16 - len, answer_buf, len); memset(answer_buf, 0, 16 - len); } memcpy (answer_buf + 16, password, strlen (password)); gcry_err = gcry_cipher_encrypt (cipher, answer_buf, G_N_ELEMENTS (answer_buf), NULL, 0); g_assert (gcry_err == 0); gcry_cipher_close (cipher); /* Create Login Continue command */ comm = g_vfs_afp_command_new (AFP_COMMAND_LOGIN_CONT); g_data_output_stream_put_byte (G_DATA_OUTPUT_STREAM (comm), 0, NULL, NULL); g_data_output_stream_put_uint16 (G_DATA_OUTPUT_STREAM (comm), id, NULL, NULL); g_output_stream_write_all (G_OUTPUT_STREAM (comm), answer_buf, G_N_ELEMENTS (answer_buf), NULL, NULL, NULL); reply = g_vfs_afp_connection_send_command_sync (priv->conn, comm, cancellable, error); g_object_unref (comm); if (!reply) goto error; res_code = g_vfs_afp_reply_get_result_code (reply); g_object_unref (reply); if (res_code != AFP_RESULT_NO_ERROR) { if (res_code == AFP_RESULT_USER_NOT_AUTH) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("Unable to connect to the server “%s” with the given password."), priv->info.server_name); goto error; } else goto generic_error; } res = TRUE; done: gcry_mpi_release (prime); gcry_mpi_release (ra); return res; error: res = FALSE; goto done; generic_error: g_propagate_error (error, afp_result_code_to_gerror (res_code)); res = FALSE; goto done; invalid_reply: g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to connect to the server “%s”. A communication problem occurred."), priv->info.server_name); goto error; } #endif static gboolean do_login (GVfsAfpServer *server, const char *username, const char *password, gboolean anonymous, GCancellable *cancellable, GError **error) { GVfsAfpServerPrivate *priv = server->priv; /* anonymous login */ if (anonymous) { GVfsAfpCommand *comm; GVfsAfpReply *reply; AfpResultCode res_code; if (!g_slist_find_custom (priv->info.uams, AFP_UAM_NO_USER, (GCompareFunc)g_strcmp0)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("The server “%s” doesn’t support anonymous access."), priv->info.server_name); return FALSE; } comm = g_vfs_afp_command_new (AFP_COMMAND_LOGIN); g_vfs_afp_command_put_pascal (comm, afp_version_to_string (priv->info.version)); g_vfs_afp_command_put_pascal (comm, AFP_UAM_NO_USER); reply = g_vfs_afp_connection_send_command_sync (priv->conn, comm, cancellable, error); g_object_unref (comm); if (!reply) return FALSE; res_code = g_vfs_afp_reply_get_result_code (reply); g_object_unref (reply); if (res_code != AFP_RESULT_NO_ERROR) { switch (res_code) { case AFP_RESULT_USER_NOT_AUTH: case AFP_RESULT_BAD_UAM: g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("The server “%s” doesn’t support anonymous access."), priv->info.server_name); break; default: g_propagate_error (error, afp_result_code_to_gerror (res_code)); break; } return FALSE; } return TRUE; } else { #ifdef HAVE_GCRYPT /* Diffie-Hellman 2 */ if (g_slist_find_custom (priv->info.uams, AFP_UAM_DHX2, (GCompareFunc)g_strcmp0)) return dhx2_login (server, username, password, cancellable, error); /* Diffie-Hellman */ if (g_slist_find_custom (priv->info.uams, AFP_UAM_DHX, (GCompareFunc)g_strcmp0)) return dhx_login (server, username, password, cancellable, error); #endif g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to connect to the server “%s”. No suitable authentication mechanism was found."), priv->info.server_name); return FALSE; } } static gboolean get_server_info (GVfsAfpServer *server, GCancellable *cancellable, GError **error) { GVfsAfpServerPrivate *priv = server->priv; GVfsAfpReply *reply; guint16 MachineType_offset, AFPVersionCount_offset, UAMCount_offset; guint8 count; guint i; reply = g_vfs_afp_query_server_info (G_SOCKET_CONNECTABLE (priv->addr), cancellable, error); if (!reply) return FALSE; REPLY_READ_UINT16 (reply, &MachineType_offset); REPLY_READ_UINT16 (reply, &AFPVersionCount_offset); REPLY_READ_UINT16 (reply, &UAMCount_offset); /* VolumeIconAndMask_offset */ REPLY_READ_UINT16 (reply, NULL); REPLY_READ_UINT16 (reply, &priv->info.flags); REPLY_READ_PASCAL (reply, FALSE, &priv->info.server_name); /* Parse UTF-8 ServerName */ if (priv->info.flags & (0x1 << 8)) { guint16 UTF8ServerName_offset; GVfsAfpName *utf8_server_name; REPLY_SKIP_TO_EVEN (reply); REPLY_SEEK (reply, 6, G_SEEK_CUR); REPLY_READ_UINT16 (reply, &UTF8ServerName_offset); REPLY_SEEK (reply, UTF8ServerName_offset, G_SEEK_SET); REPLY_READ_AFP_NAME (reply, FALSE, &utf8_server_name); priv->info.utf8_server_name = g_vfs_afp_name_get_string (utf8_server_name); g_vfs_afp_name_unref (utf8_server_name); } /* Parse MachineType */ REPLY_SEEK (reply, MachineType_offset, G_SEEK_SET); REPLY_READ_PASCAL (reply, FALSE, &priv->info.machine_type); /* Parse Versions */ REPLY_SEEK (reply, AFPVersionCount_offset, G_SEEK_SET); REPLY_READ_BYTE (reply, &count); for (i = 0; i < count; i++) { char *version; AfpVersion afp_version; REPLY_READ_PASCAL (reply, FALSE, &version); afp_version = string_to_afp_version (version); g_free (version); if (afp_version > priv->info.version) priv->info.version = afp_version; } if (priv->info.version == AFP_VERSION_INVALID) { g_object_unref (reply); g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to connect to the server “%s”. The server doesn’t support AFP version 3.0 or later."), priv->info.server_name); return FALSE; } /* Parse UAMs */ REPLY_SEEK (reply, UAMCount_offset, G_SEEK_SET); REPLY_READ_BYTE (reply, &count); for (i = 0; i < count; i++) { char *uam; REPLY_READ_PASCAL (reply, FALSE, &uam); priv->info.uams = g_slist_prepend (priv->info.uams, uam); } g_object_unref (reply); return TRUE; invalid_reply: g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to connect to the server “%s”. A communication problem occurred."), priv->info.server_name); return FALSE; } static gboolean get_server_parms (GVfsAfpServer *server, GCancellable *cancellable, GError **error) { GVfsAfpServerPrivate *priv = server->priv; GVfsAfpCommand *comm; GVfsAfpReply *reply; AfpResultCode res_code; gint32 server_time; /* Get Server Parameters */ comm = g_vfs_afp_command_new (AFP_COMMAND_GET_SRVR_PARMS); /* pad byte */ g_vfs_afp_command_put_byte (comm, 0); reply = g_vfs_afp_connection_send_command_sync (priv->conn, comm, cancellable, error); g_object_unref (comm); if (!reply) return FALSE; res_code = g_vfs_afp_reply_get_result_code (reply); if (res_code != AFP_RESULT_NO_ERROR) { g_object_unref (reply); g_propagate_error (error, afp_result_code_to_gerror (res_code)); return FALSE; } /* server time */ REPLY_READ_INT32 (reply, &server_time); priv->time_diff = (g_get_real_time () / G_USEC_PER_SEC) - server_time; g_object_unref (reply); return TRUE; invalid_reply: g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to connect to the server “%s”. A communication problem occurred."), priv->info.server_name); return FALSE; } static GVfsAfpReply * command_get_user_info (GVfsAfpServer *server, guint16 bitmap, GCancellable *cancellable, GError **error) { GVfsAfpServerPrivate *priv = server->priv; GVfsAfpCommand *comm; GVfsAfpReply *reply; AfpResultCode res_code; comm = g_vfs_afp_command_new (AFP_COMMAND_GET_USER_INFO); /* Flags, ThisUser = 1 */ g_vfs_afp_command_put_byte (comm, 0x01); /* UserId */ g_vfs_afp_command_put_int32 (comm, 0); /* Bitmap */ g_vfs_afp_command_put_uint16 (comm, bitmap); reply = g_vfs_afp_connection_send_command_sync (priv->conn, comm, cancellable, error); g_object_unref (comm); if (!reply) return NULL; res_code = g_vfs_afp_reply_get_result_code (reply); if (res_code != AFP_RESULT_NO_ERROR) { g_object_unref (reply); switch (res_code) { case AFP_RESULT_ACCESS_DENIED: g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("Permission denied.")); break; break; case AFP_RESULT_CALL_NOT_SUPPORTED: g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("The command is not supported by the server.")); break; case AFP_RESULT_PWD_EXPIRED_ERR: g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("Your password has expired.")); break; case AFP_RESULT_PWD_NEEDS_CHANGE_ERR: g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("Your password needs to be changed.")); break; default: g_propagate_error (error, afp_result_code_to_gerror (res_code)); break; } return NULL; } return reply; } static gboolean get_userinfo (GVfsAfpServer *server, GCancellable *cancellable, GError **error) { GVfsAfpServerPrivate *priv = server->priv; gboolean res = FALSE; GVfsAfpReply *reply = NULL; guint16 bitmap; bitmap = AFP_GET_USER_INFO_BITMAP_GET_UID_BIT | AFP_GET_USER_INFO_BITMAP_GET_UUID_BIT; reply = command_get_user_info (server, bitmap, cancellable, error); if (!reply) goto done; /* Bitmap */ REPLY_READ_UINT16 (reply, &bitmap); if (bitmap != (AFP_GET_USER_INFO_BITMAP_GET_UID_BIT | AFP_GET_USER_INFO_BITMAP_GET_UUID_BIT)) goto invalid_reply; REPLY_READ_UINT32 (reply, &priv->user_id); REPLY_READ_UINT64 (reply, &priv->uuid); g_clear_object (&reply); /* We try to get the group id separately since seems to give an invalid reply * on some OS X versions. */ bitmap = AFP_GET_USER_INFO_BITMAP_GET_GID_BIT; reply = command_get_user_info (server, bitmap, cancellable, error); if (!reply) goto done; /* Bitmap */ REPLY_READ_UINT16 (reply, &bitmap); if (bitmap != AFP_GET_USER_INFO_BITMAP_GET_GID_BIT) goto invalid_reply; /* Don't check for errors since it's known to fail on some servers. */ g_vfs_afp_reply_read_uint32 (reply, &priv->group_id); res = TRUE; done: g_clear_object (&reply); return res; invalid_reply: g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to connect to the server “%s”. A communication problem occurred."), priv->info.server_name); goto done; } gboolean g_vfs_afp_server_login (GVfsAfpServer *server, const char *initial_user, GMountSource *mount_source, char **logged_in_user, GCancellable *cancellable, GError **error) { GVfsAfpServerPrivate *priv = server->priv; gboolean res; char *user, *olduser; char *password; char *server_name; gboolean anonymous; GPasswordSave password_save; char *prompt = NULL; GError *err = NULL; res = get_server_info (server, cancellable, error); if (!res) return FALSE; /* Use utf8_server_name if it exists */ server_name = priv->info.utf8_server_name ? priv->info.utf8_server_name : priv->info.server_name; olduser = g_strdup (initial_user); if (initial_user) { if (g_str_equal (initial_user, "anonymous") && g_slist_find_custom (priv->info.uams, AFP_UAM_NO_USER, (GCompareFunc)g_strcmp0)) { user = NULL; password = NULL; anonymous = TRUE; goto try_login; } } if (g_vfs_keyring_lookup_password (initial_user, g_network_address_get_hostname (priv->addr), NULL, "afp", NULL, NULL, g_network_address_get_port (priv->addr), &user, NULL, &password) && user != NULL && password != NULL) { anonymous = FALSE; goto try_login; } while (TRUE) { GString *str; GAskPasswordFlags flags; gboolean aborted; g_free (prompt); g_clear_error (&err); str = g_string_new (NULL); /* create prompt */ if (initial_user) /* Translators: the first %s is the username, the second the host name */ g_string_append_printf (str, _("Authentication Required\nEnter password for “%s” on “%s”:"), initial_user, server_name); else /* Translators: %s here is the hostname */ g_string_append_printf (str, _("Authentication Required\nEnter user and password for “%s”:"), server_name); prompt = g_string_free (str, FALSE); flags = G_ASK_PASSWORD_NEED_PASSWORD; if (!initial_user) { flags |= G_ASK_PASSWORD_NEED_USERNAME; if (g_slist_find_custom (priv->info.uams, AFP_UAM_NO_USER, (GCompareFunc)g_strcmp0)) flags |= G_ASK_PASSWORD_ANONYMOUS_SUPPORTED; } if (g_vfs_keyring_is_available ()) flags |= G_ASK_PASSWORD_SAVING_SUPPORTED; if (!g_mount_source_ask_password (mount_source, prompt, olduser, NULL, flags, &aborted, &password, &user, NULL, &anonymous, &password_save) || aborted) { g_set_error_literal (&err, G_IO_ERROR, aborted ? G_IO_ERROR_FAILED_HANDLED : G_IO_ERROR_PERMISSION_DENIED, _("The password prompt was cancelled.")); res = FALSE; break; } if (!user) user = g_strdup (olduser); try_login: /* Open connection */ priv->conn = g_vfs_afp_connection_new (G_SOCKET_CONNECTABLE (priv->addr)); res = g_vfs_afp_connection_open_sync (priv->conn, cancellable, &err); if (!res) break; res = do_login (server, user, password, anonymous, cancellable, &err); if (!res) { g_vfs_afp_connection_close_sync (priv->conn, cancellable, NULL); g_clear_object (&priv->conn); if (!g_error_matches (err, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) break; } else break; g_free (olduser); olduser = user; g_free (password); } g_free (olduser); if (!res) goto error; /* Get server parms */ if (!get_server_parms (server, cancellable, &err)) goto error; /* Get user info */ if (!get_userinfo (server, cancellable, &err)) goto error; if (prompt && !anonymous) { /* a prompt was created, so we have to save the password */ g_vfs_keyring_save_password (user, g_network_address_get_hostname (priv->addr), NULL, "afp", NULL, NULL, g_network_address_get_port (priv->addr), password, password_save); g_free (prompt); } if (logged_in_user) { if (anonymous) *logged_in_user = g_strdup ("anonymous"); else *logged_in_user = user; } else g_free (user); g_free (password); return TRUE; error: g_free (user); g_free (password); g_propagate_error (error, err); return FALSE; } /* * g_vfs_afp_server_logout_sync: * * Terminates and closes the connection to the server */ gboolean g_vfs_afp_server_logout_sync (GVfsAfpServer *server, GCancellable *cancellable, GError **error) { GVfsAfpServerPrivate *priv; GVfsAfpCommand *comm; GVfsAfpReply *reply; gint32 res_code; gboolean res = FALSE; g_return_val_if_fail (G_VFS_IS_AFP_SERVER (server), FALSE); priv = server->priv; comm = g_vfs_afp_command_new (AFP_COMMAND_LOGOUT); /* pad byte */ g_vfs_afp_command_put_byte (comm, 0); reply = g_vfs_afp_connection_send_command_sync (priv->conn, comm, cancellable, error); g_object_unref (comm); if (!reply) { g_vfs_afp_connection_close_sync (priv->conn, cancellable, NULL); goto done; } res_code = g_vfs_afp_reply_get_result_code (reply); g_object_unref (reply); if (res_code != AFP_RESULT_NO_ERROR) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to disconnect from the server.")); g_vfs_afp_connection_close_sync (priv->conn, cancellable, NULL); goto done; } res = g_vfs_afp_connection_close_sync (priv->conn, cancellable, error); done: g_clear_object (&priv->conn); return res; } /** * g_vfs_afp_server_get_info: * * @server: a #GVfsAfpServer * * Returns: a #GVfsAfpServerInfo or %NULL if the server is not logged in. */ const GVfsAfpServerInfo * g_vfs_afp_server_get_info (GVfsAfpServer *server) { GVfsAfpServerPrivate *priv; g_return_val_if_fail (G_VFS_IS_AFP_SERVER (server), FALSE); priv = server->priv; if (!priv->conn) return NULL; return &priv->info; } /** * g_vfs_afp_server_get_max_request_size: * @server: a #GVfsAfpServer * * Get the maximum request size the server supports. * * Returns: The maximum request size the server supports. */ guint32 g_vfs_afp_server_get_max_request_size (GVfsAfpServer *server) { g_return_val_if_fail (G_VFS_IS_AFP_SERVER (server), 0); return g_vfs_afp_connection_get_max_request_size (server->priv->conn); } /** * g_vfs_afp_server_time_to_local_time: * * @server: a #GVfsAfpServer * @server_time: a time value in server time * * Returns: the time converted to local time */ gint64 g_vfs_afp_server_time_to_local_time (GVfsAfpServer *server, gint32 server_time) { g_return_val_if_fail (G_VFS_IS_AFP_SERVER (server), 0); return server_time + server->priv->time_diff; } static void volume_data_free (GVfsAfpVolumeData *vol_data) { g_free (vol_data->name); g_slice_free (GVfsAfpVolumeData, vol_data); } static void get_volumes_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GVfsAfpConnection *afp_conn = G_VFS_AFP_CONNECTION (source_object); GTask *task = G_TASK (user_data); GVfsAfpReply *reply; GError *err = NULL; AfpResultCode res_code; guint8 num_volumes, i; GPtrArray *volumes; reply = g_vfs_afp_connection_send_command_finish (afp_conn, res, &err); if (!reply) { g_task_return_error (task, err); g_object_unref (task); return; } res_code = g_vfs_afp_reply_get_result_code (reply); if (res_code != AFP_RESULT_NO_ERROR) { g_object_unref (reply); g_task_return_error (task, afp_result_code_to_gerror (res_code)); g_object_unref (task); return; } /* server time */ REPLY_READ_INT32 (reply, NULL); /* NumVolStructures */ REPLY_READ_BYTE (reply, &num_volumes); volumes = g_ptr_array_sized_new (num_volumes); g_ptr_array_set_free_func (volumes, (GDestroyNotify)volume_data_free); for (i = 0; i < num_volumes; i++) { guint8 flags; char *vol_name; GVfsAfpVolumeData *volume_data; REPLY_READ_BYTE (reply, &flags); REPLY_READ_PASCAL (reply, TRUE, &vol_name); if (!vol_name) continue; volume_data = g_slice_new (GVfsAfpVolumeData); volume_data->flags = flags; volume_data->name = vol_name; g_ptr_array_add (volumes, volume_data); } g_object_unref (reply); g_task_return_pointer (task, volumes, (GDestroyNotify)g_ptr_array_unref); g_object_unref (task); return; invalid_reply: g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to connect to the server. A communication problem occurred.")); g_object_unref (task); } /** * g_vfs_afp_server_get_volumes: * * @server: a #GVfsAfpServer * @cancellable: optional #GCancellable object, %NULL to ignore. * @callback: callback to call when the request is satisfied. * @user_data: the data to pass to callback function. * * Asynchronously retrieves the volumes available on @server. */ void g_vfs_afp_server_get_volumes (GVfsAfpServer *server, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GVfsAfpCommand *comm; GTask *task; /* Get Server Parameters */ comm = g_vfs_afp_command_new (AFP_COMMAND_GET_SRVR_PARMS); /* pad byte */ g_vfs_afp_command_put_byte (comm, 0); task = g_task_new (server, cancellable, callback, user_data); g_task_set_source_tag (task, g_vfs_afp_server_get_volumes); g_vfs_afp_connection_send_command (server->priv->conn, comm, NULL, get_volumes_cb, cancellable, task); } /** * g_vfs_afp_server_get_volumes_finish: * * @server: a #GVfsAfpServer. * @result: a #GAsyncResult. * @error: a #GError, %NULL to ignore. * * Finalizes the asynchronous operation started by * g_vfs_afp_server_get_volumes. * * Returns: A #GPtrArray containing the volumes #GVfsAfpVolumeData structures or * %NULL on error. * */ GPtrArray * g_vfs_afp_server_get_volumes_finish (GVfsAfpServer *server, GAsyncResult *result, GError **error) { g_return_val_if_fail (g_task_is_valid (result, server), NULL); g_return_val_if_fail (g_async_result_is_tagged (result, g_vfs_afp_server_get_volumes), NULL); return g_task_propagate_pointer (G_TASK (result), error); } GVfsAfpVolume * g_vfs_afp_server_mount_volume_sync (GVfsAfpServer *server, const char *volume_name, GCancellable *cancellable, GError **error) { GVfsAfpVolume *volume; volume = g_vfs_afp_volume_new (server, server->priv->conn); if (!g_vfs_afp_volume_mount_sync (volume, volume_name, cancellable, error)) { g_object_unref (volume); return NULL; } return volume; } static void set_access_attributes_trusted (GFileInfo *info, guint32 perm) { g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, perm & 0x4); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, perm & 0x2); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, perm & 0x1); } /* For files we don't own we can't trust a negative response to this check, as something else could allow us to do the operation, for instance an ACL or some sticky bit thing */ static void set_access_attributes (GFileInfo *info, guint32 perm) { if (perm & 0x4) g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE); if (perm & 0x2) g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, TRUE); if (perm & 0x1) g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, TRUE); } gboolean g_vfs_afp_server_fill_info (GVfsAfpServer *server, GFileInfo *info, GVfsAfpReply *reply, gboolean directory, guint16 bitmap, GError **error) { goffset start_pos; if (directory) { const char *content_type = "inode/directory"; GIcon *icon; g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); g_file_info_set_content_type (info, content_type); icon = g_content_type_get_icon (content_type); g_file_info_set_icon (info, icon); g_object_unref (icon); icon = g_content_type_get_symbolic_icon (content_type); g_file_info_set_symbolic_icon (info, icon); g_object_unref (icon); } else g_file_info_set_file_type (info, G_FILE_TYPE_REGULAR); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE); start_pos = g_vfs_afp_reply_get_pos (reply); if (bitmap & AFP_FILEDIR_BITMAP_ATTRIBUTE_BIT) { guint16 attributes; REPLY_READ_UINT16 (reply, &attributes); if (attributes & AFP_FILEDIR_ATTRIBUTES_BITMAP_INVISIBLE_BIT) g_file_info_set_is_hidden (info, TRUE); } if (bitmap & AFP_FILEDIR_BITMAP_PARENT_DIR_ID_BIT) { guint32 parent_dir_id; REPLY_READ_UINT32 (reply, &parent_dir_id); g_file_info_set_attribute_uint32 (info, "afp::parent-dir-id", parent_dir_id); } if (bitmap & AFP_FILEDIR_BITMAP_CREATE_DATE_BIT) { gint32 create_date; gint64 create_date_local; REPLY_READ_INT32 (reply, &create_date); create_date_local = g_vfs_afp_server_time_to_local_time (server, create_date); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CREATED, create_date_local); } if (bitmap & AFP_FILEDIR_BITMAP_MOD_DATE_BIT) { gint32 mod_date; guint64 mod_date_unix; char *etag; REPLY_READ_INT32 (reply, &mod_date); mod_date_unix = g_vfs_afp_server_time_to_local_time (server, mod_date); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, mod_date_unix); etag = g_strdup_printf ("%"G_GUINT64_FORMAT, mod_date_unix); g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag); g_free (etag); } if (bitmap & AFP_FILEDIR_BITMAP_NODE_ID_BIT) { guint32 node_id; REPLY_READ_UINT32 (reply, &node_id); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_AFP_NODE_ID, node_id); } /* Directory specific attributes */ if (directory) { if (bitmap & AFP_DIR_BITMAP_OFFSPRING_COUNT_BIT) { guint16 offspring_count; REPLY_READ_UINT16 (reply, &offspring_count); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_AFP_CHILDREN_COUNT, offspring_count); } } /* File specific attributes */ else { if (bitmap & AFP_FILE_BITMAP_EXT_DATA_FORK_LEN_BIT) { guint64 fork_len; REPLY_READ_UINT64 (reply, &fork_len); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE, fork_len); } } if (bitmap & AFP_FILEDIR_BITMAP_UTF8_NAME_BIT) { guint16 UTF8Name_offset; goffset old_pos; GVfsAfpName *afp_name; char *utf8_name; REPLY_READ_UINT16 (reply, &UTF8Name_offset); /* Pad */ REPLY_READ_UINT32 (reply, NULL); old_pos = g_vfs_afp_reply_get_pos (reply); REPLY_SEEK (reply, start_pos + UTF8Name_offset, G_SEEK_SET); REPLY_READ_AFP_NAME (reply, TRUE, &afp_name); utf8_name = g_vfs_afp_name_get_string (afp_name); g_vfs_afp_name_unref (afp_name); g_file_info_set_name (info, utf8_name); g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, utf8_name); /* Set file as hidden if it begins with a dot */ if (utf8_name[0] == '.') g_file_info_set_is_hidden (info, TRUE); if (!directory) { char *content_type; gboolean uncertain_content_type; GIcon *icon; content_type = g_content_type_guess (utf8_name, NULL, 0, &uncertain_content_type); if (!uncertain_content_type) g_file_info_set_content_type (info, content_type); g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, content_type); icon = g_content_type_get_icon (content_type); g_file_info_set_icon (info, icon); g_object_unref (icon); icon = g_content_type_get_symbolic_icon (content_type); g_file_info_set_symbolic_icon (info, icon); g_object_unref (icon); g_free (content_type); } g_free (utf8_name); REPLY_SEEK (reply, old_pos, G_SEEK_SET); } if (bitmap & AFP_FILEDIR_BITMAP_UNIX_PRIVS_BIT) { guint32 uid, gid, permissions, ua_permissions; REPLY_READ_UINT32 (reply, &uid); REPLY_READ_UINT32 (reply, &gid); REPLY_READ_UINT32 (reply, &permissions); /* ua_permissions */ REPLY_READ_UINT32 (reply, &ua_permissions); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, permissions); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, uid); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, gid); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_AFP_UA_PERMISSIONS, ua_permissions); if (uid == server->priv->user_id) set_access_attributes_trusted (info, (permissions >> 6) & 0x7); else if (gid == server->priv->group_id) set_access_attributes (info, (permissions >> 3) & 0x7); else set_access_attributes (info, (permissions >> 0) & 0x7); } return TRUE; invalid_reply: g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to connect to the server “%s”. A communication problem occurred."), server->priv->info.server_name); return FALSE; } static void map_id_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object); GTask *task = G_TASK (user_data); GVfsAfpMapIDFunction function = GPOINTER_TO_INT (g_task_get_task_data (task)); GVfsAfpReply *reply; GError *err = NULL; AfpResultCode res_code; gchar *name; reply = g_vfs_afp_connection_send_command_finish (conn, res, &err); if (!reply) { g_task_return_error (task, err); g_object_unref (task); return; } res_code = g_vfs_afp_reply_get_result_code (reply); if (res_code != AFP_RESULT_NO_ERROR) { switch (res_code) { case AFP_RESULT_ITEM_NOT_FOUND: g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, _("Identification not found.")); break; default: g_task_return_error (task, afp_result_code_to_gerror (res_code)); break; } g_object_unref (task); return; } if (function == GVFS_AFP_MAP_ID_FUNCTION_USER_UUID_TO_UTF8_NAME || function == GVFS_AFP_MAP_ID_FUNCTION_GROUP_UUID_TO_UTF8_NAME) { /* objType */ REPLY_READ_UINT32 (reply, NULL); /* id */ REPLY_READ_UINT32 (reply, NULL); } if (function == GVFS_AFP_MAP_ID_FUNCTION_USER_ID_TO_NAME || function == GVFS_AFP_MAP_ID_FUNCTION_GROUP_ID_TO_NAME) { REPLY_READ_PASCAL (reply, FALSE, &name); } else { GVfsAfpName *afp_name; REPLY_READ_AFP_NAME (reply, FALSE, &afp_name); name = g_vfs_afp_name_get_string (afp_name); g_vfs_afp_name_unref (afp_name); } g_task_return_pointer (task, name, g_free); g_object_unref (task); return; invalid_reply: g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to connect to the server. A communication problem occurred.")); g_object_unref (task); } /** * g_vfs_afp_server_map_id: * * @server: a #GVfsAfpServer. * @map_function: a #GVfsAfpMapIDFunction. * @id: the id to be mapped to a name. * @cancellable: optional #GCancellable object, %NULL to ignore. * @callback: callback to call when the request is satisfied. * @user_data: the data to pass to callback function. * * Asynchronously maps a user id, group id or uuid to a name. */ void g_vfs_afp_server_map_id (GVfsAfpServer *server, GVfsAfpMapIDFunction map_function, gint64 id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GVfsAfpServerPrivate *priv; GVfsAfpCommand *comm; GTask *task; g_return_if_fail (G_VFS_IS_AFP_SERVER (server)); priv = server->priv; comm = g_vfs_afp_command_new (AFP_COMMAND_MAP_ID); /* SubFunction*/ g_vfs_afp_command_put_byte (comm, map_function); /* ID */ if (map_function == GVFS_AFP_MAP_ID_FUNCTION_USER_ID_TO_NAME || map_function == GVFS_AFP_MAP_ID_FUNCTION_GROUP_ID_TO_NAME) g_vfs_afp_command_put_int32 (comm, id); else g_vfs_afp_command_put_int64 (comm, id); task = g_task_new (server, cancellable, callback, user_data); g_task_set_source_tag (task, g_vfs_afp_server_map_id); g_task_set_task_data (task, GINT_TO_POINTER (map_function), NULL); g_vfs_afp_connection_send_command (priv->conn, comm, NULL, map_id_cb, cancellable, task); g_object_unref (comm); } /** * g_vfs_afp_server_map_id_finish: * * @server: a #GVfsAfpServer. * @result: a #GAsyncResult. * @map_function: (out) optional out parameter to get the #GVfsAfpMapIDFunction * which was used, %NULL to ignore. * @error: a #GError, %NULL to ignore. * * Finalizes the asynchronous operation started by * g_vfs_afp_server_map_id. * * Returns: (transfer full): A string with the name of the id or %NULL * on error. */ char * g_vfs_afp_server_map_id_finish (GVfsAfpServer *server, GAsyncResult *res, GVfsAfpMapIDFunction *map_function, GError **error) { g_return_val_if_fail (g_task_is_valid (res, server), NULL); g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_afp_server_map_id), NULL); if (map_function) *map_function = GPOINTER_TO_INT (g_task_get_task_data (G_TASK (res))); return g_task_propagate_pointer (G_TASK (res), error); }