/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* gkr-pam-client.h - Simple code for communicating with daemon Copyright (C) 2007 Stef Walter The Gnome Keyring Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The Gnome Keyring 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the Gnome Library; see the file COPYING.LIB. If not, . Author: Stef Walter */ #include "config.h" #include "gkr-pam.h" #include "egg/egg-unix-credentials.h" #include "egg/egg-buffer.h" #include "gkd-control-codes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(HAVE_GETPEERUCRED) #include #endif #if defined(LOCAL_PEERCRED) #include #include #endif static int check_peer_same_uid (struct passwd *pwd, int sock) { uid_t uid = -1; /* * Certain OS require a message to be sent over the unix socket for the * otherside to get the process credentials. Most uncool. * * The normal gnome-keyring protocol accomodates this and the client * sends a message/byte before sending anything else. This only works * for the daemon verifying the client. * * This code here is used by a client to verify the daemon is running * as the right user. Since we cannot modify the protocol, this only * works on OSs that can do this credentials lookup transparently. */ /* Linux */ #if defined(SO_PEERCRED) struct ucred cr; socklen_t cr_len = sizeof (cr); if (getsockopt (sock, SOL_SOCKET, SO_PEERCRED, &cr, &cr_len) == 0 && cr_len == sizeof (cr)) { uid = cr.uid; } else { syslog (GKR_LOG_ERR, "could not get gnome-keyring-daemon socket credentials, " "(returned len %d/%d)\n", cr_len, (int) sizeof (cr)); return -1; } /* The BSDs */ #elif defined(LOCAL_PEERCRED) uid_t gid; struct xucred xuc; socklen_t xuc_len = sizeof (xuc); if (getsockopt (sock, SOL_SOCKET, LOCAL_PEERCRED, &xuc, &xuc_len) == 0 && xuc_len == sizeof (xuc)) { uid = xuc.cr_uid; } else { syslog (GKR_LOG_ERR, "could not get gnome-keyring-daemon socket credentials, " "(returned len %d/%d)\n", xuc_len, (int)sizeof (xuc)); return -1; } /* NOTE: Add more here */ #else syslog (GKR_LOG_WARN, "Cannot verify that the process to which we are passing the login" " password is genuinely running as the same user login: not supported on this OS."); uid = pwd->pw_uid; #endif if (uid != pwd->pw_uid) { syslog (GKR_LOG_ERR, "The gnome keyring socket is not running with the same " "credentials as the user login. Disconnecting."); return 0; } return 1; } static int write_credentials_byte (int sock) { for (;;) { if (egg_unix_credentials_write (sock) < 0) { if (errno == EINTR || errno == EAGAIN) continue; syslog (GKR_LOG_ERR, "couldn't send credentials to daemon: %s", strerror (errno)); return -1; } break; } return 0; } static int lookup_daemon (struct passwd *pwd, const char *control, struct sockaddr_un *addr) { struct stat st; if (strlen (control) + 1 > sizeof (addr->sun_path)) { syslog (GKR_LOG_ERR, "gkr-pam: address is too long for unix socket path: %s", control); return GKD_CONTROL_RESULT_FAILED; } memset (addr, 0, sizeof (*addr)); addr->sun_family = AF_UNIX; strcpy (addr->sun_path, control); /* A bunch of checks to make sure nothing funny is going on */ if (lstat (addr->sun_path, &st) < 0) { if (errno == ENOENT) return GKD_CONTROL_RESULT_NO_DAEMON; syslog (GKR_LOG_ERR, "Couldn't access gnome keyring socket: %s: %s", addr->sun_path, strerror (errno)); return GKD_CONTROL_RESULT_FAILED; } if (st.st_uid != pwd->pw_uid) { syslog (GKR_LOG_ERR, "The gnome keyring socket is not owned with the same " "credentials as the user login: %s", addr->sun_path); return GKD_CONTROL_RESULT_FAILED; } if (S_ISLNK(st.st_mode) || !S_ISSOCK(st.st_mode)) { syslog (GKR_LOG_ERR, "The gnome keyring socket is not a valid simple " "non-linked socket"); return GKD_CONTROL_RESULT_FAILED; } return GKD_CONTROL_RESULT_OK; } static int connect_daemon (struct passwd *pwd, struct sockaddr_un *addr, int *out_sock) { int sock; /* Now we connect */ sock = socket (AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { syslog (GKR_LOG_ERR, "couldn't create control socket: %s", strerror (errno)); return GKD_CONTROL_RESULT_FAILED; } /* close on exec */ fcntl (sock, F_SETFD, 1); if (connect (sock, (struct sockaddr *)addr, sizeof (*addr)) < 0) { if (errno == ECONNREFUSED) { close (sock); return GKD_CONTROL_RESULT_NO_DAEMON; } syslog (GKR_LOG_ERR, "couldn't connect to gnome-keyring-daemon socket at: %s: %s", addr->sun_path, strerror (errno)); close (sock); return GKD_CONTROL_RESULT_FAILED; } /* Verify the server is running as the right user */ if (check_peer_same_uid (pwd, sock) <= 0) { close (sock); return GKD_CONTROL_RESULT_FAILED; } /* This lets the server verify us */ if (write_credentials_byte (sock) < 0) { close (sock); return GKD_CONTROL_RESULT_FAILED; } *out_sock = sock; return GKD_CONTROL_RESULT_OK; } static void write_part (int fd, const unsigned char *data, int len, int *res) { assert (res); /* Already an error present */ if (*res != GKD_CONTROL_RESULT_OK) return; assert (data); while (len > 0) { int r = write (fd, data, len); if (r < 0) { if (errno == EAGAIN) continue; syslog (GKR_LOG_ERR, "couldn't send data to gnome-keyring-daemon: %s", strerror (errno)); *res = GKD_CONTROL_RESULT_FAILED; return; } data += r; len -= r; } } static int read_part (int fd, unsigned char *data, int len, int disconnect_ok) { int r, all; all = len; while (len > 0) { r = read (fd, data, len); if (r < 0) { if (errno == EAGAIN) continue; if (errno == ECONNRESET && disconnect_ok) return 0; syslog (GKR_LOG_ERR, "couldn't read data from gnome-keyring-daemon: %s", strerror (errno)); return -1; } if (r == 0) { if (disconnect_ok) return 0; syslog (GKR_LOG_ERR, "couldn't read data from gnome-keyring-daemon: %s", "unexpected end of data"); return -1; } data += r; len -= r; } return all; } static int keyring_daemon_op (struct passwd *pwd, struct sockaddr_un *addr, int op, int argc, const char *argv[]) { int ret = GKD_CONTROL_RESULT_OK; unsigned char buf[4]; int want_disconnect; int i, sock = -1; uint oplen, l; assert (addr); /* * We only support operations with zero or more strings * and an empty (only result code) return. */ assert (op == GKD_CONTROL_OP_CHANGE || op == GKD_CONTROL_OP_UNLOCK || op == GKD_CONTROL_OP_QUIT); ret = connect_daemon (pwd, addr, &sock); if (ret != GKD_CONTROL_RESULT_OK) goto done; /* Calculate the packet length */ oplen = 8; /* The packet size, and op code */ for (i = 0; i < argc; ++i) oplen += 4 + strlen (argv[i]); /* Write out the length, and op */ egg_buffer_encode_uint32 (buf, oplen); write_part (sock, buf, 4, &ret); egg_buffer_encode_uint32 (buf, op); write_part (sock, buf, 4, &ret); /* And now the arguments */ for (i = 0; i < argc; ++i) { if (argv[i] == NULL) l = 0x7FFFFFFF; else l = strlen (argv[i]); egg_buffer_encode_uint32 (buf, l); write_part (sock, buf, 4, &ret); if (argv[i] != NULL) write_part (sock, (unsigned char*)argv[i], l, &ret); } if (ret != GKD_CONTROL_RESULT_OK) goto done; /* * If we're asking the daemon to quit, then we expect * disconnects after we send the initial request */ want_disconnect = (op == GKD_CONTROL_OP_QUIT); /* Read the response length */ if (read_part (sock, buf, 4, want_disconnect) != 4) { ret = GKD_CONTROL_RESULT_FAILED; goto done; } /* We only support simple responses */ l = egg_buffer_decode_uint32 (buf); if (l != 8) { syslog (GKR_LOG_ERR, "invalid length response from gnome-keyring-daemon: %d", l); ret = GKD_CONTROL_RESULT_FAILED; goto done; } if (read_part (sock, buf, 4, want_disconnect) != 4) { ret = GKD_CONTROL_RESULT_FAILED; goto done; } ret = egg_buffer_decode_uint32 (buf); /* * If we asked the daemon to quit, wait for it to disconnect * by waiting until the socket disconnects from the other end. */ if (want_disconnect) { while (read (sock, buf, 4) > 0); } done: if (sock >= 0) close (sock); return ret; } int gkr_pam_client_run_operation (struct passwd *pwd, const char *control, int op, int argc, const char* argv[]) { struct sigaction ignpipe, oldpipe, defchld, oldchld; struct sockaddr_un addr; int res; pid_t pid; int status; /* Make dumb signals go away */ memset (&ignpipe, 0, sizeof (ignpipe)); memset (&oldpipe, 0, sizeof (oldpipe)); ignpipe.sa_handler = SIG_IGN; sigaction (SIGPIPE, &ignpipe, &oldpipe); memset (&defchld, 0, sizeof (defchld)); memset (&oldchld, 0, sizeof (oldchld)); defchld.sa_handler = SIG_DFL; sigaction (SIGCHLD, &defchld, &oldchld); res = lookup_daemon (pwd, control, &addr); if (res != GKD_CONTROL_RESULT_OK) goto out; if (pwd->pw_uid == getuid () && pwd->pw_gid == getgid () && pwd->pw_uid == geteuid () && pwd->pw_gid == getegid ()) { /* Already running as the right user, simple */ res = keyring_daemon_op (pwd, &addr, op, argc, argv); } else { /* Otherwise run a child process to do the dirty work */ switch (pid = fork ()) { case -1: syslog (GKR_LOG_ERR, "gkr-pam: couldn't fork: %s", strerror (errno)); res = GKD_CONTROL_RESULT_FAILED; break; case 0: /* Setup process credentials */ if (setgid (pwd->pw_gid) < 0 || setuid (pwd->pw_uid) < 0 || setegid (pwd->pw_gid) < 0 || seteuid (pwd->pw_uid) < 0) { syslog (GKR_LOG_ERR, "gkr-pam: couldn't switch to user: %s: %s", pwd->pw_name, strerror (errno)); exit (GKD_CONTROL_RESULT_FAILED); } res = keyring_daemon_op (pwd, &addr, op, argc, argv); exit (res); return 0; /* Never reached */ default: /* wait for child process */ if (wait (&status) != pid) { syslog (GKR_LOG_ERR, "gkr-pam: couldn't wait on child process: %s", strerror (errno)); res = GKD_CONTROL_RESULT_FAILED; } res = WEXITSTATUS (status); break; }; } out: sigaction (SIGCHLD, &oldchld, NULL); sigaction (SIGPIPE, &oldpipe, NULL); return res; }