/* * gibber-linklocal-transport.c - Source for GibberLLTransport * Copyright (C) 2006, 2008 Collabora Ltd. * @author: Sjoerd Simons * @author: Alban Crequy * * 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.1 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ /* needed for struct ucred */ #define _GNU_SOURCE #include "config.h" #include "gibber-unix-transport.h" #include #ifdef G_OS_UNIX /* If you claim to be Unix but you don't have these headers, you may have * already lost. */ #include #include #include #include #include #include #include #include #include #include "gibber-util.h" #define DEBUG_FLAG DEBUG_NET #include "gibber-debug.h" G_DEFINE_TYPE(GibberUnixTransport, gibber_unix_transport, \ GIBBER_TYPE_FD_TRANSPORT) GQuark gibber_unix_transport_error_quark (void) { static GQuark quark = 0; if (!quark) quark = g_quark_from_static_string ("gibber_unix_transport_error"); return quark; } /* private structure */ typedef struct _GibberUnixTransportPrivate GibberUnixTransportPrivate; struct _GibberUnixTransportPrivate { gboolean incoming; GibberUnixTransportRecvCredentialsCb recv_creds_cb; gpointer recv_creds_data; gboolean dispose_has_run; }; #define GIBBER_UNIX_TRANSPORT_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GIBBER_TYPE_UNIX_TRANSPORT, \ GibberUnixTransportPrivate)) static void gibber_unix_transport_finalize (GObject *object); static void gibber_unix_transport_init (GibberUnixTransport *self) { GibberUnixTransportPrivate *priv = GIBBER_UNIX_TRANSPORT_GET_PRIVATE (self); priv->incoming = FALSE; } static void gibber_unix_transport_dispose (GObject *object); static GibberFdIOResult gibber_unix_transport_read ( GibberFdTransport *transport, GIOChannel *channel, GError **error); static void gibber_unix_transport_class_init ( GibberUnixTransportClass *gibber_unix_transport_class) { GObjectClass *object_class = G_OBJECT_CLASS (gibber_unix_transport_class); GibberFdTransportClass *fd_class = GIBBER_FD_TRANSPORT_CLASS ( gibber_unix_transport_class); g_type_class_add_private (gibber_unix_transport_class, sizeof (GibberUnixTransportPrivate)); object_class->dispose = gibber_unix_transport_dispose; object_class->finalize = gibber_unix_transport_finalize; /* override GibberFdTransport's read */ fd_class->read = gibber_unix_transport_read; } void gibber_unix_transport_dispose (GObject *object) { GibberUnixTransport *self = GIBBER_UNIX_TRANSPORT (object); GibberUnixTransportPrivate *priv = GIBBER_UNIX_TRANSPORT_GET_PRIVATE (self); if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; priv->recv_creds_cb = NULL; priv->recv_creds_data = NULL; if (G_OBJECT_CLASS (gibber_unix_transport_parent_class)->dispose) G_OBJECT_CLASS (gibber_unix_transport_parent_class)->dispose (object); } void gibber_unix_transport_finalize (GObject *object) { G_OBJECT_CLASS (gibber_unix_transport_parent_class)->finalize (object); } GibberUnixTransport * gibber_unix_transport_new (void) { return g_object_new (GIBBER_TYPE_UNIX_TRANSPORT, NULL); } gboolean gibber_unix_transport_connect (GibberUnixTransport *transport, const gchar *path, GError **error) { union { struct sockaddr_un un; struct sockaddr addr; } addr; int fd; gibber_transport_set_state (GIBBER_TRANSPORT (transport), GIBBER_TRANSPORT_CONNECTING); memset (&addr, 0, sizeof (addr)); fd = socket (PF_UNIX, SOCK_STREAM, 0); if (fd == -1) { DEBUG ("Error creating socket: %s", g_strerror (errno)); g_set_error (error, GIBBER_UNIX_TRANSPORT_ERROR, GIBBER_UNIX_TRANSPORT_ERROR_CONNECT_FAILED, "Error creating socket: %s", g_strerror (errno)); goto failed; } addr.un.sun_family = PF_UNIX; g_strlcpy (addr.un.sun_path, path, sizeof (addr.un.sun_path)); if (connect (fd, &addr.addr, sizeof (addr.un)) == -1) { g_set_error (error, GIBBER_UNIX_TRANSPORT_ERROR, GIBBER_UNIX_TRANSPORT_ERROR_CONNECT_FAILED, "Error connecting socket: %s", g_strerror (errno)); DEBUG ("Error connecting socket: %s", g_strerror (errno)); goto failed; } DEBUG ("Connected to socket"); gibber_fd_transport_set_fd (GIBBER_FD_TRANSPORT (transport), fd, TRUE); return TRUE; failed: g_assert (error != NULL); gibber_transport_emit_error (GIBBER_TRANSPORT(transport), *error); gibber_transport_set_state (GIBBER_TRANSPORT (transport), GIBBER_TRANSPORT_DISCONNECTED); return FALSE; } GibberUnixTransport * gibber_unix_transport_new_from_fd (int fd) { GibberUnixTransport *transport; transport = gibber_unix_transport_new (); gibber_fd_transport_set_fd (GIBBER_FD_TRANSPORT (transport), fd, TRUE); return transport; } /* Patches that reimplement these functions for non-Linux would be welcome * (please file a bug) */ #if defined(__linux__) gboolean gibber_unix_transport_supports_credentials (void) { return TRUE; } gboolean gibber_unix_transport_send_credentials (GibberUnixTransport *transport, const guint8 *data, gsize size) { int fd, ret; struct ucred *cred; struct msghdr msg; struct cmsghdr *ch; struct iovec iov; char buffer[CMSG_SPACE (sizeof (struct ucred))]; DEBUG ("send credentials"); fd = GIBBER_FD_TRANSPORT (transport)->fd; /* Set the message payload */ memset (&iov, 0, sizeof (iov)); iov.iov_base = (void *) data; iov.iov_len = size; memset (&msg, 0, sizeof (msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = buffer; msg.msg_controllen = sizeof (buffer); memset (buffer, 0, sizeof (buffer)); /* Set the credentials */ ch = CMSG_FIRSTHDR (&msg); ch->cmsg_len = CMSG_LEN (sizeof (struct ucred)); ch->cmsg_level = SOL_SOCKET; ch->cmsg_type = SCM_CREDENTIALS; cred = (struct ucred *) CMSG_DATA (ch); cred->pid = getpid (); cred->uid = getuid (); cred->gid = getgid (); ret = sendmsg (fd, &msg, 0); if (ret == -1) { DEBUG ("sendmsg failed: %s", g_strerror (errno)); return FALSE; } return TRUE; } #define BUFSIZE 1024 static GibberFdIOResult gibber_unix_transport_read (GibberFdTransport *transport, GIOChannel *channel, GError **error) { GibberUnixTransport *self = GIBBER_UNIX_TRANSPORT (transport); GibberUnixTransportPrivate *priv = GIBBER_UNIX_TRANSPORT_GET_PRIVATE (self); int fd; guint8 buffer[BUFSIZE]; ssize_t bytes_read; GibberBuffer buf; struct iovec iov; struct msghdr msg; char control[CMSG_SPACE (sizeof (struct ucred))]; struct cmsghdr *ch; struct ucred *cred; int opt; if (priv->recv_creds_cb == NULL) return gibber_fd_transport_read (transport, channel, error); /* We are waiting for credentials */ fd = transport->fd; /* set SO_PASSCRED flag */ opt = 1; setsockopt (fd, SOL_SOCKET, SO_PASSCRED, &opt, sizeof (opt)); memset (buffer, 0, sizeof (buffer)); memset (&iov, 0, sizeof (iov)); iov.iov_base = buffer; iov.iov_len = sizeof (buffer); memset (&msg, 0, sizeof (msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = control; msg.msg_controllen = sizeof (control); bytes_read = recvmsg (fd, &msg, 0); if (bytes_read == -1) { GError *err = NULL; g_set_error_literal (&err, G_IO_CHANNEL_ERROR, g_io_channel_error_from_errno (errno), "recvmsg failed"); priv->recv_creds_cb (self, NULL, NULL, err, priv->recv_creds_data); g_propagate_error (error, err); priv->recv_creds_cb = NULL; priv->recv_creds_data = NULL; return GIBBER_FD_IO_RESULT_ERROR; } /* unset SO_PASSCRED flag */ opt = 0; setsockopt (fd, SOL_SOCKET, SO_PASSCRED, &opt, sizeof (opt)); buf.data = buffer; buf.length = bytes_read; /* extract the credentials */ ch = CMSG_FIRSTHDR (&msg); if (ch == NULL) { GError *err = NULL; DEBUG ("Message doesn't contain credentials"); g_set_error_literal (&err, GIBBER_UNIX_TRANSPORT_ERROR, GIBBER_UNIX_TRANSPORT_ERROR_NO_CREDENTIALS, "no credentials received"); priv->recv_creds_cb (self, &buf, NULL, err, priv->recv_creds_data); g_error_free (err); } else { GibberCredentials credentials; cred = (struct ucred *) CMSG_DATA (ch); credentials.pid = cred->pid; credentials.uid = cred->uid; credentials.gid = cred->gid; priv->recv_creds_cb (self, &buf, &credentials, NULL, priv->recv_creds_data); } priv->recv_creds_cb = NULL; priv->recv_creds_data = NULL; return GIBBER_FD_IO_RESULT_SUCCESS; } gboolean gibber_unix_transport_recv_credentials (GibberUnixTransport *self, GibberUnixTransportRecvCredentialsCb callback, gpointer user_data) { GibberUnixTransportPrivate *priv = GIBBER_UNIX_TRANSPORT_GET_PRIVATE (self); if (priv->recv_creds_cb != NULL) { DEBUG ("already waiting for credentials"); return FALSE; } priv->recv_creds_cb = callback; priv->recv_creds_data = user_data; return TRUE; } #else /* OSs where we have no implementation */ gboolean gibber_unix_transport_supports_credentials (void) { return FALSE; } gboolean gibber_unix_transport_recv_credentials (GibberUnixTransport *self, GibberUnixTransportRecvCredentialsCb callback, gpointer user_data) { DEBUG ("stub implementation, failing"); return FALSE; } gboolean gibber_unix_transport_send_credentials (GibberUnixTransport *transport, const guint8 *data, gsize size) { DEBUG ("stub implementation, failing"); return FALSE; } static GibberFdIOResult gibber_unix_transport_read (GibberFdTransport *transport, GIOChannel *channel, GError **error) { return gibber_fd_transport_read (transport, channel, error); } #endif /* OSs where we have no implementation of credentials */ #endif /* G_OS_UNIX */