/*
* 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 .
*/
#include "config.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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