summaryrefslogtreecommitdiffstats
path: root/src/sss_client/common.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sss_client/common.c')
-rw-r--r--src/sss_client/common.c1445
1 files changed, 1445 insertions, 0 deletions
diff --git a/src/sss_client/common.c b/src/sss_client/common.c
new file mode 100644
index 0000000..702d059
--- /dev/null
+++ b/src/sss_client/common.c
@@ -0,0 +1,1445 @@
+/*
+ * System Security Services Daemon. NSS client interface
+ *
+ * Copyright (C) Simo Sorce 2007
+ *
+ * Winbind derived code:
+ * Copyright (C) Tim Potter 2000
+ * Copyright (C) Andrew Tridgell 2000
+ * Copyright (C) Andrew Bartlett 2002
+ *
+ * This program 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 program 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 program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <nss.h>
+#include <security/pam_modules.h>
+#include <errno.h>
+#include <stdatomic.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <time.h>
+
+#include <libintl.h>
+#define _(STRING) dgettext (PACKAGE, STRING)
+#include "sss_cli.h"
+#include "common_private.h"
+#include "util/util_errors.h"
+
+/*
+* Note we set MSG_NOSIGNAL to avoid
+* having to fiddle with signal masks
+* but also do not want to die in case
+* SIGPIPE gets raised and the application
+* does not handle it.
+*/
+#ifdef MSG_NOSIGNAL
+#define SSS_DEFAULT_WRITE_FLAGS MSG_NOSIGNAL
+#else
+#define SSS_DEFAULT_WRITE_FLAGS 0
+#endif
+
+/* common functions */
+
+static int sss_cli_sd_get(void);
+static void sss_cli_sd_set(int sd);
+static const struct stat *sss_cli_sb_get(void);
+static int sss_cli_sb_set_by_sd(int sd);
+
+#ifdef HAVE_PTHREAD_EXT
+static pthread_key_t sss_sd_key;
+static pthread_once_t sss_sd_key_init = PTHREAD_ONCE_INIT;
+static atomic_bool sss_sd_key_initialized = false;
+struct sss_socket_descriptor_t {
+ int sd;
+ struct stat sb;
+};
+#else
+static int _sss_cli_sd = -1; /* the sss client socket descriptor */
+static struct stat _sss_cli_sb; /* the sss client stat buffer */
+#endif
+
+void sss_cli_close_socket(void)
+{
+ int sd = sss_cli_sd_get();
+
+ if (sd != -1) {
+ close(sd);
+ sss_cli_sd_set(-1);
+ }
+}
+
+#ifdef HAVE_PTHREAD_EXT
+static void sss_at_thread_exit(void *v)
+{
+ sss_cli_close_socket();
+ free(v);
+ pthread_setspecific(sss_sd_key, NULL);
+}
+
+static void init_sd_key(void)
+{
+ if (pthread_key_create(&sss_sd_key, sss_at_thread_exit) == 0) {
+ sss_sd_key_initialized = true;
+ }
+}
+#endif
+
+#if HAVE_FUNCTION_ATTRIBUTE_DESTRUCTOR
+__attribute__((destructor)) void sss_at_lib_unload(void)
+{
+ sss_cli_close_socket();
+#ifdef HAVE_PTHREAD_EXT
+ if (sss_sd_key_initialized) {
+ sss_sd_key_initialized = false;
+ free(pthread_getspecific(sss_sd_key));
+ pthread_setspecific(sss_sd_key, NULL);
+ pthread_key_delete(sss_sd_key);
+ }
+#endif
+}
+#endif
+
+
+/* Requests:
+ *
+ * byte 0-3: 32bit unsigned with length (the complete packet length: 0 to X)
+ * byte 4-7: 32bit unsigned with command code
+ * byte 8-11: 32bit unsigned (reserved)
+ * byte 12-15: 32bit unsigned (reserved)
+ * byte 16-X: (optional) request structure associated to the command code used
+ */
+static enum sss_status sss_cli_send_req(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ int timeout,
+ int *errnop)
+{
+ uint32_t header[4];
+ size_t datasent;
+
+ header[0] = SSS_NSS_HEADER_SIZE + (rd?rd->len:0);
+ header[1] = cmd;
+ header[2] = 0;
+ header[3] = 0;
+
+ datasent = 0;
+
+ while (datasent < header[0]) {
+ struct pollfd pfd;
+ int rdsent;
+ int res, error;
+
+ *errnop = 0;
+ pfd.fd = sss_cli_sd_get();
+ pfd.events = POLLOUT;
+
+ do {
+ errno = 0;
+ res = poll(&pfd, 1, timeout);
+ error = errno;
+
+ /* If error is EINTR here, we'll try again
+ * If it's any other error, we'll catch it
+ * below.
+ */
+ } while (error == EINTR);
+
+ switch (res) {
+ case -1:
+ *errnop = error;
+ break;
+ case 0:
+ *errnop = ETIME;
+ break;
+ case 1:
+ if (pfd.revents & (POLLERR | POLLHUP)) {
+ *errnop = EPIPE;
+ } else if (pfd.revents & POLLNVAL) {
+ /* Invalid request: fd is not opened */
+ sss_cli_sd_set(-1);
+ *errnop = EPIPE;
+ } else if (!(pfd.revents & POLLOUT)) {
+ *errnop = EBUSY;
+ }
+ break;
+ default: /* more than one available!? */
+ *errnop = EBADF;
+ break;
+ }
+ if (*errnop) {
+ sss_cli_close_socket();
+ return SSS_STATUS_UNAVAIL;
+ }
+
+ errno = 0;
+ if (datasent < SSS_NSS_HEADER_SIZE) {
+ res = send(sss_cli_sd_get(),
+ (char *)header + datasent,
+ SSS_NSS_HEADER_SIZE - datasent,
+ SSS_DEFAULT_WRITE_FLAGS);
+ } else {
+ rdsent = datasent - SSS_NSS_HEADER_SIZE;
+ res = send(sss_cli_sd_get(),
+ (const char *)rd->data + rdsent,
+ rd->len - rdsent,
+ SSS_DEFAULT_WRITE_FLAGS);
+ }
+ error = errno;
+
+ if ((res == -1) || (res == 0)) {
+ if ((error == EINTR) || error == EAGAIN) {
+ /* If the write was interrupted, go back through
+ * the loop and try again
+ */
+ continue;
+ }
+
+ /* Write failed */
+ sss_cli_close_socket();
+ *errnop = error;
+ return SSS_STATUS_UNAVAIL;
+ }
+
+ datasent += res;
+ }
+
+ return SSS_STATUS_SUCCESS;
+}
+
+/* Replies:
+ *
+ * byte 0-3: 32bit unsigned with length (the complete packet length: 0 to X)
+ * byte 4-7: 32bit unsigned with command code
+ * byte 8-11: 32bit unsigned with the request status (server errno)
+ * byte 12-15: 32bit unsigned (reserved)
+ * byte 16-X: (optional) reply structure associated to the command code used
+ */
+
+static enum sss_status sss_cli_recv_rep(enum sss_cli_command cmd,
+ int timeout,
+ uint8_t **_buf, int *_len,
+ int *errnop)
+{
+ uint32_t header[4];
+ size_t datarecv;
+ uint8_t *buf = NULL;
+ bool pollhup = false;
+ int len;
+ int ret;
+
+ header[0] = SSS_NSS_HEADER_SIZE; /* until we know the real length */
+ header[1] = 0;
+ header[2] = 0;
+ header[3] = 0;
+
+ datarecv = 0;
+ buf = NULL;
+ len = 0;
+ *errnop = 0;
+
+ while (datarecv < header[0]) {
+ struct pollfd pfd;
+ int bufrecv;
+ int res, error;
+
+ pfd.fd = sss_cli_sd_get();
+ pfd.events = POLLIN;
+
+ do {
+ errno = 0;
+ res = poll(&pfd, 1, timeout);
+ error = errno;
+
+ /* If error is EINTR here, we'll try again
+ * If it's any other error, we'll catch it
+ * below.
+ */
+ } while (error == EINTR);
+
+ switch (res) {
+ case -1:
+ *errnop = error;
+ break;
+ case 0:
+ *errnop = ETIME;
+ break;
+ case 1:
+ if (pfd.revents & (POLLHUP)) {
+ pollhup = true;
+ }
+ if (pfd.revents & POLLERR) {
+ *errnop = EPIPE;
+ } else if (pfd.revents & POLLNVAL) {
+ /* Invalid request: fd is not opened */
+ sss_cli_sd_set(-1);
+ *errnop = EPIPE;
+ } else if (!(pfd.revents & POLLIN)) {
+ *errnop = EBUSY;
+ }
+ break;
+ default: /* more than one available!? */
+ *errnop = EBADF;
+ break;
+ }
+ if (*errnop) {
+ sss_cli_close_socket();
+ ret = SSS_STATUS_UNAVAIL;
+ goto failed;
+ }
+
+ errno = 0;
+ if (datarecv < SSS_NSS_HEADER_SIZE) {
+ res = read(sss_cli_sd_get(),
+ (char *)header + datarecv,
+ SSS_NSS_HEADER_SIZE - datarecv);
+ } else {
+ bufrecv = datarecv - SSS_NSS_HEADER_SIZE;
+ res = read(sss_cli_sd_get(),
+ (char *) buf + bufrecv,
+ header[0] - datarecv);
+ }
+ error = errno;
+
+ if ((res == -1) || (res == 0)) {
+ if ((error == EINTR) || error == EAGAIN) {
+ /* If the read was interrupted, go back through
+ * the loop and try again
+ */
+ continue;
+ }
+
+ /* Read failed. I think the only useful thing
+ * we can do here is just return -1 and fail
+ * since the transaction has failed half way
+ * through. */
+
+ sss_cli_close_socket();
+ *errnop = error;
+ ret = SSS_STATUS_UNAVAIL;
+ goto failed;
+ }
+
+ datarecv += res;
+
+ if (datarecv == SSS_NSS_HEADER_SIZE && len == 0) {
+ /* at this point recv buf is not yet
+ * allocated and the header has just
+ * been read, do checks and proceed */
+ if (header[2] != 0) {
+ /* server side error */
+ sss_cli_close_socket();
+ *errnop = header[2];
+ if (*errnop == EAGAIN) {
+ ret = SSS_STATUS_TRYAGAIN;
+ goto failed;
+ } else {
+ ret = SSS_STATUS_UNAVAIL;
+ goto failed;
+ }
+ }
+ if (header[1] != cmd) {
+ /* wrong command id */
+ sss_cli_close_socket();
+ *errnop = EBADMSG;
+ ret = SSS_STATUS_UNAVAIL;
+ goto failed;
+ }
+ if (header[0] > SSS_NSS_HEADER_SIZE) {
+ len = header[0] - SSS_NSS_HEADER_SIZE;
+ buf = malloc(len);
+ if (!buf) {
+ sss_cli_close_socket();
+ *errnop = ENOMEM;
+ ret = SSS_STATUS_UNAVAIL;
+ goto failed;
+ }
+ }
+ }
+ }
+
+ if (pollhup) {
+ sss_cli_close_socket();
+ }
+
+ *_len = len;
+ *_buf = buf;
+
+ return SSS_STATUS_SUCCESS;
+
+failed:
+ free(buf);
+ return ret;
+}
+
+/* this function will check command codes match and returned length is ok */
+/* repbuf and replen report only the data section not the header */
+static enum sss_status sss_cli_make_request_nochecks(
+ enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ int timeout,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop)
+{
+ enum sss_status ret;
+ uint8_t *buf = NULL;
+ int len = 0;
+
+ /* send data */
+ ret = sss_cli_send_req(cmd, rd, timeout, errnop);
+ if (ret != SSS_STATUS_SUCCESS) {
+ return ret;
+ }
+
+ /* data sent, now get reply */
+ ret = sss_cli_recv_rep(cmd, timeout, &buf, &len, errnop);
+ if (ret != SSS_STATUS_SUCCESS) {
+ return ret;
+ }
+
+ /* we got through, now we have the custom data in buf if any,
+ * return it if requested */
+ if (repbuf && buf) {
+ *repbuf = buf;
+ if (replen) {
+ *replen = len;
+ }
+ } else {
+ free(buf);
+ if (replen) {
+ *replen = 0;
+ }
+ }
+
+ return SSS_STATUS_SUCCESS;
+}
+
+/* GET_VERSION Reply:
+ * 0-3: 32bit unsigned version number
+ */
+
+static bool sss_cli_check_version(const char *socket_name, int timeout)
+{
+ uint8_t *repbuf = NULL;
+ size_t replen;
+ enum sss_status nret;
+ int errnop;
+ uint32_t expected_version;
+ uint32_t obtained_version;
+ struct sss_cli_req_data req;
+
+ if (strcmp(socket_name, SSS_NSS_SOCKET_NAME) == 0) {
+ expected_version = SSS_NSS_PROTOCOL_VERSION;
+ } else if (strcmp(socket_name, SSS_PAM_SOCKET_NAME) == 0 ||
+ strcmp(socket_name, SSS_PAM_PRIV_SOCKET_NAME) == 0) {
+ expected_version = SSS_PAM_PROTOCOL_VERSION;
+ } else if (strcmp(socket_name, SSS_SUDO_SOCKET_NAME) == 0) {
+ expected_version = SSS_SUDO_PROTOCOL_VERSION;
+ } else if (strcmp(socket_name, SSS_AUTOFS_SOCKET_NAME) == 0) {
+ expected_version = SSS_AUTOFS_PROTOCOL_VERSION;
+ } else if (strcmp(socket_name, SSS_SSH_SOCKET_NAME) == 0) {
+ expected_version = SSS_SSH_PROTOCOL_VERSION;
+ } else if (strcmp(socket_name, SSS_PAC_SOCKET_NAME) == 0) {
+ expected_version = SSS_PAC_PROTOCOL_VERSION;
+ } else {
+ return false;
+ }
+
+ req.len = sizeof(expected_version);
+ req.data = &expected_version;
+
+ nret = sss_cli_make_request_nochecks(SSS_GET_VERSION, &req, timeout,
+ &repbuf, &replen, &errnop);
+ if (nret != SSS_STATUS_SUCCESS) {
+ return false;
+ }
+
+ if (!repbuf) {
+ return false;
+ }
+
+ SAFEALIGN_COPY_UINT32(&obtained_version, repbuf, NULL);
+ free(repbuf);
+
+ return (obtained_version == expected_version);
+}
+
+/* this 2 functions are adapted from samba3 winbind's wb_common.c */
+
+/* Make sure socket handle isn't stdin (0), stdout(1) or stderr(2) by setting
+ * the limit to 3 */
+#define RECURSION_LIMIT 3
+
+static int make_nonstd_fd_internals(int fd, int limit)
+{
+ int new_fd;
+ if (fd >= 0 && fd <= 2) {
+#ifdef F_DUPFD
+ if ((new_fd = fcntl(fd, F_DUPFD, 3)) == -1) {
+ return -1;
+ }
+ /* Paranoia */
+ if (new_fd < 3) {
+ close(new_fd);
+ return -1;
+ }
+ close(fd);
+ return new_fd;
+#else
+ if (limit <= 0)
+ return -1;
+
+ new_fd = dup(fd);
+ if (new_fd == -1)
+ return -1;
+
+ /* use the program stack to hold our list of FDs to close */
+ new_fd = make_nonstd_fd_internals(new_fd, limit - 1);
+ close(fd);
+ return new_fd;
+#endif
+ }
+ return fd;
+}
+
+/****************************************************************************
+ Ensures fd isn't std[in/out/err] (duplicates it if needed) and
+ set it into nonblocking mode. Uses POSIX O_NONBLOCK if available,
+ else
+ if SYSV use O_NDELAY
+ if BSD use FNDELAY
+ Set close on exec also.
+****************************************************************************/
+
+static int make_safe_fd(int fd)
+{
+ int result, flags;
+ int new_fd = make_nonstd_fd_internals(fd, RECURSION_LIMIT);
+ if (new_fd == -1) {
+ close(fd);
+ return -1;
+ }
+
+ /* Socket should be nonblocking. */
+#ifdef O_NONBLOCK
+#define FLAG_TO_SET O_NONBLOCK
+#else
+#ifdef SYSV
+#define FLAG_TO_SET O_NDELAY
+#else /* BSD */
+#define FLAG_TO_SET FNDELAY
+#endif
+#endif
+
+ if ((flags = fcntl(new_fd, F_GETFL)) == -1) {
+ close(new_fd);
+ return -1;
+ }
+
+ flags |= FLAG_TO_SET;
+ if (fcntl(new_fd, F_SETFL, flags) == -1) {
+ close(new_fd);
+ return -1;
+ }
+
+#undef FLAG_TO_SET
+
+ /* Socket should be closed on exec() */
+#ifdef FD_CLOEXEC
+ result = flags = fcntl(new_fd, F_GETFD, 0);
+ if (flags >= 0) {
+ flags |= FD_CLOEXEC;
+ result = fcntl( new_fd, F_SETFD, flags );
+ }
+ if (result < 0) {
+ close(new_fd);
+ return -1;
+ }
+#endif
+ return new_fd;
+}
+
+static int sss_cli_open_socket(int *errnop, const char *socket_name, int timeout)
+{
+ struct sockaddr_un nssaddr;
+ bool inprogress = true;
+ bool connected = false;
+ unsigned int wait_time;
+ unsigned int sleep_time;
+ time_t start_time = time(NULL);
+ int ret;
+ int sd;
+
+ if (sizeof(nssaddr.sun_path) < strlen(socket_name) + 1) {
+ *errnop = EINVAL;
+ return -1;
+ }
+
+ memset(&nssaddr, 0, sizeof(struct sockaddr_un));
+ nssaddr.sun_family = AF_UNIX;
+ strcpy(nssaddr.sun_path, socket_name); /* safe due to above check */
+
+ sd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sd == -1) {
+ *errnop = errno;
+ return -1;
+ }
+
+ /* set as non-blocking, close on exec, and make sure standard
+ * descriptors are not used */
+ sd = make_safe_fd(sd);
+ if (sd == -1) {
+ *errnop = errno;
+ return -1;
+ }
+
+ /* this piece is adapted from winbind client code */
+ wait_time = 0;
+ sleep_time = 0;
+ while (inprogress) {
+ int connect_errno = 0;
+ socklen_t errnosize;
+ struct pollfd pfd;
+
+ wait_time += sleep_time * 1000;
+
+ ret = connect(sd, (struct sockaddr *)&nssaddr,
+ sizeof(nssaddr));
+ if (ret == 0) {
+ connected = true;
+ break;
+ }
+
+ switch(errno) {
+ case EINPROGRESS:
+ pfd.fd = sd;
+ pfd.events = POLLOUT;
+
+ ret = poll(&pfd, 1, timeout - wait_time);
+
+ if (ret > 0) {
+ errnosize = sizeof(connect_errno);
+ ret = getsockopt(sd, SOL_SOCKET, SO_ERROR,
+ &connect_errno, &errnosize);
+ if (ret >= 0 && connect_errno == 0) {
+ connected = true;
+ break;
+ }
+ }
+ wait_time = time(NULL) - start_time;
+ break;
+ case EAGAIN:
+ if (wait_time < timeout) {
+ sleep_time = 1;
+ sleep(sleep_time);
+ }
+ break;
+ default:
+ *errnop = errno;
+ inprogress = false;
+ break;
+ }
+
+ if (wait_time >= timeout) {
+ inprogress = false;
+ }
+
+ if (connected) {
+ inprogress = false;
+ }
+ }
+
+ if (!connected) {
+ close(sd);
+ return -1;
+ }
+
+ ret = sss_cli_sb_set_by_sd(sd);
+ if (ret != 0) {
+ close(sd);
+ return -1;
+ }
+
+ return sd;
+}
+
+static enum sss_status sss_cli_check_socket(int *errnop,
+ const char *socket_name,
+ int timeout)
+{
+ static pid_t mypid_s;
+ static ino_t myself_ino;
+ struct stat mypid_sb, myself_sb;
+ const struct stat *sss_cli_sb = NULL;
+ pid_t mypid_d;
+ int mysd;
+ int ret;
+#ifdef HAVE_PTHREAD_EXT
+ struct sss_socket_descriptor_t *descriptor = NULL;
+
+ ret = pthread_once(&sss_sd_key_init, init_sd_key); /* once for all threads */
+ if (ret != 0) {
+ *errnop = EFAULT;
+ return SSS_STATUS_UNAVAIL;
+ }
+ if (!sss_sd_key_initialized) {
+ /* pthread_once::init_sd_key::pthread_key_create failed -- game over? */
+ *errnop = EFAULT;
+ return SSS_STATUS_UNAVAIL;
+ }
+
+ if (pthread_getspecific(sss_sd_key) == NULL) { /* for every thread */
+ descriptor = (struct sss_socket_descriptor_t *)
+ calloc(1, sizeof(struct sss_socket_descriptor_t));
+ if (descriptor == NULL) {
+ *errnop = ENOMEM;
+ return SSS_STATUS_UNAVAIL;
+ }
+ descriptor->sd = -1;
+ ret = pthread_setspecific(sss_sd_key, descriptor);
+ if (ret != 0) {
+ free(descriptor);
+ *errnop = ENOMEM;
+ return SSS_STATUS_UNAVAIL;
+ }
+ }
+#endif
+ sss_cli_sb = sss_cli_sb_get();
+ if (sss_cli_sb == NULL) {
+ *errnop = EFAULT;
+ return SSS_STATUS_UNAVAIL;
+ }
+
+ ret = lstat("/proc/self/", &myself_sb);
+ mypid_d = getpid();
+ if (mypid_d != mypid_s || (ret == 0 && myself_sb.st_ino != myself_ino)) {
+ ret = fstat(sss_cli_sd_get(), &mypid_sb);
+ if (ret == 0) {
+ if (S_ISSOCK(mypid_sb.st_mode) &&
+ mypid_sb.st_dev == sss_cli_sb->st_dev &&
+ mypid_sb.st_ino == sss_cli_sb->st_ino) {
+ sss_cli_close_socket();
+ }
+ }
+ sss_cli_sd_set(-1);
+ mypid_s = mypid_d;
+ myself_ino = myself_sb.st_ino;
+ }
+
+ /* check if the socket has been hijacked */
+ if (sss_cli_sd_get() != -1) {
+ ret = fstat(sss_cli_sd_get(), &mypid_sb);
+ if ((ret != 0) || (!S_ISSOCK(mypid_sb.st_mode))
+ || (mypid_sb.st_dev != sss_cli_sb->st_dev)
+ || (mypid_sb.st_ino != sss_cli_sb->st_ino)) {
+ sss_cli_sd_set(-1); /* don't ruin app even if it's misbehaving */
+ }
+ }
+
+ /* check if the socket has been closed on the other side */
+ if (sss_cli_sd_get() != -1) {
+ struct pollfd pfd;
+ int res, error;
+
+ *errnop = 0;
+ pfd.fd = sss_cli_sd_get();
+ pfd.events = POLLIN | POLLOUT;
+
+ do {
+ errno = 0;
+ res = poll(&pfd, 1, timeout);
+ error = errno;
+
+ /* If error is EINTR here, we'll try again
+ * If it's any other error, we'll catch it
+ * below.
+ */
+ } while (error == EINTR);
+
+ switch (res) {
+ case -1:
+ *errnop = error;
+ break;
+ case 0:
+ *errnop = ETIME;
+ break;
+ case 1:
+ if (pfd.revents & (POLLERR | POLLHUP)) {
+ *errnop = EPIPE;
+ } else if (pfd.revents & POLLNVAL) {
+ /* Invalid request: fd is not opened */
+ sss_cli_sd_set(-1);
+ *errnop = EPIPE;
+ } else if (!(pfd.revents & (POLLIN | POLLOUT))) {
+ *errnop = EBUSY;
+ }
+ break;
+ default: /* more than one available!? */
+ *errnop = EBADF;
+ break;
+ }
+ if (*errnop == 0) {
+ return SSS_STATUS_SUCCESS;
+ }
+
+ sss_cli_close_socket();
+ }
+
+ mysd = sss_cli_open_socket(errnop, socket_name, timeout);
+ if (mysd == -1) {
+ return SSS_STATUS_UNAVAIL;
+ }
+
+ sss_cli_sd_set(mysd);
+
+ if (sss_cli_check_version(socket_name, timeout)) {
+ return SSS_STATUS_SUCCESS;
+ }
+
+ sss_cli_close_socket();
+ *errnop = EFAULT;
+ return SSS_STATUS_UNAVAIL;
+}
+
+/* this function will check command codes match and returned length is ok */
+/* repbuf and replen report only the data section not the header */
+enum nss_status sss_nss_make_request_timeout(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ int timeout,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop)
+{
+ enum sss_status ret;
+ char *envval;
+
+ /* avoid looping in the nss daemon */
+ envval = getenv("_SSS_LOOPS");
+ if (envval && strcmp(envval, "NO") == 0) {
+ return NSS_STATUS_NOTFOUND;
+ }
+
+ ret = sss_cli_check_socket(errnop, SSS_NSS_SOCKET_NAME, timeout);
+ if (ret != SSS_STATUS_SUCCESS) {
+#ifdef NONSTANDARD_SSS_NSS_BEHAVIOUR
+ *errnop = 0;
+ errno = 0;
+ return NSS_STATUS_NOTFOUND;
+#else
+ return NSS_STATUS_UNAVAIL;
+#endif
+ }
+
+ ret = sss_cli_make_request_nochecks(cmd, rd, timeout, repbuf, replen,
+ errnop);
+ if (ret == SSS_STATUS_UNAVAIL && *errnop == EPIPE) {
+ /* try reopen socket */
+ ret = sss_cli_check_socket(errnop, SSS_NSS_SOCKET_NAME, timeout);
+ if (ret != SSS_STATUS_SUCCESS) {
+#ifdef NONSTANDARD_SSS_NSS_BEHAVIOUR
+ *errnop = 0;
+ errno = 0;
+ return NSS_STATUS_NOTFOUND;
+#else
+ return NSS_STATUS_UNAVAIL;
+#endif
+ }
+
+ /* and make request one more time */
+ ret = sss_cli_make_request_nochecks(cmd, rd, timeout, repbuf, replen,
+ errnop);
+ }
+ switch (ret) {
+ case SSS_STATUS_TRYAGAIN:
+ return NSS_STATUS_TRYAGAIN;
+ case SSS_STATUS_SUCCESS:
+ return NSS_STATUS_SUCCESS;
+ case SSS_STATUS_UNAVAIL:
+ default:
+#ifdef NONSTANDARD_SSS_NSS_BEHAVIOUR
+ *errnop = 0;
+ errno = 0;
+ return NSS_STATUS_NOTFOUND;
+#else
+ return NSS_STATUS_UNAVAIL;
+#endif
+ }
+}
+
+enum nss_status sss_nss_make_request(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop)
+{
+ return sss_nss_make_request_timeout(cmd, rd, SSS_CLI_SOCKET_TIMEOUT,
+ repbuf, replen, errnop);
+}
+
+int sss_pac_check_and_open(void)
+{
+ enum sss_status ret;
+ int errnop;
+
+ ret = sss_cli_check_socket(&errnop, SSS_PAC_SOCKET_NAME,
+ SSS_CLI_SOCKET_TIMEOUT);
+ if (ret != SSS_STATUS_SUCCESS) {
+ return EIO;
+ }
+
+ return EOK;
+}
+
+int sss_pac_make_request(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop)
+{
+ enum sss_status ret;
+ char *envval;
+ int timeout = SSS_CLI_SOCKET_TIMEOUT;
+
+ /* avoid looping in the nss daemon */
+ envval = getenv("_SSS_LOOPS");
+ if (envval && strcmp(envval, "NO") == 0) {
+ return NSS_STATUS_NOTFOUND;
+ }
+
+ ret = sss_cli_check_socket(errnop, SSS_PAC_SOCKET_NAME, timeout);
+ if (ret != SSS_STATUS_SUCCESS) {
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ ret = sss_cli_make_request_nochecks(cmd, rd, timeout, repbuf, replen,
+ errnop);
+ if (ret == SSS_STATUS_UNAVAIL && *errnop == EPIPE) {
+ /* try reopen socket */
+ ret = sss_cli_check_socket(errnop, SSS_PAC_SOCKET_NAME, timeout);
+ if (ret != SSS_STATUS_SUCCESS) {
+ return NSS_STATUS_UNAVAIL;
+ }
+
+ /* and make request one more time */
+ ret = sss_cli_make_request_nochecks(cmd, rd, timeout, repbuf, replen,
+ errnop);
+ }
+ switch (ret) {
+ case SSS_STATUS_TRYAGAIN:
+ return NSS_STATUS_TRYAGAIN;
+ case SSS_STATUS_SUCCESS:
+ return NSS_STATUS_SUCCESS;
+ case SSS_STATUS_UNAVAIL:
+ default:
+ return NSS_STATUS_UNAVAIL;
+ }
+}
+
+int sss_pac_make_request_with_lock(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop)
+{
+ int ret;
+
+ sss_pac_lock();
+
+ ret = sss_pac_make_request(cmd, rd, repbuf, replen, errnop);
+
+ sss_pac_unlock();
+
+ return ret;
+}
+
+errno_t check_server_cred(int sockfd)
+{
+#ifdef HAVE_UCRED
+ int ret;
+ struct ucred server_cred;
+ socklen_t server_cred_len = sizeof(server_cred);
+
+ ret = getsockopt(sockfd, SOL_SOCKET, SO_PEERCRED, &server_cred,
+ &server_cred_len);
+ if (ret != 0) {
+ return errno;
+ }
+
+ if (server_cred_len != sizeof(struct ucred)) {
+ return ESSS_BAD_CRED_MSG;
+ }
+
+ if (server_cred.uid != 0 || server_cred.gid != 0) {
+ return ESSS_SERVER_NOT_TRUSTED;
+ }
+#endif
+ return 0;
+}
+
+int sss_pam_make_request(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop)
+{
+ int ret, statret;
+ errno_t error;
+ enum sss_status status;
+ char *envval;
+ struct stat stat_buf;
+ const char *socket_name;
+ int timeout = SSS_CLI_SOCKET_TIMEOUT;
+
+ sss_pam_lock();
+
+ /* avoid looping in the pam daemon */
+ envval = getenv("_SSS_LOOPS");
+ if (envval && strcmp(envval, "NO") == 0) {
+ ret = PAM_SERVICE_ERR;
+ goto out;
+ }
+
+ /* only UID 0 shall use the privileged pipe */
+ if (getuid() == 0) {
+ socket_name = SSS_PAM_PRIV_SOCKET_NAME;
+ errno = 0;
+ statret = stat(socket_name, &stat_buf);
+ if (statret != 0) {
+ if (errno == ENOENT) {
+ *errnop = ESSS_NO_SOCKET;
+ } else {
+ *errnop = ESSS_SOCKET_STAT_ERROR;
+ }
+ ret = PAM_SERVICE_ERR;
+ goto out;
+ }
+ if ( ! (stat_buf.st_uid == 0 &&
+ stat_buf.st_gid == 0 &&
+ S_ISSOCK(stat_buf.st_mode) &&
+ (stat_buf.st_mode & ~S_IFMT) == 0600 )) {
+ *errnop = ESSS_BAD_PRIV_SOCKET;
+ ret = PAM_SERVICE_ERR;
+ goto out;
+ }
+ } else {
+ socket_name = SSS_PAM_SOCKET_NAME;
+ errno = 0;
+ statret = stat(socket_name, &stat_buf);
+ if (statret != 0) {
+ if (errno == ENOENT) {
+ *errnop = ESSS_NO_SOCKET;
+ } else {
+ *errnop = ESSS_SOCKET_STAT_ERROR;
+ }
+ ret = PAM_SERVICE_ERR;
+ goto out;
+ }
+ if ( ! (stat_buf.st_uid == 0 &&
+ stat_buf.st_gid == 0 &&
+ S_ISSOCK(stat_buf.st_mode) &&
+ (stat_buf.st_mode & ~S_IFMT) == 0666 )) {
+ *errnop = ESSS_BAD_PUB_SOCKET;
+ ret = PAM_SERVICE_ERR;
+ goto out;
+ }
+ }
+
+ status = sss_cli_check_socket(errnop, socket_name, timeout);
+ if (status != SSS_STATUS_SUCCESS) {
+ ret = PAM_SERVICE_ERR;
+ goto out;
+ }
+
+ error = check_server_cred(sss_cli_sd_get());
+ if (error != 0) {
+ sss_cli_close_socket();
+ *errnop = error;
+ ret = PAM_SERVICE_ERR;
+ goto out;
+ }
+
+ status = sss_cli_make_request_nochecks(cmd, rd, timeout, repbuf, replen,
+ errnop);
+ if (status == SSS_STATUS_UNAVAIL && *errnop == EPIPE) {
+ /* try reopen socket */
+ status = sss_cli_check_socket(errnop, socket_name, timeout);
+ if (status != SSS_STATUS_SUCCESS) {
+ ret = PAM_SERVICE_ERR;
+ goto out;
+ }
+
+ /* and make request one more time */
+ status = sss_cli_make_request_nochecks(cmd, rd, timeout, repbuf, replen,
+ errnop);
+ }
+
+ if (status == SSS_STATUS_SUCCESS) {
+ ret = PAM_SUCCESS;
+ } else {
+ ret = PAM_SERVICE_ERR;
+ }
+
+out:
+ sss_pam_unlock();
+ return ret;
+}
+
+enum sss_status
+sss_cli_make_request_with_checks(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ int timeout,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop,
+ const char *socket_name)
+{
+ enum sss_status ret = SSS_STATUS_UNAVAIL;
+
+ ret = sss_cli_check_socket(errnop, socket_name, timeout);
+ if (ret != SSS_STATUS_SUCCESS) {
+ return SSS_STATUS_UNAVAIL;
+ }
+
+ ret = sss_cli_make_request_nochecks(cmd, rd, timeout, repbuf, replen,
+ errnop);
+ if (ret == SSS_STATUS_UNAVAIL && *errnop == EPIPE) {
+ /* try reopen socket */
+ ret = sss_cli_check_socket(errnop, socket_name, timeout);
+ if (ret != SSS_STATUS_SUCCESS) {
+ return SSS_STATUS_UNAVAIL;
+ }
+
+ /* and make request one more time */
+ ret = sss_cli_make_request_nochecks(cmd, rd, timeout, repbuf, replen,
+ errnop);
+ }
+
+ return ret;
+}
+
+int sss_sudo_make_request(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop)
+{
+ return sss_cli_make_request_with_checks(cmd, rd, SSS_CLI_SOCKET_TIMEOUT,
+ repbuf, replen, errnop,
+ SSS_SUDO_SOCKET_NAME);
+}
+
+int sss_autofs_make_request(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop)
+{
+ enum sss_status status;
+
+ status = sss_cli_make_request_with_checks(cmd, rd, SSS_CLI_SOCKET_TIMEOUT,
+ repbuf, replen, errnop,
+ SSS_AUTOFS_SOCKET_NAME);
+
+ if (*errnop == ERR_OFFLINE) {
+ *errnop = EHOSTDOWN;
+ }
+
+ return status;
+}
+
+int sss_ssh_make_request(enum sss_cli_command cmd,
+ struct sss_cli_req_data *rd,
+ uint8_t **repbuf, size_t *replen,
+ int *errnop)
+{
+ return sss_cli_make_request_with_checks(cmd, rd, SSS_CLI_SOCKET_TIMEOUT,
+ repbuf, replen, errnop,
+ SSS_SSH_SOCKET_NAME);
+}
+
+
+const char *ssscli_err2string(int err)
+{
+ const char *m;
+
+ switch(err) {
+ case ESSS_BAD_PRIV_SOCKET:
+ return _("Privileged socket has wrong ownership or permissions.");
+ break;
+ case ESSS_BAD_PUB_SOCKET:
+ return _("Public socket has wrong ownership or permissions.");
+ break;
+ case ESSS_BAD_CRED_MSG:
+ return _("Unexpected format of the server credential message.");
+ break;
+ case ESSS_SERVER_NOT_TRUSTED:
+ return _("SSSD is not run by root.");
+ break;
+ case ESSS_NO_SOCKET:
+ return _("SSSD socket does not exist.");
+ break;
+ case ESSS_SOCKET_STAT_ERROR:
+ return _("Cannot get stat of SSSD socket.");
+ break;
+ default:
+ m = strerror(err);
+ if (m == NULL) {
+ return _("An error occurred, but no description can be found.");
+ }
+ return m;
+ break;
+ }
+
+ return _("Unexpected error while looking for an error description");
+}
+
+/* Return strlen(str) or maxlen, whichever is shorter
+ * Returns EINVAL if str is NULL, EFBIG if str is longer than maxlen
+ * _len will return the result
+ *
+ * This function is useful for preventing buffer overflow attacks.
+ */
+errno_t sss_strnlen(const char *str, size_t maxlen, size_t *len)
+{
+ if (!str) {
+ return EINVAL;
+ }
+
+#if defined __USE_GNU
+ *len = strnlen(str, maxlen);
+#else
+ *len = 0;
+ while (*len < maxlen) {
+ if (str[*len] == '\0') break;
+ (*len)++;
+ }
+#endif
+
+ if (*len == maxlen && str[*len] != '\0') {
+ return EFBIG;
+ }
+
+ return 0;
+}
+
+#if HAVE_PTHREAD
+
+#ifdef HAVE_PTHREAD_EXT
+static bool sss_lock_free = true;
+static pthread_once_t sss_lock_mode_initialized = PTHREAD_ONCE_INIT;
+
+static void init_lock_mode(void)
+{
+ const char *env = getenv("SSS_LOCKFREE");
+
+ if ((env != NULL) && (strcasecmp(env, "NO") == 0)) {
+ sss_lock_free = false;
+ }
+}
+
+bool sss_is_lockfree_mode(void)
+{
+ pthread_once(&sss_lock_mode_initialized, init_lock_mode);
+ return sss_lock_free;
+}
+#endif
+
+struct sss_mutex sss_nss_mtx = { .mtx = PTHREAD_MUTEX_INITIALIZER };
+static struct sss_mutex sss_pam_mtx = { .mtx = PTHREAD_MUTEX_INITIALIZER };
+static struct sss_mutex sss_pac_mtx = { .mtx = PTHREAD_MUTEX_INITIALIZER };
+
+static void sss_mt_lock(struct sss_mutex *m)
+{
+#ifdef HAVE_PTHREAD_EXT
+ if (sss_is_lockfree_mode()) {
+ return;
+ }
+#endif
+
+ pthread_mutex_lock(&m->mtx);
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &m->old_cancel_state);
+}
+
+static void sss_mt_unlock(struct sss_mutex *m)
+{
+#ifdef HAVE_PTHREAD_EXT
+ if (sss_is_lockfree_mode()) {
+ return;
+ }
+#endif
+
+ pthread_setcancelstate(m->old_cancel_state, NULL);
+ pthread_mutex_unlock(&m->mtx);
+}
+
+/* NSS mutex wrappers */
+void sss_nss_lock(void)
+{
+ sss_mt_lock(&sss_nss_mtx);
+}
+void sss_nss_unlock(void)
+{
+ sss_mt_unlock(&sss_nss_mtx);
+}
+
+/* PAM mutex wrappers */
+void sss_pam_lock(void)
+{
+ sss_mt_lock(&sss_pam_mtx);
+}
+void sss_pam_unlock(void)
+{
+ sss_mt_unlock(&sss_pam_mtx);
+}
+
+/* PAC mutex wrappers */
+void sss_pac_lock(void)
+{
+ sss_mt_lock(&sss_pac_mtx);
+}
+void sss_pac_unlock(void)
+{
+ sss_mt_unlock(&sss_pac_mtx);
+}
+
+#else
+
+/* sorry no mutexes available */
+void sss_nss_lock(void) { return; }
+void sss_nss_unlock(void) { return; }
+void sss_pam_lock(void) { return; }
+void sss_pam_unlock(void) { return; }
+void sss_nss_mc_lock(void) { return; }
+void sss_nss_mc_unlock(void) { return; }
+void sss_pac_lock(void) { return; }
+void sss_pac_unlock(void) { return; }
+#endif
+
+
+errno_t sss_readrep_copy_string(const char *in,
+ size_t *offset,
+ size_t *slen,
+ size_t *dlen,
+ char **out,
+ size_t *size)
+{
+ size_t i = 0;
+ while (*slen > *offset && *dlen > 0) {
+ (*out)[i] = in[*offset];
+ if ((*out)[i] == '\0') break;
+ i++;
+ (*offset)++;
+ (*dlen)--;
+ }
+ if (*slen <= *offset) { /* premature end of buf */
+ return EBADMSG;
+ }
+ if (*dlen == 0) { /* not enough memory */
+ return ERANGE; /* not ENOMEM, ERANGE is what glibc looks for */
+ }
+ (*offset)++;
+ (*dlen)--;
+ if (size) {
+ *size = i;
+ }
+
+ return EOK;
+}
+
+#ifdef HAVE_PTHREAD_EXT
+
+static int sss_cli_sd_get(void)
+{
+ if (!sss_sd_key_initialized) {
+ return -1;
+ }
+
+ struct sss_socket_descriptor_t *descriptor = pthread_getspecific(sss_sd_key);
+
+ if (descriptor == NULL) { /* sanity check */
+ return -1;
+ }
+
+ return descriptor->sd;
+}
+
+static void sss_cli_sd_set(int sd)
+{
+ if (!sss_sd_key_initialized) {
+ return;
+ }
+
+ struct sss_socket_descriptor_t *descriptor = pthread_getspecific(sss_sd_key);
+
+ if (descriptor == NULL) { /* sanity check */
+ return;
+ }
+
+ descriptor->sd = sd;
+}
+
+static const struct stat *sss_cli_sb_get(void)
+{
+ if (!sss_sd_key_initialized) {
+ return NULL;
+ }
+
+ struct sss_socket_descriptor_t *descriptor = pthread_getspecific(sss_sd_key);
+
+ if (descriptor == NULL) { /* sanity check */
+ return NULL;
+ }
+
+ return &descriptor->sb;
+}
+
+static int sss_cli_sb_set_by_sd(int sd)
+{
+ if (!sss_sd_key_initialized) {
+ return -1;
+ }
+
+ struct sss_socket_descriptor_t *descriptor = pthread_getspecific(sss_sd_key);
+
+ if (descriptor == NULL) { /* sanity check */
+ return -1;
+ }
+
+ return fstat(sd, &descriptor->sb);
+}
+
+#else
+
+static int sss_cli_sd_get(void)
+{
+ return _sss_cli_sd;
+}
+
+static void sss_cli_sd_set(int sd)
+{
+ _sss_cli_sd = sd;
+}
+
+static const struct stat *sss_cli_sb_get(void)
+{
+ return &_sss_cli_sb;
+}
+
+static int sss_cli_sb_set_by_sd(int sd)
+{
+ return fstat(sd, &_sss_cli_sb);
+}
+
+#endif