diff options
Diffstat (limited to 'src/util')
111 files changed, 26800 insertions, 0 deletions
diff --git a/src/util/atomic_io.c b/src/util/atomic_io.c new file mode 100644 index 0000000..7cc4304 --- /dev/null +++ b/src/util/atomic_io.c @@ -0,0 +1,102 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdint.h> + +#include "util/atomic_io.h" + +/* based on code from libssh <http://www.libssh.org> */ +ssize_t sss_atomic_io_s(int fd, void *buf, size_t n, bool do_read) +{ + char *b = buf; + size_t pos = 0; + ssize_t res; + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = do_read ? POLLIN : POLLOUT; + + while (n > pos) { + if (do_read) { + res = read(fd, b + pos, n - pos); + } else { + res = write(fd, b + pos, n - pos); + } + switch (res) { + case -1: + if (errno == EINTR) { + continue; + } + if (errno == EAGAIN || errno == EWOULDBLOCK) { + (void) poll(&pfd, 1, -1); + continue; + } + return -1; + case 0: + /* read returns 0 on end-of-file */ + errno = do_read ? 0 : EPIPE; + return pos; + default: + pos += (size_t) res; + } + } + + return pos; +} + +ssize_t sss_atomic_write_safe_s(int fd, void *buf, size_t len) +{ + uint32_t ulen = len; + ssize_t ret; + + ret = sss_atomic_write_s(fd, &ulen, sizeof(uint32_t)); + if (ret != sizeof(uint32_t)) { + errno = errno == 0 ? EIO : errno; + return -1; + } + + return sss_atomic_write_s(fd, buf, len); +} + +ssize_t sss_atomic_read_safe_s(int fd, void *buf, size_t buf_len, size_t *_len) +{ + uint32_t ulen = (uint32_t)-1; + ssize_t ret; + + ret = sss_atomic_read_s(fd, &ulen, sizeof(uint32_t)); + if (ret != sizeof(uint32_t)) { + errno = errno == 0 ? EIO : errno; + return -1; + } + + if (ulen > buf_len) { + if (_len != NULL) { + *_len = ulen; + } + errno = ERANGE; + return -1; + } + + if (_len != NULL) { + *_len = ulen; + } + + return sss_atomic_read_s(fd, buf, ulen); +} diff --git a/src/util/atomic_io.h b/src/util/atomic_io.h new file mode 100644 index 0000000..fee0998 --- /dev/null +++ b/src/util/atomic_io.h @@ -0,0 +1,53 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __SSSD_ATOMIC_IO_H__ +#define __SSSD_ATOMIC_IO_H__ + +#include <unistd.h> +#include <stdbool.h> +#include <poll.h> +#include <errno.h> + +/* Performs a read or write operation in an manner that is seemingly atomic + * to the caller. + * + * Please note that the function does not perform any asynchronous operation + * so the operation might potentially block + */ +ssize_t sss_atomic_io_s(int fd, void *buf, size_t n, bool do_read); + +#define sss_atomic_read_s(fd, buf, n) sss_atomic_io_s(fd, buf, n, true) +#define sss_atomic_write_s(fd, buf, n) sss_atomic_io_s(fd, buf, n, false) + +/** + * Write length of the buffer then the buffer itself. + * + * (uint32_t)length + buffer + */ +ssize_t sss_atomic_write_safe_s(int fd, void *buf, size_t len); + +/** + * First, read uint32_t as a message length, then read the rest of the message + * expecting given length. The exact length is returned in _len parameter. + */ +ssize_t sss_atomic_read_safe_s(int fd, void *buf, size_t max_len, size_t *_len); + +#endif /* __SSSD_ATOMIC_IO_H__ */ diff --git a/src/util/auth_utils.h b/src/util/auth_utils.h new file mode 100644 index 0000000..8883c5c --- /dev/null +++ b/src/util/auth_utils.h @@ -0,0 +1,44 @@ +/* + SSSD + + Authentication utility functions + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <errno.h> +#include <security/pam_appl.h> + +static inline int cached_login_pam_status(int auth_res) +{ + switch (auth_res) { + case EOK: + return PAM_SUCCESS; + case ERR_ACCOUNT_UNKNOWN: + return PAM_AUTHINFO_UNAVAIL; + case ERR_NO_CACHED_CREDS: + case ERR_CACHED_CREDS_EXPIRED: + case ERR_AUTH_DENIED: + return PAM_PERM_DENIED; + case ERR_AUTH_FAILED: + return PAM_AUTH_ERR; + default: + return PAM_SYSTEM_ERR; + } +} diff --git a/src/util/authtok-utils.c b/src/util/authtok-utils.c new file mode 100644 index 0000000..ac3c8bb --- /dev/null +++ b/src/util/authtok-utils.c @@ -0,0 +1,277 @@ +/* + SSSD - auth utils helpers + + Copyright (C) Sumit Bose <sbose@redhat.com> 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* This file is use by SSSD clients and the main daemons. Please do not add + * code which is specific to only one of them. */ + +#include <errno.h> + +#include "sss_client/sss_cli.h" +#include "sss_client/pam_message.h" + +errno_t sss_auth_pack_2fa_blob(const char *fa1, size_t fa1_len, + const char *fa2, size_t fa2_len, + uint8_t *buf, size_t buf_len, + size_t *_2fa_blob_len) +{ + size_t c; + uint32_t tmp_uint32_t; + + if (fa1 == NULL || *fa1 == '\0' || fa1_len > UINT32_MAX + || fa2 == NULL || *fa2 == '\0' || fa2_len > UINT32_MAX + || (buf == NULL && buf_len != 0)) { + return EINVAL; + } + + if (fa1_len == 0) { + fa1_len = strlen(fa1); + } else { + if (fa1[fa1_len] != '\0') { + return EINVAL; + } + } + + if (fa2_len == 0) { + fa2_len = strlen(fa2); + } else { + if (fa2[fa2_len] != '\0') { + return EINVAL; + } + } + + *_2fa_blob_len = fa1_len + fa2_len + 2 + 2 * sizeof(uint32_t); + if (buf == NULL || buf_len < *_2fa_blob_len) { + return EAGAIN; + } + + c = 0; + tmp_uint32_t = (uint32_t) fa1_len + 1; + SAFEALIGN_COPY_UINT32(buf, &tmp_uint32_t, &c); + tmp_uint32_t = (uint32_t) fa2_len + 1; + SAFEALIGN_COPY_UINT32(buf + c, &tmp_uint32_t, &c); + + memcpy(buf + c, fa1, fa1_len + 1); + c += fa1_len + 1; + + memcpy(buf + c, fa2, fa2_len + 1); + + return 0; +} + +errno_t sss_auth_passkey_calc_size(const char *uv, + const char *key, + const char *pin, + size_t *_passkey_buf_len) +{ + size_t len = 0; + + if (uv == NULL || key == NULL) { + return EINVAL; + } + + len += strlen(key) + 1; + len += strlen(uv) + 1; + + if (pin != NULL) { + len += strlen(pin) + 1; + } + + *_passkey_buf_len = len; + + return EOK; +} + +errno_t sss_auth_pack_passkey_blob(uint8_t *buf, + const char *uv, + const char *key, + const char *pin) +{ + size_t len = 0; + size_t key_len; + size_t uv_len; + size_t pin_len; + + if (uv == NULL || key == NULL) { + return EINVAL; + } + + uv_len = strlen(uv) + 1; + memcpy(buf + len, uv, uv_len); + len += uv_len; + + key_len = strlen(key) + 1; + memcpy(buf + len, key, key_len); + len += key_len; + + /* Add provided PIN */ + if (pin != NULL) { + pin_len = strlen(pin) + 1; + /* User verification is false */ + } else { + pin = ""; + pin_len = 0; + } + memcpy(buf + len, pin, pin_len); + + return EOK; +} + +errno_t sss_auth_pack_sc_blob(const char *pin, size_t pin_len, + const char *token_name, size_t token_name_len, + const char *module_name, size_t module_name_len, + const char *key_id, size_t key_id_len, + const char *label, size_t label_len, + uint8_t *buf, size_t buf_len, + size_t *_sc_blob_len) +{ + size_t c; + uint32_t tmp_uint32_t; + + if (pin_len > UINT32_MAX || token_name_len > UINT32_MAX + || module_name_len > UINT32_MAX + || (pin_len != 0 && pin == NULL) + || (token_name_len != 0 && token_name == NULL) + || (module_name_len != 0 && module_name == NULL) + || (key_id_len != 0 && key_id == NULL) + || (label_len != 0 && label == NULL)) { + return EINVAL; + } + + /* A missing pin is ok in the case of a reader with a keyboard */ + if (pin == NULL) { + pin = ""; + pin_len = 0; + } + + if (token_name == NULL) { + token_name = ""; + token_name_len = 0; + } + + if (module_name == NULL) { + module_name = ""; + module_name_len = 0; + } + + if (key_id == NULL) { + key_id = ""; + key_id_len = 0; + } + + if (label == NULL) { + label = ""; + label_len = 0; + } + + /* len should not include the trailing \0 */ + if (pin_len == 0 || pin[pin_len - 1] == '\0') { + pin_len = strlen(pin); + } + + if (token_name_len == 0 || token_name[token_name_len - 1] == '\0') { + token_name_len = strlen(token_name); + } + + if (module_name_len == 0 || module_name[module_name_len - 1] == '\0') { + module_name_len = strlen(module_name); + } + + if (key_id_len == 0 || key_id[key_id_len - 1] == '\0') { + key_id_len = strlen(key_id); + } + + if (label_len == 0 || label[label_len - 1] == '\0') { + label_len = strlen(label); + } + + *_sc_blob_len = pin_len + token_name_len + module_name_len + key_id_len + + label_len + 5 + 5 * sizeof(uint32_t); + if (buf == NULL || buf_len < *_sc_blob_len) { + return EAGAIN; + } + + c = 0; + tmp_uint32_t = (uint32_t) pin_len + 1; + SAFEALIGN_COPY_UINT32(buf, &tmp_uint32_t, &c); + tmp_uint32_t = (uint32_t) token_name_len + 1; + SAFEALIGN_COPY_UINT32(buf + c, &tmp_uint32_t, &c); + tmp_uint32_t = (uint32_t) module_name_len + 1; + SAFEALIGN_COPY_UINT32(buf + c, &tmp_uint32_t, &c); + tmp_uint32_t = (uint32_t) key_id_len + 1; + SAFEALIGN_COPY_UINT32(buf + c, &tmp_uint32_t, &c); + tmp_uint32_t = (uint32_t) label_len + 1; + SAFEALIGN_COPY_UINT32(buf + c, &tmp_uint32_t, &c); + + memcpy(buf + c, pin, pin_len); + buf[c + pin_len] = '\0'; + c += pin_len + 1; + + memcpy(buf + c, token_name, token_name_len); + buf[c + token_name_len] = '\0'; + c += token_name_len + 1; + + memcpy(buf + c, module_name, module_name_len); + buf[c + module_name_len] = '\0'; + c += module_name_len + 1; + + memcpy(buf + c, key_id, key_id_len); + buf[c + key_id_len] = '\0'; + c += key_id_len +1; + + memcpy(buf + c, label, label_len); + buf[c + label_len] = '\0'; + + return 0; +} + +const char *sss_auth_get_pin_from_sc_blob(uint8_t *blob, size_t blob_len) +{ + size_t c = 0; + uint32_t pin_len; + uint32_t token_name_len; + uint32_t module_name_len; + uint32_t key_id_len; + uint32_t label_len; + + if (blob == NULL || blob_len == 0) { + return NULL; + } + + SAFEALIGN_COPY_UINT32(&pin_len, blob, &c); + if (pin_len == 0) { + return NULL; + } + + SAFEALIGN_COPY_UINT32(&token_name_len, blob + c, &c); + SAFEALIGN_COPY_UINT32(&module_name_len, blob + c, &c); + SAFEALIGN_COPY_UINT32(&key_id_len, blob + c, &c); + SAFEALIGN_COPY_UINT32(&label_len, blob + c, &c); + + if (blob_len != 5 * sizeof(uint32_t) + pin_len + token_name_len + + module_name_len + key_id_len + + label_len) { + return NULL; + } + + if (blob[c + pin_len - 1] != '\0') { + return NULL; + } + + return (const char *) blob + c; +} diff --git a/src/util/authtok-utils.h b/src/util/authtok-utils.h new file mode 100644 index 0000000..3a7e7c1 --- /dev/null +++ b/src/util/authtok-utils.h @@ -0,0 +1,185 @@ +/* + SSSD - auth utils helpers + + Copyright (C) Sumit Bose <simo@redhat.com> 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __AUTHTOK_UTILS_H__ +#define __AUTHTOK_UTILS_H__ + +#include <talloc.h> + +#include "sss_client/sss_cli.h" +#include "sss_client/pam_message.h" + +/** + * @brief Fill memory buffer with Smartcard authentication blob + * + * @param[in] pin PIN, null terminated + * @param[in] pin_len Length of the PIN, if 0 + * strlen() will be called internally + * @param[in] token_name Token name, null terminated + * @param[in] token_name_len Length of the token name, if 0 + * strlen() will be called internally + * @param[in] module_name Name of PKCS#11 module, null terminated + * @param[in] module_name_len Length of the module name, if 0 + * strlen() will be called internally + * @param[in] key_id Key ID of the certificate + * @param[in] key_id_len Length of the key id of the certificate, if 0 + * strlen() will be called internally + * @param[in] label Label of the certificate + * @param[in] label_len Length of the label of the certificate, if 0 + * strlen() will be called internally + * @param[in] buf memory buffer of size buf_len, may be NULL + * @param[in] buf_len size of memory buffer buf + * + * @param[out] _sc_blob len size of the Smartcard authentication blob + * + * @return EOK on success + * EINVAL if input data is not consistent + * EAGAIN if provided buffer is too small, _sc_blob_len + * contains the size needed to store the SC blob + */ +errno_t sss_auth_pack_sc_blob(const char *pin, size_t pin_len, + const char *token_name, size_t token_name_len, + const char *module_name, size_t module_name_len, + const char *key_id, size_t key_id_len, + const char *label, size_t label_len, + uint8_t *buf, size_t buf_len, + size_t *_sc_blob_len); +/** + * @brief Fill memory buffer with 2FA blob + * + * @param[in] fa1 First authentication factor, null terminated + * @param[in] fa1_len Length of the first authentication factor, if 0 + * strlen() will be called internally + * @param[in] fa2 Second authentication factor, null terminated + * @param[in] fa2_len Length of the second authentication factor, if 0 + * strlen() will be called internally + * @param[in] buf memory buffer of size buf_len + * @param[in] buf_len size of memory buffer buf + * + * @param[out] _2fa_blob_len size of the 2FA blob + * + * @return EOK on success + * EINVAL if input data is not consistent + * EAGAIN if provided buffer is too small, _2fa_blob_len + * contains the size needed to store the 2FA blob + */ +errno_t sss_auth_pack_2fa_blob(const char *fa1, size_t fa1_len, + const char *fa2, size_t fa2_len, + uint8_t *buf, size_t buf_len, + size_t *_2fa_blob_len); + +/** + * @brief Extract 2FA data from memory buffer + * + * @param[in] mem_ctx Talloc memory context to allocate the 2FA data on + * @param[in] blob Memory buffer containing the 2FA data + * @param[in] blob_len Size of the memory buffer + * @param[out] _fa1 First authentication factor, null terminated + * @param[out] _fa1_len Length of the first authentication factor + * @param[out] _fa2 Second authentication factor, null terminated + * @param[out] _fa2_len Length of the second authentication factor + * + * @return EOK on success + * EINVAL if input data is not consistent + * EINVAL if no memory can be allocated + */ +errno_t sss_auth_unpack_2fa_blob(TALLOC_CTX *mem_ctx, + const uint8_t *blob, size_t blob_len, + char **fa1, size_t *_fa1_len, + char **fa2, size_t *_fa2_len); + +/** + * @brief Extract SC data from memory buffer + * + * @param[in] mem_ctx Talloc memory context to allocate the 2FA + * data on + * @param[in] blob Memory buffer containing the 2FA data + * @param[in] blob_len Size of the memory buffer + * @param[out] _pin PIN, null terminated + * @param[out] _pin_len Length of the PIN + * @param[out] _token_name Token name, null terminated + * @param[out] _token_name_len Length of the token name + * @param[out] _module_name Name of PKCS#11 module, null terminated + * @param[out] _module_name_len Length of the module name + * @param[out] _key_id Key ID of the certificate, null terminated + * @param[out] _key_id_len Length of the key ID + * @param[out] _labe l Label of the certificate, null terminated + * @param[out] _label_len Length of the label + * + * @return EOK on success + * EINVAL if input data is not consistent + * EINVAL if no memory can be allocated + */ +errno_t sss_auth_unpack_sc_blob(TALLOC_CTX *mem_ctx, + const uint8_t *blob, size_t blob_len, + char **pin, size_t *_pin_len, + char **token_name, size_t *_token_name_len, + char **module_name, size_t *_module_name_len, + char **key_id, size_t *_key_id_len, + char **label, size_t *_label_len); + +/** + * @brief Return a pointer to the PIN string in the memory buffer + * + * @param[in] blob Memory buffer containing the 2FA data + * @param[in] blob_len Size of the memory buffer + * + * @return pointer to 0-terminate PIN string in the memory buffer + */ +const char *sss_auth_get_pin_from_sc_blob(uint8_t *blob, size_t blob_len); + +/** + * @brief Fill memory buffer with Passkey authentication blob + * + * @param[in] buf Memory buffer containing the Passkey data + * @param[in] uv User verification, "true" or "false" + * @param[in] key Hash table key used to lookup Passkey data + * in the PAM responder. + * @param[in] pin PIN provided by the user. Can be set to + * NULL if no PIN is provided (user verification false) + * + * @param[out] _passkey_buf_len len size of the Passkey authentication blob + * + * @return EOK on success + * EINVAL if input data is not valid + */ +errno_t sss_auth_pack_passkey_blob(uint8_t *buf, + const char *uv, + const char *key, + const char *pin); +/** + * @brief Calculate size of Passkey authentication data + * + * @param[in] uv User verification, "true" or "false" + * @param[in] key Hash table key used to lookup Passkey data + * in the PAM responder. + * @param[in] pin PIN provided by the user. Can be + * Set to NULL if no PIN is + * provided (user verification false) + * + * @param[out] _passkey_buf_len len size of the Passkey authentication blob + * + * @return EOK on success + * EINVAL if input data is not valid + */ +errno_t sss_auth_passkey_calc_size(const char *uv, + const char *key, + const char *pin, + size_t *_passkey_buf_len); +#endif /* __AUTHTOK_UTILS_H__ */ diff --git a/src/util/authtok.c b/src/util/authtok.c new file mode 100644 index 0000000..6a6e74e --- /dev/null +++ b/src/util/authtok.c @@ -0,0 +1,1234 @@ +/* + SSSD - auth utils + + Copyright (C) Simo Sorce <simo@redhat.com> 2012 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "authtok.h" + +struct sss_auth_token { + enum sss_authtok_type type; + uint8_t *data; + size_t length; +}; + +const char *sss_authtok_type_to_str(enum sss_authtok_type type) +{ + switch (type) { + case SSS_AUTHTOK_TYPE_EMPTY: + return "No authentication token available"; + case SSS_AUTHTOK_TYPE_PASSWORD: + return "Password"; + case SSS_AUTHTOK_TYPE_CCFILE: + return "Path to a Kerberos credential cache file"; + case SSS_AUTHTOK_TYPE_2FA: + return "Two factors"; + case SSS_AUTHTOK_TYPE_SC_PIN: + return "Smart Card PIN"; + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + return "Smart Card PIN will be entered at the card reader"; + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + return "Two factors in a single string"; + case SSS_AUTHTOK_TYPE_OAUTH2: + return "OAuth2"; + case SSS_AUTHTOK_TYPE_PASSKEY: + return "Passkey"; + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + return "Passkey kerberos"; + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + return "Passkey kerberos reply"; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Unknown authtok type %d\n", type); + return "-unknown-"; +} + +enum sss_authtok_type sss_authtok_get_type(struct sss_auth_token *tok) +{ + return tok->type; +} + +size_t sss_authtok_get_size(struct sss_auth_token *tok) +{ + if (!tok) { + return 0; + } + switch (tok->type) { + case SSS_AUTHTOK_TYPE_PASSWORD: + case SSS_AUTHTOK_TYPE_CCFILE: + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + case SSS_AUTHTOK_TYPE_OAUTH2: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + return tok->length; + case SSS_AUTHTOK_TYPE_EMPTY: + return 0; + } + + return EINVAL; +} + +uint8_t *sss_authtok_get_data(struct sss_auth_token *tok) +{ + if (!tok) { + return NULL; + } + return tok->data; +} + +errno_t sss_authtok_get_password(struct sss_auth_token *tok, + const char **pwd, size_t *len) +{ + if (!tok) { + return EFAULT; + } + switch (tok->type) { + case SSS_AUTHTOK_TYPE_EMPTY: + return ENOENT; + case SSS_AUTHTOK_TYPE_PASSWORD: + *pwd = (const char *)tok->data; + if (len) { + *len = tok->length - 1; + } + return EOK; + case SSS_AUTHTOK_TYPE_CCFILE: + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + case SSS_AUTHTOK_TYPE_OAUTH2: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + return EACCES; + } + + return EINVAL; +} + +errno_t sss_authtok_get_ccfile(struct sss_auth_token *tok, + const char **ccfile, size_t *len) +{ + if (!tok) { + return EINVAL; + } + switch (tok->type) { + case SSS_AUTHTOK_TYPE_EMPTY: + return ENOENT; + case SSS_AUTHTOK_TYPE_CCFILE: + *ccfile = (const char *)tok->data; + if (len) { + *len = tok->length - 1; + } + return EOK; + case SSS_AUTHTOK_TYPE_PASSWORD: + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + case SSS_AUTHTOK_TYPE_OAUTH2: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + return EACCES; + } + + return EINVAL; +} + +errno_t sss_authtok_get_2fa_single(struct sss_auth_token *tok, + const char **str, size_t *len) +{ + if (!tok) { + return EINVAL; + } + switch (tok->type) { + case SSS_AUTHTOK_TYPE_EMPTY: + return ENOENT; + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + *str = (const char *)tok->data; + if (len) { + *len = tok->length - 1; + } + return EOK; + case SSS_AUTHTOK_TYPE_PASSWORD: + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_CCFILE: + case SSS_AUTHTOK_TYPE_OAUTH2: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + return EACCES; + } + + return EINVAL; +} + +errno_t sss_authtok_get_oauth2(struct sss_auth_token *tok, + const char **str, size_t *len) +{ + if (!tok) { + return EINVAL; + } + switch (tok->type) { + case SSS_AUTHTOK_TYPE_EMPTY: + return ENOENT; + case SSS_AUTHTOK_TYPE_OAUTH2: + *str = (const char *)tok->data; + if (len) { + *len = tok->length - 1; + } + return EOK; + case SSS_AUTHTOK_TYPE_PASSWORD: + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_CCFILE: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + return EACCES; + } + + return EINVAL; +} + +errno_t sss_authtok_get_passkey_reply(struct sss_auth_token *tok, + const char **str, size_t *len) +{ + if (!tok) { + return EINVAL; + } + switch (tok->type) { + case SSS_AUTHTOK_TYPE_EMPTY: + return ENOENT; + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + *str = (const char *)tok->data; + if (len) { + *len = tok->length - 1; + } + return EOK; + case SSS_AUTHTOK_TYPE_PASSWORD: + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_CCFILE: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + case SSS_AUTHTOK_TYPE_OAUTH2: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + return EACCES; + } + + return EINVAL; +} + +errno_t sss_authtok_get_passkey_pin(struct sss_auth_token *tok, + const char **_pin, size_t *_len) +{ + int ret; + const char *pin = NULL; + const char *key = NULL; + const char *prompt = NULL; + size_t pin_len = 0; + + if (!tok) { + return EINVAL; + } + switch (tok->type) { + case SSS_AUTHTOK_TYPE_EMPTY: + return ENOENT; + case SSS_AUTHTOK_TYPE_PASSKEY: + *_pin = (const char *)tok->data; + if (_len) { + *_len = tok->length - 1; + } + return EOK; + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + ret = sss_authtok_get_passkey(NULL, tok, &prompt, &key, &pin, &pin_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_passkey failed.\n"); + return ret; + } + + *_pin = pin; + *_len = pin_len; + + return EOK; + case SSS_AUTHTOK_TYPE_PASSWORD: + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_CCFILE: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + case SSS_AUTHTOK_TYPE_OAUTH2: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + return EACCES; + } + + return EINVAL; +} +static errno_t sss_authtok_set_string(struct sss_auth_token *tok, + enum sss_authtok_type type, + const char *context_name, + const char *str, size_t len) +{ + size_t size; + + if (len == 0) { + len = strlen(str); + } else { + while (len > 0 && str[len - 1] == '\0') len--; + } + + if (len == 0) { + /* we do not allow zero length typed tokens */ + return EINVAL; + } + + size = len + 1; + + tok->data = talloc_named(tok, size, "%s", context_name); + if (!tok->data) { + return ENOMEM; + } + memcpy(tok->data, str, len); + tok->data[len] = '\0'; + tok->type = type; + tok->length = size; + + return EOK; + +} + +void sss_authtok_set_empty(struct sss_auth_token *tok) +{ + if (!tok) { + return; + } + switch (tok->type) { + case SSS_AUTHTOK_TYPE_EMPTY: + return; + case SSS_AUTHTOK_TYPE_PASSWORD: + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + case SSS_AUTHTOK_TYPE_OAUTH2: + sss_erase_mem_securely(tok->data, tok->length); + break; + case SSS_AUTHTOK_TYPE_CCFILE: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + break; + } + + tok->type = SSS_AUTHTOK_TYPE_EMPTY; + talloc_zfree(tok->data); + tok->length = 0; +} + +errno_t sss_authtok_set_password(struct sss_auth_token *tok, + const char *password, size_t len) +{ + sss_authtok_set_empty(tok); + + return sss_authtok_set_string(tok, SSS_AUTHTOK_TYPE_PASSWORD, + "password", password, len); +} + +errno_t sss_authtok_set_ccfile(struct sss_auth_token *tok, + const char *ccfile, size_t len) +{ + sss_authtok_set_empty(tok); + + return sss_authtok_set_string(tok, SSS_AUTHTOK_TYPE_CCFILE, + "ccfile", ccfile, len); +} + +errno_t sss_authtok_set_2fa_single(struct sss_auth_token *tok, + const char *str, size_t len) +{ + sss_authtok_set_empty(tok); + + return sss_authtok_set_string(tok, SSS_AUTHTOK_TYPE_2FA_SINGLE, + "2fa_single", str, len); +} + +errno_t sss_authtok_set_oauth2(struct sss_auth_token *tok, + const char *str, size_t len) +{ + sss_authtok_set_empty(tok); + + return sss_authtok_set_string(tok, SSS_AUTHTOK_TYPE_OAUTH2, + "oauth2", str, len); +} + +errno_t sss_authtok_set_passkey_reply(struct sss_auth_token *tok, + const char *str, size_t len) +{ + sss_authtok_set_empty(tok); + + return sss_authtok_set_string(tok, SSS_AUTHTOK_TYPE_PASSKEY_REPLY, + "passkey kerberos reply", str, len); +} + +errno_t sss_authtok_set_passkey(struct sss_auth_token *tok, + const char *str, size_t len) +{ + sss_authtok_set_empty(tok); + + return sss_authtok_set_string(tok, SSS_AUTHTOK_TYPE_PASSKEY, + "passkey", str, len); +} + +static errno_t sss_authtok_set_2fa_from_blob(struct sss_auth_token *tok, + const uint8_t *data, size_t len); +static errno_t sss_authtok_set_passkey_from_blob(struct sss_auth_token *tok, + const uint8_t *data, size_t len); + +errno_t sss_authtok_set(struct sss_auth_token *tok, + enum sss_authtok_type type, + const uint8_t *data, size_t len) +{ + switch (type) { + case SSS_AUTHTOK_TYPE_PASSWORD: + return sss_authtok_set_password(tok, (const char *)data, len); + case SSS_AUTHTOK_TYPE_CCFILE: + return sss_authtok_set_ccfile(tok, (const char *)data, len); + case SSS_AUTHTOK_TYPE_2FA: + return sss_authtok_set_2fa_from_blob(tok, data, len); + case SSS_AUTHTOK_TYPE_SC_PIN: + return sss_authtok_set_sc_from_blob(tok, data, len); + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + return sss_authtok_set_sc_from_blob(tok, data, len); + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + return sss_authtok_set_2fa_single(tok, (const char *) data, len); + case SSS_AUTHTOK_TYPE_OAUTH2: + return sss_authtok_set_oauth2(tok, (const char *) data, len); + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + return sss_authtok_set_passkey_from_blob(tok, data, len); + case SSS_AUTHTOK_TYPE_PASSKEY: + return sss_authtok_set_passkey(tok, (const char *) data, len); + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + return sss_authtok_set_passkey_reply(tok, (const char *)data, len); + case SSS_AUTHTOK_TYPE_EMPTY: + sss_authtok_set_empty(tok); + return EOK; + } + + return EINVAL; +} + +errno_t sss_authtok_copy(struct sss_auth_token *src, + struct sss_auth_token *dst) +{ + if (!src || !dst) { + return EINVAL; + } + sss_authtok_set_empty(dst); + + if (src->type == SSS_AUTHTOK_TYPE_EMPTY) { + return EOK; + } + + dst->data = talloc_memdup(dst, src->data, src->length); + if (!dst->data) { + return ENOMEM; + } + dst->length = src->length; + dst->type = src->type; + + return EOK; +} + +static int sss_auth_token_destructor(struct sss_auth_token *tok) +{ + if (tok != NULL) { + sss_erase_talloc_mem_securely(tok->data); + } + return 0; +} + +struct sss_auth_token *sss_authtok_new(TALLOC_CTX *mem_ctx) +{ + struct sss_auth_token *token; + + token = talloc_zero(mem_ctx, struct sss_auth_token); + if (token == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + } + + talloc_set_destructor(token, sss_auth_token_destructor); + + return token; +} + + +void sss_authtok_wipe_password(struct sss_auth_token *tok) +{ + if (!tok || tok->type != SSS_AUTHTOK_TYPE_PASSWORD) { + return; + } + + sss_erase_mem_securely(tok->data, tok->length); +} + +errno_t sss_auth_unpack_2fa_blob(TALLOC_CTX *mem_ctx, + const uint8_t *blob, size_t blob_len, + char **fa1, size_t *_fa1_len, + char **fa2, size_t *_fa2_len) +{ + size_t c; + uint32_t fa1_len; + uint32_t fa2_len; + + if (blob_len < 2 * sizeof(uint32_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Blob too small.\n"); + return EINVAL; + } + + c = 0; + SAFEALIGN_COPY_UINT32(&fa1_len, blob, &c); + SAFEALIGN_COPY_UINT32(&fa2_len, blob + c, &c); + + if (blob_len != 2 * sizeof(uint32_t) + fa1_len + fa2_len) { + DEBUG(SSSDBG_CRIT_FAILURE, "Blob size mismatch.\n"); + return EINVAL; + } + + if (fa1_len != 0) { + *fa1 = talloc_strndup(mem_ctx, (const char *) blob + c, fa1_len); + if (*fa1 == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + return ENOMEM; + } + } else { + *fa1 = NULL; + } + + if (fa2_len != 0) { + *fa2 = talloc_strndup(mem_ctx, (const char *) blob + c + fa1_len, + fa2_len); + if (*fa2 == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + talloc_free(*fa1); + return ENOMEM; + } + } else { + *fa2 = NULL; + } + + /* Re-calculate length for the case where \0 was missing in the blob */ + *_fa1_len = (*fa1 == NULL) ? 0 : strlen(*fa1); + *_fa2_len = (*fa2 == NULL) ? 0 : strlen(*fa2); + + return EOK; +} + +static errno_t sss_authtok_set_2fa_from_blob(struct sss_auth_token *tok, + const uint8_t *data, size_t len) +{ + TALLOC_CTX *tmp_ctx; + int ret; + char *fa1; + size_t fa1_len; + char *fa2; + size_t fa2_len; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sss_auth_unpack_2fa_blob(tmp_ctx, data, len, &fa1, &fa1_len, + &fa2, &fa2_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_auth_unpack_2fa_blob failed.\n"); + goto done; + } + + ret = sss_authtok_set_2fa(tok, fa1, fa1_len, fa2, fa2_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_set_2fa failed.\n"); + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + if (ret != EOK) { + sss_authtok_set_empty(tok); + } + + return ret; +} + +errno_t sss_authtok_get_2fa(struct sss_auth_token *tok, + const char **fa1, size_t *fa1_len, + const char **fa2, size_t *fa2_len) +{ + size_t c; + uint32_t tmp_uint32_t; + + if (tok->type != SSS_AUTHTOK_TYPE_2FA) { + return (tok->type == SSS_AUTHTOK_TYPE_EMPTY) ? ENOENT : EACCES; + } + + if (tok->length < 2 * sizeof(uint32_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Blob too small.\n"); + return EINVAL; + } + + c = 0; + SAFEALIGN_COPY_UINT32(&tmp_uint32_t, tok->data, &c); + *fa1_len = tmp_uint32_t - 1; + SAFEALIGN_COPY_UINT32(&tmp_uint32_t, tok->data + c, &c); + *fa2_len = tmp_uint32_t - 1; + + if (*fa1_len == 0 || *fa2_len == 0 + || tok->length != 2 * sizeof(uint32_t) + *fa1_len + *fa2_len + 2) { + DEBUG(SSSDBG_CRIT_FAILURE, "Blob size mismatch.\n"); + return EINVAL; + } + + if (tok->data[c + *fa1_len] != '\0' + || tok->data[c + *fa1_len + 1 + *fa2_len] != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing terminating null character.\n"); + return EINVAL; + } + + *fa1 = (const char *) tok->data + c; + *fa2 = (const char *) tok->data + c + *fa1_len + 1; + + return EOK; +} + +errno_t sss_authtok_set_2fa(struct sss_auth_token *tok, + const char *fa1, size_t fa1_len, + const char *fa2, size_t fa2_len) +{ + int ret; + size_t needed_size; + + if (tok == NULL) { + return EINVAL; + } + + sss_authtok_set_empty(tok); + + ret = sss_auth_pack_2fa_blob(fa1, fa1_len, fa2, fa2_len, NULL, 0, + &needed_size); + if (ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_auth_pack_2fa_blob unexpectedly returned [%d].\n", ret); + return EINVAL; + } + + tok->data = talloc_size(tok, needed_size); + if (tok->data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + ret = sss_auth_pack_2fa_blob(fa1, fa1_len, fa2, fa2_len, tok->data, + needed_size, &needed_size); + if (ret != EOK) { + talloc_free(tok->data); + DEBUG(SSSDBG_OP_FAILURE, "sss_auth_pack_2fa_blob failed.\n"); + return ret; + } + tok->length = needed_size; + tok->type = SSS_AUTHTOK_TYPE_2FA; + + return EOK; +} + +errno_t sss_auth_unpack_passkey_blob(TALLOC_CTX *mem_ctx, + const uint8_t *blob, + char **_prompt, + char **_key, + char **_pin) +{ + size_t len = 0; + char *prompt; + char *key; + char *pin; + + prompt = talloc_strdup(mem_ctx, (const char *) blob + len); + if (prompt == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup prompt failed.\n"); + return ENOMEM; + } + len += strlen(prompt) + 1; + + key = talloc_strdup(mem_ctx, (const char *) blob + len); + if (key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup key failed.\n"); + talloc_free(prompt); + return ENOMEM; + } + len += strlen(key) + 1; + + pin = talloc_strdup(mem_ctx, (const char *) blob + len); + if (pin == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup pin failed.\n"); + talloc_free(prompt); + talloc_free(key); + return ENOMEM; + } + + *_prompt = prompt; + *_key = key; + *_pin = pin; + + return EOK; +} + +errno_t sss_authtok_set_passkey_krb(struct sss_auth_token *tok, + const char *prompt, + const char *key, + const char *pin) +{ + int ret; + size_t needed_size; + + if (tok == NULL) { + return EINVAL; + } + + sss_authtok_set_empty(tok); + + ret = sss_auth_passkey_calc_size(prompt, key, pin, &needed_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_auth_calc_size failed [%d]: [%s].\n", + ret, sss_strerror(ret)); + return ret; + } + + tok->data = talloc_size(tok, needed_size); + if (tok->data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + ret = sss_auth_pack_passkey_blob(tok->data, prompt, key, pin); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_auth_pack_passkey_blob unexpectedly returned [%d]: [%s].\n", + ret, sss_strerror(ret)); + return EINVAL; + } + + tok->length = needed_size; + tok->type = SSS_AUTHTOK_TYPE_PASSKEY_KRB; + + return EOK; +} + +static errno_t sss_authtok_set_passkey_from_blob(struct sss_auth_token *tok, + const uint8_t *data, size_t len) +{ + TALLOC_CTX *tmp_ctx; + int ret; + char *prompt; + char *key; + char *pin; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sss_auth_unpack_passkey_blob(tmp_ctx, data, &prompt, &key, + &pin); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_auth_unpack_passkey_blob returned [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sss_authtok_set_passkey_krb(tok, prompt, key, pin); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_set_passkey_krb returned [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + if (ret != EOK) { + sss_authtok_set_empty(tok); + } + + return ret; +} + +errno_t sss_authtok_get_passkey(TALLOC_CTX *mem_ctx, + struct sss_auth_token *tok, + const char **_prompt, + const char **_key, + const char **_pin, + size_t *_pin_len) +{ + char *prompt; + char *key; + char *pin; + size_t pin_len = 0; + errno_t ret; + + if (!tok) { + return EFAULT; + } + if (tok->type != SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + return (tok->type == SSS_AUTHTOK_TYPE_EMPTY) ? ENOENT : EACCES; + } + + ret = sss_auth_unpack_passkey_blob(mem_ctx, tok->data, &prompt, &key, + &pin); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_auth_unpack_passkey_blob returned [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + pin_len = strlen(pin); + + *_prompt = prompt; + *_key = key; + *_pin = pin; + *_pin_len = pin_len; +done: + return ret; + +} + +errno_t sss_authtok_set_sc(struct sss_auth_token *tok, + enum sss_authtok_type type, + const char *pin, size_t pin_len, + const char *token_name, size_t token_name_len, + const char *module_name, size_t module_name_len, + const char *key_id, size_t key_id_len, + const char *label, size_t label_len) +{ + int ret; + size_t needed_size; + + if (type != SSS_AUTHTOK_TYPE_SC_PIN + && type != SSS_AUTHTOK_TYPE_SC_KEYPAD) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid type [%d].\n", type); + return EINVAL; + } + + sss_authtok_set_empty(tok); + + ret = sss_auth_pack_sc_blob(pin, pin_len, token_name, token_name_len, + module_name, module_name_len, + key_id, key_id_len, label, label_len, NULL, 0, + &needed_size); + if (ret != EAGAIN) { + DEBUG(SSSDBG_OP_FAILURE, "sss_auth_pack_sc_blob failed.\n"); + return ret; + } + + tok->data = talloc_size(tok, needed_size); + if (tok->data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + ret = sss_auth_pack_sc_blob(pin, pin_len, token_name, token_name_len, + module_name, module_name_len, + key_id, key_id_len, label, label_len, tok->data, + needed_size, &needed_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_auth_pack_sc_blob failed.\n"); + talloc_free(tok->data); + return ret; + } + + tok->length = needed_size; + tok->type = type; + + return EOK; +} + +errno_t sss_authtok_set_sc_from_blob(struct sss_auth_token *tok, + const uint8_t *data, + size_t len) +{ + int ret; + char *pin = NULL; + size_t pin_len; + char *token_name = NULL; + size_t token_name_len; + char *module_name = NULL; + size_t module_name_len; + char *key_id = NULL; + size_t key_id_len; + char *label = NULL; + size_t label_len; + TALLOC_CTX *tmp_ctx; + + if (tok == NULL) { + return EFAULT; + } + if (data == NULL || len == 0) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sss_auth_unpack_sc_blob(tmp_ctx, data, len, &pin, &pin_len, + &token_name, &token_name_len, + &module_name, &module_name_len, + &key_id, &key_id_len, &label, &label_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_auth_unpack_sc_blob failed.\n"); + goto done; + } + + ret = sss_authtok_set_sc(tok, SSS_AUTHTOK_TYPE_SC_PIN, pin, pin_len, + token_name, token_name_len, + module_name, module_name_len, + key_id, key_id_len, label, label_len); + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t sss_authtok_set_sc_pin(struct sss_auth_token *tok, const char *pin, + size_t len) +{ + if (tok == NULL) { + return EFAULT; + } + if (pin == NULL) { + return EINVAL; + } + + return sss_authtok_set_sc(tok, SSS_AUTHTOK_TYPE_SC_PIN, pin, len, + NULL, 0, NULL, 0, NULL, 0, NULL, 0); +} + +errno_t sss_authtok_get_sc_pin(struct sss_auth_token *tok, const char **_pin, + size_t *len) +{ + int ret; + const char *pin = NULL; + size_t pin_len; + + if (!tok) { + return EFAULT; + } + switch (tok->type) { + case SSS_AUTHTOK_TYPE_EMPTY: + return ENOENT; + case SSS_AUTHTOK_TYPE_SC_PIN: + ret = sss_authtok_get_sc(tok, &pin, &pin_len, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc failed.\n"); + return ret; + } + + *_pin = pin; + if (len) { + *len = pin_len; + } + return EOK; + case SSS_AUTHTOK_TYPE_PASSWORD: + case SSS_AUTHTOK_TYPE_CCFILE: + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + case SSS_AUTHTOK_TYPE_OAUTH2: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + return EACCES; + } + + return EINVAL; +} + +void sss_authtok_set_sc_keypad(struct sss_auth_token *tok) +{ + if (tok == NULL) { + return; + } + + sss_authtok_set_empty(tok); + + tok->type = SSS_AUTHTOK_TYPE_SC_KEYPAD; +} + +errno_t sss_auth_unpack_sc_blob(TALLOC_CTX *mem_ctx, + const uint8_t *blob, size_t blob_len, + char **pin, size_t *_pin_len, + char **token_name, size_t *_token_name_len, + char **module_name, size_t *_module_name_len, + char **key_id, size_t *_key_id_len, + char **label, size_t *_label_len) +{ + size_t c; + uint32_t pin_len; + uint32_t token_name_len; + uint32_t module_name_len; + uint32_t key_id_len; + uint32_t label_len; + + c = 0; + + if (blob == NULL || blob_len == 0) { + pin_len = 0; + token_name_len = 0; + module_name_len = 0; + key_id_len = 0; + label_len = 0; + } else if (blob_len > 0 + && strnlen((const char *) blob, blob_len) == blob_len - 1) { + pin_len = blob_len; + token_name_len = 0; + module_name_len = 0; + key_id_len = 0; + label_len = 0; + } else { + if (blob_len < 5 * sizeof(uint32_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Blob too small.\n"); + return EINVAL; + } + + SAFEALIGN_COPY_UINT32(&pin_len, blob, &c); + SAFEALIGN_COPY_UINT32(&token_name_len, blob + c, &c); + SAFEALIGN_COPY_UINT32(&module_name_len, blob + c, &c); + SAFEALIGN_COPY_UINT32(&key_id_len, blob + c, &c); + SAFEALIGN_COPY_UINT32(&label_len, blob + c, &c); + + if (blob_len != 5 * sizeof(uint32_t) + pin_len + token_name_len + + module_name_len + key_id_len + + label_len) { + DEBUG(SSSDBG_CRIT_FAILURE, "Blob size mismatch.\n"); + return EINVAL; + } + } + + if (pin_len != 0) { + *pin = talloc_strndup(mem_ctx, (const char *) blob + c, pin_len); + if (*pin == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + return ENOMEM; + } + } else { + *pin = NULL; + } + + if (token_name_len != 0) { + *token_name = talloc_strndup(mem_ctx, (const char *) blob + c + pin_len, + token_name_len); + if (*token_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + talloc_free(*pin); + return ENOMEM; + } + } else { + *token_name = NULL; + } + + if (module_name_len != 0) { + *module_name = talloc_strndup(mem_ctx, + (const char *) blob + c + pin_len + + token_name_len, + module_name_len); + if (*module_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + talloc_free(*pin); + talloc_free(*token_name); + return ENOMEM; + } + } else { + *module_name = NULL; + } + + if (key_id_len != 0) { + *key_id = talloc_strndup(mem_ctx, + (const char *) blob + c + pin_len + + token_name_len + + module_name_len, + key_id_len); + if (*key_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + talloc_free(*pin); + talloc_free(*token_name); + talloc_free(*module_name); + return ENOMEM; + } + } else { + *key_id = NULL; + } + + if (label_len != 0) { + *label = talloc_strndup(mem_ctx, + (const char *) blob + c + pin_len + + token_name_len + + module_name_len + + key_id_len, + label_len); + if (*label == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + talloc_free(*pin); + talloc_free(*token_name); + talloc_free(*module_name); + talloc_free(*key_id); + return ENOMEM; + } + } else { + *label = NULL; + } + + /* Re-calculate length for the case where \0 was missing in the blob */ + if (_pin_len != NULL) { + *_pin_len = (*pin == NULL) ? 0 : strlen(*pin); + } + if (_token_name_len != NULL) { + *_token_name_len = (*token_name == NULL) ? 0 : strlen(*token_name); + } + if (_module_name_len != NULL) { + *_module_name_len = (*module_name == NULL) ? 0 : strlen(*module_name); + } + + if (_key_id_len != NULL) { + *_key_id_len = (*key_id == NULL) ? 0 : strlen(*key_id); + } + + if (_label_len != NULL) { + *_label_len = (*label == NULL) ? 0 : strlen(*label); + } + + return EOK; +} + +errno_t sss_authtok_get_sc(struct sss_auth_token *tok, + const char **_pin, size_t *_pin_len, + const char **_token_name, size_t *_token_name_len, + const char **_module_name, size_t *_module_name_len, + const char **_key_id, size_t *_key_id_len, + const char **_label, size_t *_label_len) +{ + size_t c = 0; + size_t pin_len; + size_t token_name_len; + size_t module_name_len; + size_t key_id_len; + size_t label_len; + uint32_t tmp_uint32_t; + + if (!tok) { + return EFAULT; + } + + if (tok->type != SSS_AUTHTOK_TYPE_SC_PIN + && tok->type != SSS_AUTHTOK_TYPE_SC_KEYPAD) { + return (tok->type == SSS_AUTHTOK_TYPE_EMPTY) ? ENOENT : EACCES; + } + + if (tok->length < 5 * sizeof(uint32_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Blob too small.\n"); + return EINVAL; + } + + SAFEALIGN_COPY_UINT32(&tmp_uint32_t, tok->data, &c); + pin_len = tmp_uint32_t - 1; + SAFEALIGN_COPY_UINT32(&tmp_uint32_t, tok->data + c, &c); + token_name_len = tmp_uint32_t - 1; + SAFEALIGN_COPY_UINT32(&tmp_uint32_t, tok->data + c, &c); + module_name_len = tmp_uint32_t -1; + SAFEALIGN_COPY_UINT32(&tmp_uint32_t, tok->data + c, &c); + key_id_len = tmp_uint32_t -1; + SAFEALIGN_COPY_UINT32(&tmp_uint32_t, tok->data + c, &c); + label_len = tmp_uint32_t -1; + + if (tok->length != 5 * sizeof(uint32_t) + 5 + pin_len + token_name_len + + module_name_len + key_id_len + + label_len) { + DEBUG(SSSDBG_CRIT_FAILURE, "Blob size mismatch.\n"); + return EINVAL; + } + + if (_pin != NULL) { + *_pin = (const char *) tok->data + c; + } + if (_pin_len != NULL) { + *_pin_len = pin_len; + } + + if (_token_name != NULL) { + *_token_name = (const char *) tok->data + c + pin_len + 1; + } + if (_token_name_len != NULL) { + *_token_name_len = token_name_len; + } + + if (_module_name != NULL) { + *_module_name = (const char *) tok->data + c + pin_len + 1 + + token_name_len + 1; + } + if (_module_name_len != NULL) { + *_module_name_len = module_name_len; + } + + if (_key_id != NULL) { + *_key_id = (const char *) tok->data + c + pin_len + 1 + + token_name_len + 1 + module_name_len + 1; + } + if (_key_id_len != NULL) { + *_key_id_len = key_id_len; + } + + if (_label != NULL) { + *_label = (const char *) tok->data + c + pin_len + 1 + + token_name_len + 1 + module_name_len + 1 + + key_id_len + 1; + } + if (_label_len != NULL) { + *_label_len = label_len; + } + + return EOK; +} diff --git a/src/util/authtok.h b/src/util/authtok.h new file mode 100644 index 0000000..f9cc79c --- /dev/null +++ b/src/util/authtok.h @@ -0,0 +1,523 @@ +/* + SSSD - auth utils + + Copyright (C) Simo Sorce <simo@redhat.com> 2012 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __AUTHTOK_H__ +#define __AUTHTOK_H__ + +#include "util/util.h" +#include "util/authtok-utils.h" +#include "sss_client/sss_cli.h" + +#define IS_SC_AUTHTOK(tok) ( \ + sss_authtok_get_type((tok)) == SSS_AUTHTOK_TYPE_SC_PIN \ + || sss_authtok_get_type((tok)) == SSS_AUTHTOK_TYPE_SC_KEYPAD) + + +/* Use sss_authtok_* accessor functions instead of struct sss_auth_token + */ +struct sss_auth_token; + +/** + * @brief Converts token type to string for debugging purposes. + * + * @param type Tonen type + * + * @return Token type string representation + */ +const char *sss_authtok_type_to_str(enum sss_authtok_type type); + +/** + * @brief Returns the token type + * + * @param tok A pointer to an sss_auth_token + * + * @return An sss_authtok_type (empty, password, ...) + */ +enum sss_authtok_type sss_authtok_get_type(struct sss_auth_token *tok); + +/** + * @brief Returns the token size + * + * @param tok A pointer to an sss_auth_token + * + * @return The current size of the token payload + */ +size_t sss_authtok_get_size(struct sss_auth_token *tok); + +/** + * @brief Get the data buffer + * + * @param tok A pointer to an sss_auth_token + * + * @return A pointer to the token payload + */ +uint8_t *sss_authtok_get_data(struct sss_auth_token *tok); + +/** + * @brief Returns a const string if the auth token is of type + SSS_AUTHTOK_TYPE_PASSWORD, otherwise it returns an error + * + * @param tok A pointer to an sss_auth_token + * @param pwd A pointer to a const char *, that will point to a null + * terminated string + * @param len The length of the password string + * + * @return EOK on success + * ENOENT if the token is empty + * EACCESS if the token is not a password token + */ +errno_t sss_authtok_get_password(struct sss_auth_token *tok, + const char **pwd, size_t *len); + +/** + * @brief Set a password into an auth token, replacing any previous data + * + * @param tok A pointer to an sss_auth_token structure to change, also + * used as a memory context to allocate the internal data. + * @param password A string + * @param len The length of the string or, if 0 is passed, + * then strlen(password) will be used internally. + * + * @return EOK on success + * ENOMEM on error + */ +errno_t sss_authtok_set_password(struct sss_auth_token *tok, + const char *password, size_t len); + +/** + * @brief Returns a const string if the auth token is of type + SSS_AUTHTOK_TYPE_CCFILE, otherwise it returns an error + * + * @param tok A pointer to an sss_auth_token + * @param ccfile A pointer to a const char *, that will point to a null + * terminated string, also used as a memory context use to allocate the internal data + * @param len The length of the string + * + * @return EOK on success + * ENOENT if the token is empty + * EACCESS if the token is not a password token + */ +errno_t sss_authtok_get_ccfile(struct sss_auth_token *tok, + const char **ccfile, size_t *len); + +/** + * @brief Set a cc file name into an auth token, replacing any previous data + * + * @param tok A pointer to an sss_auth_token structure to change, also + * used as a memory context to allocate the internal data. + * @param ccfile A null terminated string + * @param len The length of the string + * + * @return EOK on success + * ENOMEM on error + */ +errno_t sss_authtok_set_ccfile(struct sss_auth_token *tok, + const char *ccfile, size_t len); + +/** + * @brief Resets an auth token to the empty status + * + * @param tok A pointer to an sss_auth_token structure to reset + * + * NOTE: This function uses sss_erase_mem_securely() on the payload if the type + * is SSS_AUTHTOK_TYPE_PASSWORD + */ +void sss_authtok_set_empty(struct sss_auth_token *tok); + +/** + * @brief Set an auth token by type, replacing any previous data + * + * @param tok A pointer to an sss_auth_token structure to change, also + * used as a memory context to allocate the internal data. + * @param type A valid authtok type + * @param data A data pointer + * @param len The length of the data + * + * @return EOK on success + * ENOMEM or EINVAL on error + */ +errno_t sss_authtok_set(struct sss_auth_token *tok, + enum sss_authtok_type type, + const uint8_t *data, size_t len); + +/** + * @brief Copy an auth token from source to destination + * + * @param src The source auth token + * @param dst The destination auth token, also used as a memory context + * to allocate dst internal data. + * + * @return EOK on success + * ENOMEM on error + */ +errno_t sss_authtok_copy(struct sss_auth_token *src, + struct sss_auth_token *dst); + +/** + * @brief Uses sss_erase_mem_securely to wipe the password from memory + * if the authtoken contains a password, otherwise does nothing. + * + * @param tok A pointer to an sss_auth_token structure to change + * + * NOTE: This function should only be used in destructors or similar + * functions where freeing the actual string is unsafe and where it can + * be guaranteed that the auth token will not be used anymore. + * Use sss_authtok_set_empty() in normal circumstances. + */ +void sss_authtok_wipe_password(struct sss_auth_token *tok); + +/** + * @brief Create new empty struct sss_auth_token. + * + * @param mem_ctx A memory context use to allocate the internal data + * @return A pointer to new empty struct sss_auth_token + * NULL in case of failure + * + * NOTE: This function is the only way, how to create new empty + * struct sss_auth_token. + */ +struct sss_auth_token *sss_authtok_new(TALLOC_CTX *mem_ctx); + +/** + * @brief Set authtoken with 2FA data + * + * @param tok A pointer to an sss_auth_token structure to change, also + * used as a memory context to allocate the internal data. + * @param[in] fa1 First authentication factor, null terminated + * @param[in] fa1_len Length of the first authentication factor, if 0 + * strlen() will be called internally + * @param[in] fa2 Second authentication factor, null terminated + * @param[in] fa2_len Length of the second authentication factor, if 0 + * strlen() will be called internally + * + * @return EOK on success + * ENOMEM if memory allocation failed + * EINVAL if input data is not consistent + */ +errno_t sss_authtok_set_2fa(struct sss_auth_token *tok, + const char *fa1, size_t fa1_len, + const char *fa2, size_t fa2_len); + +/** + * @brief Get 2FA factors from authtoken + * + * @param tok A pointer to an sss_auth_token structure to change, also + * used as a memory context to allocate the internal data. + * @param[out] fa1 A pointer to a const char *, that will point to a + * null terminated string holding the first + * authentication factor, may not be modified or freed + * @param[out] fa1_len Length of the first authentication factor + * @param[out] fa2 A pointer to a const char *, that will point to a + * null terminated string holding the second + * authentication factor, may not be modified or freed + * @param[out] fa2_len Length of the second authentication factor + * + * @return EOK on success + * ENOMEM if memory allocation failed + * EINVAL if input data is not consistent + * ENOENT if the token is empty + * EACCESS if the token is not a 2FA token + */ +errno_t sss_authtok_get_2fa(struct sss_auth_token *tok, + const char **fa1, size_t *fa1_len, + const char **fa2, size_t *fa2_len); + +/** + * @brief Set a Smart Card PIN into an auth token, replacing any previous data + * + * @param tok A pointer to an sss_auth_token structure to change, also + * used as a memory context to allocate the internal data. + * @param pin A string + * @param len The length of the string or, if 0 is passed, + * then strlen(password) will be used internally. + * + * @return EOK on success + * ENOMEM on error + */ +errno_t sss_authtok_set_sc_pin(struct sss_auth_token *tok, const char *pin, + size_t len); + +/** + * @brief Returns a Smart Card PIN as const string if the auth token is of + * type SSS_AUTHTOK_TYPE_SC_PIN, otherwise it returns an error + * + * @param tok A pointer to an sss_auth_token + * @param pin A pointer to a const char *, that will point to a null + * terminated string + * @param len The length of the pin string + * + * @return EOK on success + * ENOENT if the token is empty + * EACCESS if the token is not a Smart Card PIN token + */ +errno_t sss_authtok_get_sc_pin(struct sss_auth_token *tok, const char **pin, + size_t *len); + +/** + * @brief Sets an auth token to type SSS_AUTHTOK_TYPE_SC_KEYPAD, replacing any + * previous data + * + * @param tok A pointer to an sss_auth_token structure to change, also + * used as a memory context to allocate the internal data. + */ +void sss_authtok_set_sc_keypad(struct sss_auth_token *tok); + +/** + * @brief Set complete Smart Card authentication blob including PKCS#11 token + * name, module name and key id. + * + * @param tok A pointer to an sss_auth_token + * @param type Authentication token type, may be + * SSS_AUTHTOK_TYPE_SC_PIN or SSS_AUTHTOK_TYPE_SC_KEYPAD + * @param pin A pointer to a const char *, that will point to a null + * terminated string containing the PIN + * @param pin_len The length of the pin string, if set to 0 it will be + * calculated + * @param token_name A pointer to a const char *, that will point to a null + * terminated string containing the PKCS#11 token name + * @param token_name_len The length of the token name string, if set to 0 it + * will be calculated + * @param module_name A pointer to a const char *, that will point to a null + * terminated string containing the PKCS#11 module name + * @param module_name_len The length of the module name string, if set to 0 it + * will be calculated + * @param key_id A pointer to a const char *, that will point to a null + * terminated string containing the PKCS#11 key id + * @param key_id_len The length of the key id string, if set to 0 it will be + * calculated + * @param label A pointer to a const char *, that will point to a null + * terminated string containing the PKCS#11 label + * @param label_len The length of the label string, if set to 0 it will be + * calculated + * + * @return EOK on success + * EINVAL unexpected or inval input + * ENOMEM memory allocation error + */ +errno_t sss_authtok_set_sc(struct sss_auth_token *tok, + enum sss_authtok_type type, + const char *pin, size_t pin_len, + const char *token_name, size_t token_name_len, + const char *module_name, size_t module_name_len, + const char *key_id, size_t key_id_len, + const char *label, size_t label_len); +/** + * @brief Set a Smart Card authentication data, replacing any previous data + * + * @param tok A pointer to an sss_auth_token structure to change, also + * used as a memory context to allocate the internal data. + * @param data Smart Card authentication data blob + * @param len The length of the blob + * + * @return EOK on success + * ENOMEM on error + */ +errno_t sss_authtok_set_sc_from_blob(struct sss_auth_token *tok, + const uint8_t *data, + size_t len); + +/** + * @brief Get complete Smart Card authtoken data + * + * @param tok A pointer to an sss_auth_token structure + * @param[out] _pin A pointer to a const char *, that will point to + * a null terminated string holding the PIN, + * may not be modified or freed + * @param[out] _pin__len Length of the PIN + * @param[out] _token_name A pointer to a const char *, that will point to + * a null terminated string holding the PKCS#11 + * token name, may not be modified or freed + * @param[out] _token_name_len Length of the PKCS#11 token name + * @param[out] _module_name A pointer to a const char *, that will point to + * a null terminated string holding the PKCS#11 + * module name, may not be modified or freed + * @param[out] _module_name_len Length of the PKCS#11 module name + * @param[out] _key_id A pointer to a const char *, that will point to + * a null terminated string holding the PKCS#11 + * key id, may not be modified or freed + * @param[out] _key_id_len Length of the PKCS#11 key id + * @param[out] _label A pointer to a const char *, that will point to + * a null terminated string holding the PKCS#11 + * label, may not be modified or freed + * @param[out] _label_len Length of the PKCS#11 label + * + * Any of the output pointers may be NULL if the caller does not need the + * specific item. + * + * @return EOK on success + * EFAULT missing token + * EINVAL if input data is not consistent + * ENOENT if the token is empty + * EACCESS if the token is not a Smart Card token + */ +errno_t sss_authtok_get_sc(struct sss_auth_token *tok, + const char **_pin, size_t *_pin_len, + const char **_token_name, size_t *_token_name_len, + const char **_module_name, size_t *_module_name_len, + const char **_key_id, size_t *_key_id_len, + const char **_label, size_t *_label_len); + + +/** + * @brief Returns a const string if the auth token is of type + SSS_AUTHTOK_TYPE_2FA_SINGLE, otherwise it returns an error + * + * @param tok A pointer to an sss_auth_token + * @param pwd A pointer to a const char *, that will point to a null + * terminated string + * @param len The length of the credential string + * + * @return EOK on success + * ENOENT if the token is empty + * EACCESS if the token is not a password token + */ +errno_t sss_authtok_get_2fa_single(struct sss_auth_token *tok, + const char **str, size_t *len); + +/** + * @brief Set a 2FA credentials in a single strings into an auth token, + * replacing any previous data + * + * @param tok A pointer to an sss_auth_token structure to change, also + * used as a memory context to allocate the internal data. + * @param str A string where the two authentication factors are + * concatenated together + * @param len The length of the string or, if 0 is passed, + * then strlen(password) will be used internally. + * + * @return EOK on success + * ENOMEM on error + */ +errno_t sss_authtok_set_2fa_single(struct sss_auth_token *tok, + const char *str, size_t len); + +/** + * @brief Returns a const string if the auth token is of type + SSS_AUTHTOK_TYPE_OAUTH2, otherwise it returns an error + * + * @param tok A pointer to an sss_auth_token + * @param pwd A pointer to a const char *, that will point to a null + * terminated string + * @param len The length of the credential string + * + * @return EOK on success + * ENOENT if the token is empty + * EACCESS if the token is not a password token + */ +errno_t sss_authtok_get_oauth2(struct sss_auth_token *tok, + const char **str, size_t *len); + +/** + * @brief Set one-time password into an auth token, replacing any previous data. + * + * @param tok A pointer to an sss_auth_token structure to change, also + * used as a memory context to allocate the internal data. + * @param str A string that holds the one-time password. + * @param len The length of the string or, if 0 is passed, + * then strlen(password) will be used internally. + * + * @return EOK on success + * ENOMEM on error + */ +errno_t sss_authtok_set_oauth2(struct sss_auth_token *tok, + const char *str, size_t len); +/** + * @brief Returns a const string if the auth token is of type + SSS_AUTHTOK_TYPE_PASSKEY_REPLY, otherwise it returns an error + * + * @param tok A pointer to an sss_auth_token + * @param str A string that holds the passkey assertion data + * @param len The length of the credential string + * + * @return EOK on success + * ENOENT if the token is empty + * EACCESS if the token is not a passkey token + */ +errno_t sss_authtok_set_passkey_reply(struct sss_auth_token *tok, + const char *str, size_t len); +/** + * @brief Returns a const string if the auth token is of type + SSS_AUTHTOK_TYPE_PASSKEY_REPLY, otherwise it returns an error + * + * @param tok A pointer to an sss_auth_token + * @param str A pointer to a const char *, that will point to a null + * terminated string + * @param len The length of the credential string + * + * @return EOK on success + * ENOENT if the token is empty + * EACCESS if the token is not a password token + */ +errno_t sss_authtok_get_passkey_reply(struct sss_auth_token *tok, + const char **str, size_t *len); + +/** + * @brief Returns a const string if the auth token is of type + SSS_AUTHTOK_TYPE_PASSKEY, otherwise it returns an error + * + * @param mem_ctx Parent talloc context to attach to + * @param tok A pointer to an sss_auth_token + * @param prompt A pointer to a const char *, that will point to a null + * terminated string + * @param key A pointer to a const char *, that will point to a null + * terminated string + * @param pin A pointer to a const char *, that will point to a null + * terminated string + * + * @return EOK on success + * ENOENT if the token is empty + * EACCESS if the token is not a password token + */ +errno_t sss_authtok_get_passkey(TALLOC_CTX *mem_ctx, + struct sss_auth_token *tok, + const char **_prompt, const char **_key, + const char **_pin, size_t *_pin_len); +/** + * @brief Returns a const string if the auth token is of type + SSS_AUTHTOK_TYPE_PASSKEY, otherwise it returns the error code + * + * @param tok A pointer to an sss_auth_token + * @param pwd A pointer to a const char *, that will point to a null + * terminated string + * @param len The length of the credential string + * + * @return EOK on success + * ENOENT if the token is empty + * EACCESS if the token is not a password token + */ +errno_t sss_authtok_get_passkey_pin(struct sss_auth_token *tok, + const char **pin, size_t *len); + +/** + * @brief Set passkey kerberos preauth credentials into an auth token, + * replacing any previous data. + * + * @param tok A pointer to an sss_auth_token structure to change, also + * used as a memory context to allocate the internal data. + * @param pin A string that holds the passkey PIN. + * @param len The length of the string or, if 0 is passed, + * then strlen(password) will be used internally. + * + * @return EOK on success + * ENOMEM on error + */ +errno_t sss_authtok_set_passkey_krb(struct sss_auth_token *tok, + const char *prompt, const char *key, + const char *pin); +#endif /* __AUTHTOK_H__ */ diff --git a/src/util/backup_file.c b/src/util/backup_file.c new file mode 100644 index 0000000..a164a86 --- /dev/null +++ b/src/util/backup_file.c @@ -0,0 +1,120 @@ +/* + SSSD + + Backup files + + Copyright (C) Simo Sorce 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include <fcntl.h> +#include <stddef.h> +#include <stdlib.h> + +#define BUFFER_SIZE 65536 + +int backup_file(const char *src_file, int dbglvl) +{ + TALLOC_CTX *tmp_ctx = NULL; + char buf[BUFFER_SIZE]; + int src_fd = -1; + int dst_fd = -1; + char *dst_file; + ssize_t numread; + ssize_t written; + int ret, i; + + src_fd = open(src_file, O_RDONLY); + if (src_fd < 0) { + ret = errno; + DEBUG(dbglvl, "Error (%d [%s]) opening source file %s\n", + ret, strerror(ret), src_file); + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + goto done; + } + + /* try a few times to come up with a new backup file, then give up */ + for (i = 0; i < 10; i++) { + if (i == 0) { + dst_file = talloc_asprintf(tmp_ctx, "%s.bak", src_file); + } else { + dst_file = talloc_asprintf(tmp_ctx, "%s.bak%d", src_file, i); + } + if (!dst_file) { + ret = ENOMEM; + goto done; + } + + errno = 0; + dst_fd = open(dst_file, O_CREAT|O_EXCL|O_WRONLY, 0600); + ret = errno; + + if (dst_fd >= 0) break; + + if (ret != EEXIST) { + DEBUG(dbglvl, "Error (%d [%s]) opening destination file %s\n", + ret, strerror(ret), dst_file); + goto done; + } + } + if (ret != 0) { + DEBUG(dbglvl, "Error (%d [%s]) opening destination file %s\n", + ret, strerror(ret), dst_file); + goto done; + } + + /* copy file contents */ + while (1) { + errno = 0; + numread = sss_atomic_read_s(src_fd, buf, BUFFER_SIZE); + if (numread < 0) { + ret = errno; + DEBUG(dbglvl, "Error (%d [%s]) reading from source %s\n", + ret, strerror(ret), src_file); + goto done; + } + if (numread == 0) break; + + errno = 0; + written = sss_atomic_write_s(dst_fd, buf, numread); + if (written == -1) { + ret = errno; + DEBUG(dbglvl, "Error (%d [%s]) writing to destination %s\n", + ret, strerror(ret), dst_file); + goto done; + } + + if (written != numread) { + DEBUG(dbglvl, "Wrote %zd bytes expected %zd bytes\n", + written, numread); + ret = EIO; + goto done; + } + } + + ret = EOK; + +done: + if (src_fd != -1) close(src_fd); + if (dst_fd != -1) close(dst_fd); + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/util/become_user.c b/src/util/become_user.c new file mode 100644 index 0000000..c3f726d --- /dev/null +++ b/src/util/become_user.c @@ -0,0 +1,212 @@ +/* + SSSD + + Kerberos 5 Backend Module -- Utilities + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include <grp.h> + +errno_t become_user(uid_t uid, gid_t gid) +{ + uid_t cuid; + int ret; + + DEBUG(SSSDBG_FUNC_DATA, + "Trying to become user [%"SPRIuid"][%"SPRIgid"].\n", uid, gid); + + /* skip call if we already are the requested user */ + cuid = geteuid(); + if (uid == cuid) { + DEBUG(SSSDBG_FUNC_DATA, "Already user [%"SPRIuid"].\n", uid); + return EOK; + } + + /* drop supplementary groups first */ + ret = setgroups(0, NULL); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "setgroups failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + /* change GID so that root cannot be regained (changes saved GID too) */ + ret = setresgid(gid, gid, gid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "setresgid failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + /* change UID so that root cannot be regained (changes saved UID too) */ + /* this call also takes care of dropping CAP_SETUID, so this is a PNR */ + ret = setresuid(uid, uid, uid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "setresuid failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + return EOK; +} + +struct sss_creds { + uid_t uid; + gid_t gid; + int num_gids; + gid_t gids[]; +}; + +errno_t restore_creds(struct sss_creds *saved_creds); + +/* This is a reversible version of become_user, and returns the saved + * credentials so that creds can be switched back calling restore_creds */ +errno_t switch_creds(TALLOC_CTX *mem_ctx, + uid_t uid, gid_t gid, + int num_gids, gid_t *gids, + struct sss_creds **saved_creds) +{ + struct sss_creds *ssc = NULL; + int size; + int ret; + uid_t myuid; + uid_t mygid; + + DEBUG(SSSDBG_FUNC_DATA, "Switch user to [%d][%d].\n", uid, gid); + + myuid = geteuid(); + mygid = getegid(); + + if (saved_creds) { + /* save current user credentials */ + size = getgroups(0, NULL); + if (size == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Getgroups failed! (%d, %s)\n", + ret, strerror(ret)); + goto done; + } + + ssc = talloc_size(mem_ctx, + (sizeof(struct sss_creds) + size * sizeof(gid_t))); + if (!ssc) { + DEBUG(SSSDBG_CRIT_FAILURE, "Allocation failed!\n"); + ret = ENOMEM; + goto done; + } + ssc->num_gids = size; + + size = getgroups(ssc->num_gids, ssc->gids); + if (size == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Getgroups failed! (%d, %s)\n", + ret, strerror(ret)); + /* free ssc immediately otherwise the code will try to restore + * wrong creds */ + talloc_zfree(ssc); + goto done; + } + + /* we care only about effective ids */ + ssc->uid = myuid; + ssc->gid = mygid; + } + + /* if we are regaining root, set EUID first so that we have CAP_SETUID back, + * and the other calls work too, otherwise call it last so that we can + * change groups before we loose CAP_SETUID */ + if (uid == 0) { + ret = setresuid(0, 0, 0); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "setresuid failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + } + + /* TODO: use libcap-ng if we need to get/set capabilities too? */ + + if (myuid == uid && mygid == gid) { + DEBUG(SSSDBG_FUNC_DATA, "Already user [%"SPRIuid"].\n", uid); + talloc_zfree(ssc); + return EOK; + } + + /* try to setgroups first should always work if CAP_SETUID is set, + * otherwise it will always fail, failure is not critical though as + * generally we only really care about UID and at most primary GID */ + ret = setgroups(num_gids, gids); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_TRACE_FUNC, + "setgroups failed [%d][%s].\n", ret, strerror(ret)); + } + + /* change GID now, (leaves saved GID to current, so we can restore) */ + ret = setresgid(-1, gid, -1); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "setresgid failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + if (uid != 0) { + /* change UID, (leaves saved UID to current, so we can restore) */ + ret = setresuid(-1, uid, -1); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "setresuid failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + } + + ret = 0; + +done: + if (ret) { + /* attempt to restore creds first */ + restore_creds(ssc); + talloc_free(ssc); + } else if (saved_creds) { + *saved_creds = ssc; + } + return ret; +} + +errno_t restore_creds(struct sss_creds *saved_creds) +{ + if (saved_creds == NULL) { + /* In case save_creds was saved with the UID already dropped */ + return EOK; + } + + return switch_creds(saved_creds, + saved_creds->uid, + saved_creds->gid, + saved_creds->num_gids, + saved_creds->gids, NULL); +} diff --git a/src/util/cert.h b/src/util/cert.h new file mode 100644 index 0000000..0c95865 --- /dev/null +++ b/src/util/cert.h @@ -0,0 +1,50 @@ +/* + SSSD - certificate handling utils - openssl version + + Copyright (C) Sumit Bose <sbose@redhat.com> 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdint.h> +#include <talloc.h> + +#include "util/util.h" + +#ifndef __CERT_H__ +#define __CERT_H__ + +errno_t sss_cert_der_to_pem(TALLOC_CTX *mem_ctx, const uint8_t *der_blob, + size_t der_size, char **pem, size_t *pem_size); + +errno_t sss_cert_pem_to_der(TALLOC_CTX *mem_ctx, const char *pem, + uint8_t **der_blob, size_t *der_size); + +errno_t sss_cert_derb64_to_pem(TALLOC_CTX *mem_ctx, const char *derb64, + char **pem, size_t *pem_size); + +errno_t sss_cert_pem_to_derb64(TALLOC_CTX *mem_ctx, const char *pem, + char **derb64); + +errno_t bin_to_ldap_filter_value(TALLOC_CTX *mem_ctx, + const uint8_t *blob, size_t blob_size, + char **_str); + +errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx, + uint8_t *der_blob, size_t der_size, + uint8_t **key_blob, size_t *key_size); + +errno_t get_ssh_key_from_derb64(TALLOC_CTX *mem_ctx, const char *derb64, + uint8_t **key_blob, size_t *key_size); +#endif /* __CERT_H__ */ diff --git a/src/util/cert/cert_common.c b/src/util/cert/cert_common.c new file mode 100644 index 0000000..610d755 --- /dev/null +++ b/src/util/cert/cert_common.c @@ -0,0 +1,138 @@ +/* + SSSD - certificate handling utils + + Copyright (C) Sumit Bose <sbose@redhat.com> 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "util/cert.h" +#include "util/crypto/sss_crypto.h" + +errno_t sss_cert_derb64_to_pem(TALLOC_CTX *mem_ctx, const char *derb64, + char **pem, size_t *pem_size) +{ + int ret; + unsigned char *der; + size_t der_size; + + if (derb64 == NULL) { + return EINVAL; + } + + der = sss_base64_decode(mem_ctx, derb64, &der_size); + if (der == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); + return EINVAL; + } + + ret = sss_cert_der_to_pem(mem_ctx, der, der_size, pem, pem_size); + talloc_free(der); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_cert_der_to_pem failed.\n"); + } + + return ret; +} + +errno_t sss_cert_pem_to_derb64(TALLOC_CTX *mem_ctx, const char *pem, + char **derb64) +{ + int ret; + uint8_t *der; + size_t der_size; + + ret = sss_cert_pem_to_der(mem_ctx, pem, &der, &der_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_cert_pem_to_der failed.\n"); + return ret; + } + + *derb64 = sss_base64_encode(mem_ctx, der, der_size); + talloc_free(der); + if (*derb64 == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_encode failed.\n"); + return EINVAL; + } + + return EOK; +} + +errno_t bin_to_ldap_filter_value(TALLOC_CTX *mem_ctx, + const uint8_t *blob, size_t blob_size, + char **_str) +{ + int ret; + size_t c; + size_t len; + char *str = NULL; + char *p; + + if (blob == NULL || blob_size == 0 || _str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing input parameter.\n"); + return EINVAL; + } + + len = (blob_size * 3) + 1; + str = talloc_size(mem_ctx, len); + if (str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + str[len - 1] = '\0'; + + p = str; + for (c = 0; c < blob_size; c++) { + ret = snprintf(p, 4, "\\%02x", blob[c]); + if (ret != 3) { + DEBUG(SSSDBG_OP_FAILURE, "snprintf failed.\n"); + ret = EIO; + goto done; + } + + p += 3; + } + + ret = EOK; + +done: + if (ret == EOK) { + *_str = str; + } else { + talloc_free(str); + } + + return ret; +} + +errno_t get_ssh_key_from_derb64(TALLOC_CTX *mem_ctx, const char *derb64, + uint8_t **key_blob, size_t *key_size) +{ + int ret; + uint8_t *der_blob; + size_t der_size; + + der_blob = sss_base64_decode(mem_ctx, derb64, &der_size); + if (der_blob == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); + return EIO; + } + + ret = get_ssh_key_from_cert(mem_ctx, der_blob, der_size, + key_blob, key_size); + talloc_free(der_blob); + + return ret; +} diff --git a/src/util/cert/libcrypto/cert.c b/src/util/cert/libcrypto/cert.c new file mode 100644 index 0000000..61908c7 --- /dev/null +++ b/src/util/cert/libcrypto/cert.c @@ -0,0 +1,425 @@ +/* + SSSD - certificate handling utils - OpenSSL version + + Copyright (C) Sumit Bose <sbose@redhat.com> 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <openssl/x509.h> +#include <openssl/bio.h> +#include <openssl/pem.h> + +#include "util/util.h" +#include "util/sss_endian.h" + +errno_t sss_cert_der_to_pem(TALLOC_CTX *mem_ctx, const uint8_t *der_blob, + size_t der_size, char **pem, size_t *pem_size) +{ + X509 *x509 = NULL; + BIO *bio_mem = NULL; + const unsigned char *d; + int ret; + long p_size; + char *p; + + if (der_blob == NULL || der_size == 0) { + return EINVAL; + } + + d = (const unsigned char *) der_blob; + + x509 = d2i_X509(NULL, &d, (int) der_size); + if (x509 == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "d2i_X509 failed.\n"); + return EINVAL; + } + + bio_mem = BIO_new(BIO_s_mem()); + if (bio_mem == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "BIO_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = PEM_write_bio_X509(bio_mem, x509); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "PEM_write_bio_X509 failed.\n"); + ret = EIO; + goto done; + } + + p_size = BIO_get_mem_data(bio_mem, &p); + if (p_size == 0) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected PEM size [%ld].\n", p_size); + ret = EINVAL; + goto done; + } + + if (pem != NULL) { + *pem = talloc_strndup(mem_ctx, p, p_size); + if (*pem == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_memdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + + if (pem_size != NULL) { + *pem_size = p_size; + } + + ret = EOK; + +done: + X509_free(x509); + BIO_free_all(bio_mem); + + return ret; +} + +errno_t sss_cert_pem_to_der(TALLOC_CTX *mem_ctx, const char *pem, + uint8_t **_der_blob, size_t *_der_size) +{ + X509 *x509 = NULL; + BIO *bio_mem = NULL; + int ret; + unsigned char *buf; + int buf_size; + uint8_t *der_blob; + size_t der_size; + + if (pem == NULL) { + return EINVAL; + } + + bio_mem = BIO_new(BIO_s_mem()); + if (bio_mem == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "BIO_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = BIO_puts(bio_mem, pem); + if (ret <= 0) { + DEBUG(SSSDBG_OP_FAILURE, "BIO_puts failed.\n"); + ret = EIO; + goto done; + } + + x509 = PEM_read_bio_X509(bio_mem, NULL, NULL, NULL); + if (x509 == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "PEM_read_bio_X509 failed.\n"); + ret = EIO; + goto done; + } + + buf_size = i2d_X509(x509, NULL); + if (buf_size <= 0) { + DEBUG(SSSDBG_OP_FAILURE, "i2d_X509 failed.\n"); + ret = EIO; + goto done; + } + + if (_der_blob != NULL) { + buf = talloc_size(mem_ctx, buf_size); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + ret = ENOMEM; + goto done; + } + + der_blob = buf; + + der_size = i2d_X509(x509, &buf); + if (der_size != buf_size) { + talloc_free(der_blob); + DEBUG(SSSDBG_CRIT_FAILURE, + "i2d_X509 size mismatch between two calls.\n"); + ret = EIO; + goto done; + } + + *_der_blob = der_blob; + } + + if (_der_size != NULL) { + *_der_size = buf_size; + } + + ret = EOK; + +done: + X509_free(x509); + BIO_free_all(bio_mem); + + return ret; + +} + +/* SSH EC keys are defined in https://tools.ietf.org/html/rfc5656 */ +#define ECDSA_SHA2_HEADER "ecdsa-sha2-" +/* Looks like OpenSSH currently only supports the following 3 required + * curves. */ +#define IDENTIFIER_NISTP256 "nistp256" +#define IDENTIFIER_NISTP384 "nistp384" +#define IDENTIFIER_NISTP521 "nistp521" + +static errno_t ec_pub_key_to_ssh(TALLOC_CTX *mem_ctx, EVP_PKEY *cert_pub_key, + uint8_t **key_blob, size_t *key_size) +{ + int ret; + size_t c; + uint8_t *buf = NULL; + size_t buf_len; + EC_KEY *ec_key = NULL; + const EC_GROUP *ec_group = NULL; + const EC_POINT *ec_public_key = NULL; + BN_CTX *bn_ctx = NULL; + int key_len; + const char *identifier = NULL; + int identifier_len; + const char *header = NULL; + int header_len; + + ec_key = EVP_PKEY_get1_EC_KEY(cert_pub_key); + if (ec_key == NULL) { + ret = ENOMEM; + goto done; + } + + ec_group = EC_KEY_get0_group(ec_key); + + switch(EC_GROUP_get_curve_name(ec_group)) { + case NID_X9_62_prime256v1: + identifier = IDENTIFIER_NISTP256; + header = ECDSA_SHA2_HEADER IDENTIFIER_NISTP256; + break; + case NID_secp384r1: + identifier = IDENTIFIER_NISTP384; + header = ECDSA_SHA2_HEADER IDENTIFIER_NISTP384; + break; + case NID_secp521r1: + identifier = IDENTIFIER_NISTP521; + header = ECDSA_SHA2_HEADER IDENTIFIER_NISTP521; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported curve [%s]\n", + OBJ_nid2sn(EC_GROUP_get_curve_name(ec_group))); + ret = EINVAL; + goto done; + } + + header_len = strlen(header); + identifier_len = strlen(identifier); + + ec_public_key = EC_KEY_get0_public_key(ec_key); + + bn_ctx = BN_CTX_new(); + if (bn_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "BN_CTX_new failed.\n"); + ret = ENOMEM; + goto done; + } + + key_len = EC_POINT_point2oct(ec_group, ec_public_key, + POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bn_ctx); + if (key_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "EC_POINT_point2oct failed.\n"); + ret = EINVAL; + goto done; + } + + buf_len = header_len + identifier_len + key_len + 3 * sizeof(uint32_t); + buf = talloc_size(mem_ctx, buf_len * sizeof(uint8_t)); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + ret = ENOMEM; + goto done; + } + + c = 0; + + SAFEALIGN_SET_UINT32(buf, htobe32(header_len), &c); + safealign_memcpy(&buf[c], header, header_len, &c); + + SAFEALIGN_SET_UINT32(&buf[c], htobe32(identifier_len), &c); + safealign_memcpy(&buf[c], identifier , identifier_len, &c); + + SAFEALIGN_SET_UINT32(&buf[c], htobe32(key_len), &c); + + if (EC_POINT_point2oct(ec_group, ec_public_key, + POINT_CONVERSION_UNCOMPRESSED, buf + c, key_len, + bn_ctx) + != key_len) { + DEBUG(SSSDBG_OP_FAILURE, "EC_POINT_point2oct failed.\n"); + ret = EINVAL; + goto done; + } + + *key_size = buf_len; + *key_blob = buf; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(buf); + } + + BN_CTX_free(bn_ctx); + EC_KEY_free(ec_key); + + return ret; +} + + +#define SSH_RSA_HEADER "ssh-rsa" +#define SSH_RSA_HEADER_LEN (sizeof(SSH_RSA_HEADER) - 1) + +static errno_t rsa_pub_key_to_ssh(TALLOC_CTX *mem_ctx, EVP_PKEY *cert_pub_key, + uint8_t **key_blob, size_t *key_size) +{ + int ret; + size_t c; + size_t size; + uint8_t *buf = NULL; + const BIGNUM *n; + const BIGNUM *e; + int modulus_len; + unsigned char modulus[OPENSSL_RSA_MAX_MODULUS_BITS/8]; + int exponent_len; + unsigned char exponent[OPENSSL_RSA_MAX_PUBEXP_BITS/8]; + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + const RSA *rsa_pub_key = NULL; + rsa_pub_key = EVP_PKEY_get0_RSA(cert_pub_key); + if (rsa_pub_key == NULL) { + ret = ENOMEM; + goto done; + } + + RSA_get0_key(rsa_pub_key, &n, &e, NULL); +#else + n = cert_pub_key->pkey.rsa->n; + e = cert_pub_key->pkey.rsa->e; +#endif + modulus_len = BN_bn2bin(n, modulus); + exponent_len = BN_bn2bin(e, exponent); + + size = SSH_RSA_HEADER_LEN + 3 * sizeof(uint32_t) + + modulus_len + + exponent_len + + 1; /* see comment about missing 00 below */ + if (exponent[0] & 0x80) + size++; + + buf = talloc_size(mem_ctx, size); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + ret = ENOMEM; + goto done; + } + + c = 0; + + SAFEALIGN_SET_UINT32(buf, htobe32(SSH_RSA_HEADER_LEN), &c); + safealign_memcpy(&buf[c], SSH_RSA_HEADER, SSH_RSA_HEADER_LEN, &c); + if (exponent[0] & 0x80){ + SAFEALIGN_SET_UINT32(&buf[c], htobe32(exponent_len+1), &c); + SAFEALIGN_SETMEM_VALUE(&buf[c], '\0', unsigned char, &c); + } else { + SAFEALIGN_SET_UINT32(&buf[c], htobe32(exponent_len), &c); + } + safealign_memcpy(&buf[c], exponent, exponent_len, &c); + + /* Adding missing 00 which AFAIK is added to make sure + * the bigint is handled as positive number */ + /* TODO: make a better check if 00 must be added or not, e.g. ... & 0x80) + */ + SAFEALIGN_SET_UINT32(&buf[c], htobe32(modulus_len + 1), &c); + SAFEALIGN_SETMEM_VALUE(&buf[c], '\0', unsigned char, &c); + safealign_memcpy(&buf[c], modulus, modulus_len, &c); + + *key_blob = buf; + *key_size = size; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(buf); + } + + return ret; +} + +errno_t get_ssh_key_from_cert(TALLOC_CTX *mem_ctx, + const uint8_t *der_blob, size_t der_size, + uint8_t **key_blob, size_t *key_size) +{ + int ret; + const unsigned char *d; + X509 *cert = NULL; + EVP_PKEY *cert_pub_key = NULL; + + if (der_blob == NULL || der_size == 0) { + return EINVAL; + } + + d = (const unsigned char *) der_blob; + + cert = d2i_X509(NULL, &d, (int) der_size); + if (cert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "d2i_X509 failed.\n"); + return EINVAL; + } + + cert_pub_key = X509_get_pubkey(cert); + if (cert_pub_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "X509_get_pubkey failed.\n"); + ret = EIO; + goto done; + } + + switch (EVP_PKEY_base_id(cert_pub_key)) { + case EVP_PKEY_RSA: + ret = rsa_pub_key_to_ssh(mem_ctx, cert_pub_key, key_blob, key_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "rsa_pub_key_to_ssh failed.\n"); + goto done; + } + break; + case EVP_PKEY_EC: + ret = ec_pub_key_to_ssh(mem_ctx, cert_pub_key, key_blob, key_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "rsa_pub_key_to_ssh failed.\n"); + goto done; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected RSA or EC public key, found unsupported [%d].\n", + EVP_PKEY_base_id(cert_pub_key)); + ret = EINVAL; + goto done; + } + +done: + + EVP_PKEY_free(cert_pub_key); + X509_free(cert); + + return ret; +} diff --git a/src/util/cert_derb64_to_ldap_filter.c b/src/util/cert_derb64_to_ldap_filter.c new file mode 100644 index 0000000..838320a --- /dev/null +++ b/src/util/cert_derb64_to_ldap_filter.c @@ -0,0 +1,113 @@ +/* + SSSD - helper function with dependency to libsss_certmap.so + + Copyright (C) Sumit Bose <sbose@redhat.com> 2020 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "util/cert.h" +#include "util/crypto/sss_crypto.h" +#include "lib/certmap/sss_certmap.h" + +errno_t sss_cert_derb64_to_ldap_filter(TALLOC_CTX *mem_ctx, const char *derb64, + const char *attr_name, + struct sss_certmap_ctx *certmap_ctx, + struct sss_domain_info *dom, + char **ldap_filter) +{ + int ret; + unsigned char *der; + size_t der_size; + char *val; + char *filter = NULL; + char **domains = NULL; + size_t c; + + if (derb64 == NULL || attr_name == NULL) { + return EINVAL; + } + + der = sss_base64_decode(mem_ctx, derb64, &der_size); + if (der == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); + return EINVAL; + } + + if (certmap_ctx == NULL) { + ret = bin_to_ldap_filter_value(mem_ctx, der, der_size, &val); + talloc_free(der); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "bin_to_ldap_filter_value failed.\n"); + return ret; + } + + *ldap_filter = talloc_asprintf(mem_ctx, "(%s=%s)", attr_name, val); + talloc_free(val); + if (*ldap_filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + return ENOMEM; + } + } else { + ret = sss_certmap_get_search_filter(certmap_ctx, der, der_size, + &filter, &domains); + talloc_free(der); + if (ret != 0) { + if (ret == ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Certificate does not match matching-rules.\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "sss_certmap_get_search_filter failed.\n"); + } + } else { + if (domains == NULL) { + if (IS_SUBDOMAIN(dom)) { + DEBUG(SSSDBG_TRACE_FUNC, + "Rule applies only to local domain.\n"); + ret = ENOENT; + } + } else { + for (c = 0; domains[c] != NULL; c++) { + if (strcasecmp(dom->name, domains[c]) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "Rule applies to current domain [%s].\n", + dom->name); + ret = EOK; + break; + } + } + if (domains[c] == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "Rule does not apply to current domain [%s].\n", + dom->name); + ret = ENOENT; + } + } + } + + if (ret == EOK) { + *ldap_filter = talloc_strdup(mem_ctx, filter); + if (*ldap_filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + } + } + sss_certmap_free_filter_and_domains(filter, domains); + return ret; + } + + return EOK; +} diff --git a/src/util/check_file.c b/src/util/check_file.c new file mode 100644 index 0000000..2203a41 --- /dev/null +++ b/src/util/check_file.c @@ -0,0 +1,105 @@ +/* + SSSD + + Check file permissions + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "util/util.h" + +static errno_t perform_checks(const char *filename, + struct stat *stat_buf, + uid_t uid, gid_t gid, + mode_t mode, mode_t mask); + +errno_t check_file(const char *filename, + uid_t uid, uid_t gid, mode_t mode, mode_t mask, + struct stat *caller_stat_buf, bool follow_symlink) +{ + int ret; + struct stat local_stat_buf; + struct stat *stat_buf; + + if (caller_stat_buf == NULL) { + stat_buf = &local_stat_buf; + } else { + stat_buf = caller_stat_buf; + } + + if (follow_symlink) { + ret = stat(filename, stat_buf); + } else { + ret = lstat(filename, stat_buf); + } + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_TRACE_FUNC, "lstat for '%s' failed: [%d][%s].\n", + filename, ret, strerror(ret)); + return ret; + } + + return perform_checks(filename, stat_buf, uid, gid, mode, mask); +} + +static errno_t perform_checks(const char *filename, + struct stat *stat_buf, + uid_t uid, gid_t gid, + mode_t mode, mode_t mask) +{ + mode_t st_mode; + + if (mask) { + st_mode = stat_buf->st_mode & mask; + } else { + st_mode = stat_buf->st_mode & (S_IFMT|ALLPERMS); + } + + if ((mode & S_IFMT) != (st_mode & S_IFMT)) { + DEBUG(SSSDBG_TRACE_LIBS, "File '%s' is not of the right type.\n", + filename); + return EINVAL; + } + + if ((st_mode & ALLPERMS) != (mode & ALLPERMS)) { + DEBUG(SSSDBG_TRACE_LIBS, + "File '%s' has the wrong (bit masked) mode [%.7o], " + "expected [%.7o].\n", filename, + (st_mode & ALLPERMS), (mode & ALLPERMS)); + return EINVAL; + } + + if (uid != (uid_t)(-1) && stat_buf->st_uid != uid) { + DEBUG(SSSDBG_TRACE_LIBS, "File '%s' must be owned by uid [%d].\n", + filename, uid); + return EINVAL; + } + + if (gid != (gid_t)(-1) && stat_buf->st_gid != gid) { + DEBUG(SSSDBG_TRACE_LIBS, "File '%s' must be owned by gid [%d].\n", + filename, gid); + return EINVAL; + } + + return EOK; +} diff --git a/src/util/child_common.c b/src/util/child_common.c new file mode 100644 index 0000000..1dbf95b --- /dev/null +++ b/src/util/child_common.c @@ -0,0 +1,955 @@ +/* + SSSD + + Common helper functions to be used in child processes + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <sys/types.h> +#include <fcntl.h> +#include <signal.h> +#include <tevent.h> +#include <sys/wait.h> +#include <errno.h> +#include <sys/prctl.h> + +#include "util/util.h" +#include "util/find_uid.h" +#include "db/sysdb.h" +#include "util/child_common.h" + +struct sss_sigchild_ctx { + struct tevent_context *ev; + hash_table_t *children; + int options; +}; + +struct sss_child_ctx { + pid_t pid; + sss_child_fn_t cb; + void *pvt; + struct sss_sigchild_ctx *sigchld_ctx; +}; + +static errno_t child_debug_init(const char *logfile, int *debug_fd); + +static void sss_child_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data); + +errno_t sss_sigchld_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sss_sigchild_ctx **child_ctx) +{ + errno_t ret; + struct sss_sigchild_ctx *sigchld_ctx; + struct tevent_signal *tes; + + sigchld_ctx = talloc_zero(mem_ctx, struct sss_sigchild_ctx); + if (!sigchld_ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, + "fatal error initializing sss_sigchild_ctx\n"); + return ENOMEM; + } + sigchld_ctx->ev = ev; + + ret = sss_hash_create(sigchld_ctx, 0, &sigchld_ctx->children); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "fatal error initializing children hash table: [%s]\n", + strerror(ret)); + talloc_free(sigchld_ctx); + return ret; + } + + BlockSignals(false, SIGCHLD); + tes = tevent_add_signal(ev, sigchld_ctx, SIGCHLD, SA_SIGINFO, + sss_child_handler, sigchld_ctx); + if (tes == NULL) { + talloc_free(sigchld_ctx); + return EIO; + } + + *child_ctx = sigchld_ctx; + return EOK; +} + +static int sss_child_destructor(void *ptr) +{ + struct sss_child_ctx *child_ctx; + hash_key_t key; + int error; + + child_ctx = talloc_get_type(ptr, struct sss_child_ctx); + key.type = HASH_KEY_ULONG; + key.ul = child_ctx->pid; + + error = hash_delete(child_ctx->sigchld_ctx->children, &key); + if (error != HASH_SUCCESS && error != HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "failed to delete child_ctx from hash table [%d]: %s\n", + error, hash_error_string(error)); + } + + return 0; +} + +errno_t sss_child_register(TALLOC_CTX *mem_ctx, + struct sss_sigchild_ctx *sigchld_ctx, + pid_t pid, + sss_child_fn_t cb, + void *pvt, + struct sss_child_ctx **child_ctx) +{ + struct sss_child_ctx *child; + hash_key_t key; + hash_value_t value; + int error; + + child = talloc_zero(mem_ctx, struct sss_child_ctx); + if (child == NULL) { + return ENOMEM; + } + + child->pid = pid; + child->cb = cb; + child->pvt = pvt; + child->sigchld_ctx = sigchld_ctx; + + key.type = HASH_KEY_ULONG; + key.ul = pid; + + value.type = HASH_VALUE_PTR; + value.ptr = child; + + error = hash_enter(sigchld_ctx->children, &key, &value); + if (error != HASH_SUCCESS) { + talloc_free(child); + return ENOMEM; + } + + talloc_set_destructor((TALLOC_CTX *) child, sss_child_destructor); + + *child_ctx = child; + return EOK; +} + +struct sss_child_cb_pvt { + struct sss_child_ctx *child_ctx; + int wait_status; +}; + +static void sss_child_invoke_cb(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + struct sss_child_cb_pvt *cb_pvt; + struct sss_child_ctx *child_ctx; + hash_key_t key; + int error; + + cb_pvt = talloc_get_type(pvt, struct sss_child_cb_pvt); + child_ctx = cb_pvt->child_ctx; + + key.type = HASH_KEY_ULONG; + key.ul = child_ctx->pid; + + error = hash_delete(child_ctx->sigchld_ctx->children, &key); + if (error != HASH_SUCCESS && error != HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_OP_FAILURE, + "failed to delete child_ctx from hash table [%d]: %s\n", + error, hash_error_string(error)); + } + + if (child_ctx->cb) { + child_ctx->cb(child_ctx->pid, cb_pvt->wait_status, child_ctx->pvt); + } + + talloc_free(imm); +} + +static void sss_child_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + struct sss_sigchild_ctx *sigchld_ctx; + struct tevent_immediate *imm; + struct sss_child_cb_pvt *invoke_pvt; + struct sss_child_ctx *child_ctx; + hash_key_t key; + hash_value_t value; + int error; + int wait_status; + pid_t pid; + + sigchld_ctx = talloc_get_type(private_data, struct sss_sigchild_ctx); + key.type = HASH_KEY_ULONG; + + do { + do { + errno = 0; + pid = waitpid(-1, &wait_status, WNOHANG | sigchld_ctx->options); + } while (pid == -1 && errno == EINTR); + + if (pid == -1) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "waitpid failed [%d]: %s\n", errno, strerror(errno)); + return; + } else if (pid == 0) continue; + + key.ul = pid; + error = hash_lookup(sigchld_ctx->children, &key, &value); + if (error == HASH_SUCCESS) { + child_ctx = talloc_get_type(value.ptr, struct sss_child_ctx); + + imm = tevent_create_immediate(child_ctx); + if (imm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Out of memory invoking SIGCHLD callback\n"); + return; + } + + invoke_pvt = talloc_zero(child_ctx, struct sss_child_cb_pvt); + if (invoke_pvt == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "out of memory invoking SIGCHLD callback\n"); + return; + } + invoke_pvt->child_ctx = child_ctx; + invoke_pvt->wait_status = wait_status; + + tevent_schedule_immediate(imm, sigchld_ctx->ev, + sss_child_invoke_cb, invoke_pvt); + } else if (error == HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_TRACE_LIBS, + "BUG: waitpid() returned [%d] but it was not in the table. " + "This could be due to a linked library creating processes " + "without registering them with the sigchld handler\n", + pid); + /* We will simply ignore this and return to the loop + * This will prevent a zombie, but may cause unexpected + * behavior in the code that was trying to handle this + * pid. + */ + } else { + DEBUG(SSSDBG_OP_FAILURE, + "SIGCHLD hash table error [%d]: %s\n", + error, hash_error_string(error)); + /* This is bad, but we should try to check for other + * children anyway, to avoid potential zombies. + */ + } + } while (pid != 0); +} + +struct sss_child_ctx_old { + struct tevent_signal *sige; + pid_t pid; + int child_status; + sss_child_callback_t cb; + void *pvt; +}; + +static void child_sig_handler(struct tevent_context *ev, + struct tevent_signal *sige, int signum, + int count, void *__siginfo, void *pvt); + +int child_handler_setup(struct tevent_context *ev, int pid, + sss_child_callback_t cb, void *pvt, + struct sss_child_ctx_old **_child_ctx) +{ + struct sss_child_ctx_old *child_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Setting up signal handler up for pid [%d]\n", pid); + + child_ctx = talloc_zero(ev, struct sss_child_ctx_old); + if (child_ctx == NULL) { + return ENOMEM; + } + + child_ctx->sige = tevent_add_signal(ev, child_ctx, SIGCHLD, SA_SIGINFO, + child_sig_handler, child_ctx); + if(!child_ctx->sige) { + /* Error setting up signal handler */ + talloc_free(child_ctx); + return ENOMEM; + } + + child_ctx->pid = pid; + child_ctx->cb = cb; + child_ctx->pvt = pvt; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Signal handler set up for pid [%d]\n", pid); + + if (_child_ctx != NULL) { + *_child_ctx = child_ctx; + } + + return EOK; +} + +void child_handler_destroy(struct sss_child_ctx_old *ctx) +{ + errno_t ret; + + /* We still want to wait for the child to finish, but the caller is not + * interested in the result anymore (e.g. timeout was reached). */ + ctx->cb = NULL; + ctx->pvt = NULL; + + ret = kill(ctx->pid, SIGKILL); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "kill failed [%d][%s].\n", ret, strerror(ret)); + } +} + +/* Async communication with the child process via a pipe */ + +struct _write_pipe_state { + int fd; + uint8_t *buf; + size_t len; + bool safe; + ssize_t written; +}; + +static void _write_pipe_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *pvt); + +static struct tevent_req *_write_pipe_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint8_t *buf, + size_t len, + bool safe, + int fd) +{ + struct tevent_req *req; + struct _write_pipe_state *state; + struct tevent_fd *fde; + + req = tevent_req_create(mem_ctx, &state, struct _write_pipe_state); + if (req == NULL) return NULL; + + state->fd = fd; + state->buf = buf; + state->len = len; + state->safe = safe; + state->written = 0; + + fde = tevent_add_fd(ev, state, fd, TEVENT_FD_WRITE, + _write_pipe_handler, req); + if (fde == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_fd failed.\n"); + goto fail; + } + + return req; + +fail: + talloc_zfree(req); + return NULL; +} + +static void _write_pipe_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct _write_pipe_state *state; + errno_t ret; + + state = tevent_req_data(req, struct _write_pipe_state); + + if (flags & TEVENT_FD_READ) { + DEBUG(SSSDBG_CRIT_FAILURE, + "_write_pipe_done called with TEVENT_FD_READ," + " this should not happen.\n"); + tevent_req_error(req, EINVAL); + return; + } + + errno = 0; + if (state->safe) { + state->written = sss_atomic_write_safe_s(state->fd, state->buf, state->len); + } else { + state->written = sss_atomic_write_s(state->fd, state->buf, state->len); + } + if (state->written == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "write failed [%d][%s].\n", ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (state->len != state->written) { + DEBUG(SSSDBG_CRIT_FAILURE, "Wrote %zd bytes, expected %zu\n", + state->written, state->len); + tevent_req_error(req, EIO); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "All data has been sent!\n"); + tevent_req_done(req); + return; +} + +static int _write_pipe_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct tevent_req *write_pipe_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint8_t *buf, + size_t len, + int fd) +{ + return _write_pipe_send(mem_ctx, ev, buf, len, false, fd); +} + +int write_pipe_recv(struct tevent_req *req) +{ + return _write_pipe_recv(req); +} + +struct tevent_req *write_pipe_safe_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint8_t *buf, + size_t len, + int fd) +{ + return _write_pipe_send(mem_ctx, ev, buf, len, true, fd); +} + +int write_pipe_safe_recv(struct tevent_req *req) +{ + return _write_pipe_recv(req); +} + +struct _read_pipe_state { + int fd; + uint8_t *buf; + size_t len; + bool safe; +}; + +static void _read_pipe_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *pvt); + +static struct tevent_req *_read_pipe_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + bool safe, + int fd) +{ + struct tevent_req *req; + struct _read_pipe_state *state; + struct tevent_fd *fde; + + req = tevent_req_create(mem_ctx, &state, struct _read_pipe_state); + if (req == NULL) return NULL; + + state->fd = fd; + state->buf = NULL; + state->len = 0; + state->safe = safe; + + fde = tevent_add_fd(ev, state, fd, TEVENT_FD_READ, + _read_pipe_handler, req); + if (fde == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_fd failed.\n"); + goto fail; + } + + return req; + +fail: + talloc_zfree(req); + return NULL; +} + +static void _read_pipe_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct _read_pipe_state *state; + ssize_t size; + errno_t err; + uint8_t *buf; + size_t len = 0; + + state = tevent_req_data(req, struct _read_pipe_state); + + if (flags & TEVENT_FD_WRITE) { + DEBUG(SSSDBG_CRIT_FAILURE, "_read_pipe_done called with " + "TEVENT_FD_WRITE, this should not happen.\n"); + tevent_req_error(req, EINVAL); + return; + } + + buf = talloc_array(state, uint8_t, CHILD_MSG_CHUNK); + if (buf == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + if (state->safe) { + size = sss_atomic_read_safe_s(state->fd, buf, CHILD_MSG_CHUNK, &len); + if (size == -1 && errno == ERANGE) { + buf = talloc_realloc(state, buf, uint8_t, len); + if(!buf) { + tevent_req_error(req, ENOMEM); + return; + } + + size = sss_atomic_read_s(state->fd, buf, len); + } + } else { + size = sss_atomic_read_s(state->fd, buf, CHILD_MSG_CHUNK); + } + if (size == -1) { + err = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "read failed [%d][%s].\n", err, strerror(err)); + tevent_req_error(req, err); + return; + } else if (size > 0) { + state->buf = talloc_realloc(state, state->buf, uint8_t, + state->len + size); + if(!state->buf) { + tevent_req_error(req, ENOMEM); + return; + } + + safealign_memcpy(&state->buf[state->len], buf, + size, &state->len); + + if (state->len == len) { + DEBUG(SSSDBG_TRACE_FUNC, "All data received\n"); + tevent_req_done(req); + } + return; + + } else if (size == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "EOF received, client finished\n"); + tevent_req_done(req); + return; + + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "unexpected return value of read [%zd].\n", size); + tevent_req_error(req, EINVAL); + return; + } +} + +static errno_t _read_pipe_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t **buf, + ssize_t *len) +{ + struct _read_pipe_state *state; + state = tevent_req_data(req, struct _read_pipe_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *buf = talloc_steal(mem_ctx, state->buf); + *len = state->len; + + return EOK; +} + +struct tevent_req *read_pipe_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd) +{ + return _read_pipe_send(mem_ctx, ev, false, fd); +} + +errno_t read_pipe_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t **_buf, + ssize_t *_len) +{ + return _read_pipe_recv(req, mem_ctx, _buf, _len); +} + +struct tevent_req *read_pipe_safe_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd) +{ + return _read_pipe_send(mem_ctx, ev, true, fd); +} + +errno_t read_pipe_safe_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t **_buf, + ssize_t *_len) +{ + return _read_pipe_recv(req, mem_ctx, _buf, _len); +} + +static void child_invoke_callback(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt); +static void child_sig_handler(struct tevent_context *ev, + struct tevent_signal *sige, int signum, + int count, void *__siginfo, void *pvt) +{ + int ret, err; + struct sss_child_ctx_old *child_ctx; + struct tevent_immediate *imm; + + if (count <= 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "SIGCHLD handler called with invalid child count\n"); + return; + } + + child_ctx = talloc_get_type(pvt, struct sss_child_ctx_old); + DEBUG(SSSDBG_TRACE_LIBS, "Waiting for child [%d].\n", child_ctx->pid); + + errno = 0; + ret = waitpid(child_ctx->pid, &child_ctx->child_status, WNOHANG); + + if (ret == -1) { + err = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "waitpid failed [%d][%s].\n", err, strerror(err)); + } else if (ret == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "waitpid did not find a child with changed status.\n"); + } else { + if (WIFEXITED(child_ctx->child_status)) { + if (WEXITSTATUS(child_ctx->child_status) != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "child [%d] failed with status [%d].\n", ret, + WEXITSTATUS(child_ctx->child_status)); + } else { + DEBUG(SSSDBG_CONF_SETTINGS, + "child [%d] finished successfully.\n", ret); + } + } else if (WIFSIGNALED(child_ctx->child_status)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "child [%d] was terminated by signal [%d].\n", ret, + WTERMSIG(child_ctx->child_status)); + } else { + if (WIFSTOPPED(child_ctx->child_status)) { + DEBUG(SSSDBG_TRACE_LIBS, + "child [%d] was stopped by signal [%d].\n", ret, + WSTOPSIG(child_ctx->child_status)); + } + if (WIFCONTINUED(child_ctx->child_status) == true) { + DEBUG(SSSDBG_TRACE_LIBS, + "child [%d] was resumed by delivery of SIGCONT.\n", + ret); + } + + return; + } + + /* Invoke the callback in a tevent_immediate handler + * so that it is safe to free the tevent_signal * + */ + imm = tevent_create_immediate(child_ctx); + if (imm == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Out of memory invoking sig handler callback\n"); + return; + } + + tevent_schedule_immediate(imm, ev, child_invoke_callback, + child_ctx); + } + + return; +} + +static void child_invoke_callback(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + struct sss_child_ctx_old *child_ctx = + talloc_get_type(pvt, struct sss_child_ctx_old); + if (child_ctx->cb) { + child_ctx->cb(child_ctx->child_status, child_ctx->sige, child_ctx->pvt); + } + + /* Stop monitoring for this child */ + talloc_free(child_ctx); +} + +static errno_t prepare_child_argv(TALLOC_CTX *mem_ctx, + int child_debug_fd, + const char *binary, + const char *extra_argv[], + bool extra_args_only, + char ***_argv) +{ + /* + * program name, debug_level, debug_timestamps, + * debug_microseconds, PR_SET_DUMPABLE and NULL + */ + uint_t argc = 6; + char ** argv = NULL; + errno_t ret = EINVAL; + size_t i; + + if (extra_args_only) { + argc = 2; /* program name and NULL */ + } + + /* Save the current state in case an interrupt changes it */ + bool child_debug_timestamps = debug_timestamps; + bool child_debug_microseconds = debug_microseconds; + + if (!extra_args_only) { + argc++; + } + + if (extra_argv) { + for (i = 0; extra_argv[i]; i++) argc++; + } + + /* + * program name, debug_level, debug_timestamps, + * debug_microseconds and NULL + */ + argv = talloc_array(mem_ctx, char *, argc); + if (argv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed.\n"); + return ENOMEM; + } + + argv[--argc] = NULL; + + /* Add extra_attrs first */ + if (extra_argv) { + for (i = 0; extra_argv[i]; i++) { + argv[--argc] = talloc_strdup(argv, extra_argv[i]); + if (argv[argc] == NULL) { + ret = ENOMEM; + goto fail; + } + } + } + + if (!extra_args_only) { + argv[--argc] = talloc_asprintf(argv, "--debug-level=%#.4x", + debug_level); + if (argv[argc] == NULL) { + ret = ENOMEM; + goto fail; + } + + if (sss_logger == FILES_LOGGER) { + argv[--argc] = talloc_asprintf(argv, "--debug-fd=%d", + child_debug_fd); + if (argv[argc] == NULL) { + ret = ENOMEM; + goto fail; + } + } else { + argv[--argc] = talloc_asprintf(argv, "--logger=%s", + sss_logger_str[sss_logger]); + if (argv[argc] == NULL) { + ret = ENOMEM; + goto fail; + } + } + + argv[--argc] = talloc_asprintf(argv, "--debug-timestamps=%d", + child_debug_timestamps); + if (argv[argc] == NULL) { + ret = ENOMEM; + goto fail; + } + + argv[--argc] = talloc_asprintf(argv, "--debug-microseconds=%d", + child_debug_microseconds); + if (argv[argc] == NULL) { + ret = ENOMEM; + goto fail; + } + + argv[--argc] = talloc_asprintf(argv, "--dumpable=%d", + prctl(PR_GET_DUMPABLE)); + if (argv[argc] == NULL) { + ret = ENOMEM; + goto fail; + } + } + + argv[--argc] = talloc_strdup(argv, binary); + if (argv[argc] == NULL) { + ret = ENOMEM; + goto fail; + } + + if (argc != 0) { + ret = EINVAL; + goto fail; + } + + *_argv = argv; + + return EOK; + +fail: + talloc_free(argv); + return ret; +} + +void exec_child_ex(TALLOC_CTX *mem_ctx, + int *pipefd_to_child, int *pipefd_from_child, + const char *binary, const char *logfile, + const char *extra_argv[], bool extra_args_only, + int child_in_fd, int child_out_fd) +{ + int ret; + errno_t err; + char **argv; + int debug_fd = -1; + + if (logfile) { + ret = child_debug_init(logfile, &debug_fd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "child_debug_init() failed.\n"); + exit(EXIT_FAILURE); + } + } else { + debug_fd = STDERR_FILENO; + } + + close(pipefd_to_child[1]); + ret = dup2(pipefd_to_child[0], child_in_fd); + if (ret == -1) { + err = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "dup2 failed [%d][%s].\n", err, strerror(err)); + exit(EXIT_FAILURE); + } + + close(pipefd_from_child[0]); + ret = dup2(pipefd_from_child[1], child_out_fd); + if (ret == -1) { + err = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "dup2 failed [%d][%s].\n", err, strerror(err)); + exit(EXIT_FAILURE); + } + + ret = prepare_child_argv(mem_ctx, debug_fd, + binary, extra_argv, extra_args_only, + &argv); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "prepare_child_argv() failed.\n"); + exit(EXIT_FAILURE); + } + + execv(binary, argv); + err = errno; + DEBUG(SSSDBG_OP_FAILURE, "execv failed [%d][%s].\n", err, strerror(err)); + exit(EXIT_FAILURE); +} + +void exec_child(TALLOC_CTX *mem_ctx, + int *pipefd_to_child, int *pipefd_from_child, + const char *binary, const char *logfile) +{ + exec_child_ex(mem_ctx, pipefd_to_child, pipefd_from_child, + binary, logfile, NULL, false, + STDIN_FILENO, STDOUT_FILENO); +} + +int child_io_destructor(void *ptr) +{ + int ret; + struct child_io_fds *io = talloc_get_type(ptr, struct child_io_fds); + if (io == NULL) return EOK; + + if (io->write_to_child_fd != -1) { + ret = close(io->write_to_child_fd); + io->write_to_child_fd = -1; + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "close failed [%d][%s].\n", ret, strerror(ret)); + } + } + + if (io->read_from_child_fd != -1) { + ret = close(io->read_from_child_fd); + io->read_from_child_fd = -1; + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "close failed [%d][%s].\n", ret, strerror(ret)); + } + } + + return EOK; +} + +static errno_t child_debug_init(const char *logfile, int *debug_fd) +{ + int ret; + FILE *debug_filep; + + if (debug_fd == NULL) { + return EOK; + } + + if (sss_logger == FILES_LOGGER && *debug_fd == -1) { + ret = open_debug_file_ex(logfile, &debug_filep, false); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error setting up logging (%d) [%s]\n", + ret, sss_strerror(ret)); + return ret; + } + + *debug_fd = fileno(debug_filep); + if (*debug_fd == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "fileno failed [%d][%s]\n", ret, strerror(ret)); + return ret; + } + } + + return EOK; +} diff --git a/src/util/child_common.h b/src/util/child_common.h new file mode 100644 index 0000000..aa13e17 --- /dev/null +++ b/src/util/child_common.h @@ -0,0 +1,145 @@ +/* + SSSD + + Common helper functions to be used in child processes + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CHILD_COMMON_H__ +#define __CHILD_COMMON_H__ + +#include <errno.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <tevent.h> + +#include "util/util.h" + +#define IN_BUF_SIZE 2048 +#define CHILD_MSG_CHUNK 1024 + +#define SIGTERM_TO_SIGKILL_TIME 2 + +struct response { + uint8_t *buf; + size_t size; +}; + +struct io_buffer { + uint8_t *data; + size_t size; +}; + +struct child_io_fds { + int read_from_child_fd; + int write_to_child_fd; + pid_t pid; + bool child_exited; + bool in_use; +}; + +/* COMMON SIGCHLD HANDLING */ +typedef void (*sss_child_fn_t)(int pid, int wait_status, void *pvt); + +struct sss_sigchild_ctx; +struct sss_child_ctx; + +/* Create a new child context to manage callbacks */ +errno_t sss_sigchld_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sss_sigchild_ctx **child_ctx); + +errno_t sss_child_register(TALLOC_CTX *mem_ctx, + struct sss_sigchild_ctx *sigchld_ctx, + pid_t pid, + sss_child_fn_t cb, + void *pvt, + struct sss_child_ctx **child_ctx); + +/* Callback to be invoked when a sigchld handler is called. + * The tevent_signal * associated with the handler will be + * freed automatically when this function returns. + */ +typedef void (*sss_child_callback_t)(int child_status, + struct tevent_signal *sige, + void *pvt); + +struct sss_child_ctx_old; + +/* Set up child termination signal handler */ +int child_handler_setup(struct tevent_context *ev, int pid, + sss_child_callback_t cb, void *pvt, + struct sss_child_ctx_old **_child_ctx); + +/* Destroy child termination signal handler */ +void child_handler_destroy(struct sss_child_ctx_old *ctx); + +/* Async communication with the child process via a pipe */ +struct tevent_req *write_pipe_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint8_t *buf, + size_t len, + int fd); +int write_pipe_recv(struct tevent_req *req); + +struct tevent_req *read_pipe_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd); +errno_t read_pipe_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t **_buf, + ssize_t *_len); + +/* Include buffer length in a message header, read does not wait for EOF. */ +struct tevent_req *write_pipe_safe_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint8_t *buf, + size_t len, + int fd); +int write_pipe_safe_recv(struct tevent_req *req); + +struct tevent_req *read_pipe_safe_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd); +errno_t read_pipe_safe_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t **_buf, + ssize_t *_len); + +/* The pipes to communicate with the child must be nonblocking */ +void fd_nonblocking(int fd); + +/* Never returns EOK, ether returns an error, or doesn't return on success */ +void exec_child_ex(TALLOC_CTX *mem_ctx, + int *pipefd_to_child, int *pipefd_from_child, + const char *binary, const char *logfile, + const char *extra_argv[], bool extra_args_only, + int child_in_fd, int child_out_fd); + +/* Same as exec_child_ex() except child_in_fd is set to STDIN_FILENO and + * child_out_fd is set to STDOUT_FILENO and extra_argv is always NULL. + */ +void exec_child(TALLOC_CTX *mem_ctx, + int *pipefd_to_child, int *pipefd_from_child, + const char *binary, const char *logfile); + +int child_io_destructor(void *ptr); + +#endif /* __CHILD_COMMON_H__ */ diff --git a/src/util/crypto/libcrypto/crypto_base64.c b/src/util/crypto/libcrypto/crypto_base64.c new file mode 100644 index 0000000..11a0648 --- /dev/null +++ b/src/util/crypto/libcrypto/crypto_base64.c @@ -0,0 +1,133 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + George McCollister <george.mccollister@gmail.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "util/crypto/sss_crypto.h" + +#include <openssl/bio.h> +#include <openssl/evp.h> + +char *sss_base64_encode(TALLOC_CTX *mem_ctx, + const unsigned char *in, + size_t insize) +{ + char *b64encoded = NULL, *outbuf = NULL; + int i, j, b64size; + BIO *bmem, *b64; + + b64 = BIO_new(BIO_f_base64()); + if (!b64) return NULL; + + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + bmem = BIO_new(BIO_s_mem()); + if (!bmem) goto done; + + b64 = BIO_push(b64, bmem); + + BIO_write(b64, in, insize); + + (void) BIO_flush(b64); + + b64size = BIO_get_mem_data(bmem, &b64encoded); + if (b64encoded) { + outbuf = talloc_array(mem_ctx, char, b64size+1); + if (outbuf == NULL) goto done; + + for (i=0, j=0; i < b64size; i++) { + if (b64encoded[i] == '\n' || b64encoded[i] == '\r') { + continue; + } + outbuf[j++] = b64encoded[i]; + } + outbuf[j++] = '\0'; + } + +done: + BIO_free_all(b64); + return outbuf; +} + +unsigned char *sss_base64_decode(TALLOC_CTX *mem_ctx, + const char *in, + size_t *outsize) +{ + unsigned char *outbuf = NULL; + unsigned char *b64decoded = NULL; + unsigned char inbuf[512]; + char * in_dup; + int size, inlen = strlen(in); + BIO *bmem, *b64, *bmem_out; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return NULL; + } + + in_dup = talloc_size(tmp_ctx, inlen+1); + if (!in_dup) goto done; + memcpy(in_dup, in, inlen+1); + + b64 = BIO_new(BIO_f_base64()); + if (!b64) goto done; + + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + + bmem = BIO_new_mem_buf(in_dup, -1); + if (!bmem) { + BIO_free(b64); + goto done; + } + + b64 = BIO_push(b64, bmem); + + bmem_out = BIO_new(BIO_s_mem()); + if (!bmem_out) { + BIO_free_all(b64); + goto done; + } + + while((inlen = BIO_read(b64, inbuf, 512)) > 0) + BIO_write(bmem_out, inbuf, inlen); + + (void) BIO_flush(bmem_out); + + size = BIO_get_mem_data(bmem_out, &b64decoded); + + if (b64decoded) { + outbuf = talloc_memdup(mem_ctx, b64decoded, size); + if (!outbuf) { + BIO_free_all(b64); + BIO_free(bmem_out); + goto done; + } + + *outsize = size; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot get decoded data\n"); + } + BIO_free_all(b64); + BIO_free(bmem_out); + +done: + talloc_free(tmp_ctx); + return outbuf; +} diff --git a/src/util/crypto/libcrypto/crypto_hmac_sha1.c b/src/util/crypto/libcrypto/crypto_hmac_sha1.c new file mode 100644 index 0000000..9b072ad --- /dev/null +++ b/src/util/crypto/libcrypto/crypto_hmac_sha1.c @@ -0,0 +1,49 @@ +/* + Copyright (C) 2019 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <string.h> +#include <openssl/hmac.h> + +#include "util/util.h" +#include "util/crypto/sss_crypto.h" + + +int sss_hmac_sha1(const unsigned char *key, size_t key_len, + const unsigned char *in, size_t in_len, + unsigned char *out) +{ + unsigned int res_len = 0; + unsigned char md[EVP_MAX_MD_SIZE]; + + if ((key == NULL) || (key_len == 0) || (key_len > INT_MAX) + || (in == NULL) || (in_len == 0) || (in_len > INT_MAX) + || (out == NULL)) { + return EINVAL; + } + + if (!HMAC(EVP_sha1(), key, (int)key_len, in, (int)in_len, md, &res_len)) { + return EINVAL; + } + + if (res_len != SSS_SHA1_LENGTH) { + return EINVAL; + } + + memcpy(out, md, SSS_SHA1_LENGTH); + + return EOK; +} diff --git a/src/util/crypto/libcrypto/crypto_obfuscate.c b/src/util/crypto/libcrypto/crypto_obfuscate.c new file mode 100644 index 0000000..2cef61b --- /dev/null +++ b/src/util/crypto/libcrypto/crypto_obfuscate.c @@ -0,0 +1,313 @@ +/* + SSSD + + Password obfuscation logic + + Authors: + George McCollister <george.mccollister@gmail.com> + + Copyright (C) George McCollister 2012 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + * READ ME: + * + * Please note that password obfuscation does not improve security in any + * way. It is just a mechanism to make the password human-unreadable. + */ + +#include "config.h" +#include <talloc.h> +#include <errno.h> + +#include "util/util.h" +#include "util/crypto/sss_crypto.h" + +#include <openssl/evp.h> +#include <openssl/rand.h> + +#define OBF_BUFFER_SENTINEL "\0\1\2\3" +#define OBF_BUFFER_SENTINEL_SIZE 4 + +struct crypto_mech_data { + const EVP_CIPHER * (*cipher)(void); + uint16_t keylen; + uint16_t bsize; +}; + +static struct crypto_mech_data cmdata[] = { + /* AES with automatic padding, 256b key, 128b block */ + { EVP_aes_256_cbc, 32, 16 }, + /* sentinel */ + { 0, 0, 0 } +}; + +static struct crypto_mech_data *get_crypto_mech_data(enum obfmethod meth) +{ + if (meth >= NUM_OBFMETHODS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported cipher type\n"); + return NULL; + } + return &cmdata[meth]; +} + +int sss_password_encrypt(TALLOC_CTX *mem_ctx, const char *password, int plen, + enum obfmethod meth, char **obfpwd) +{ + int ret; + EVP_CIPHER_CTX *ctx; + struct crypto_mech_data *mech_props; + TALLOC_CTX *tmp_ctx = NULL; + unsigned char *keybuf; + unsigned char *ivbuf; + unsigned char *cryptotext; + int ct_maxsize; + int ctlen = 0; + int digestlen = 0; + int result_len; + + unsigned char *obfbuf; + size_t obufsize = 0; + size_t p = 0; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ret = ENOMEM; + goto done; + } + + mech_props = get_crypto_mech_data(meth); + if (mech_props == NULL) { + ret = EINVAL; + goto done; + } + + keybuf = talloc_array(tmp_ctx, unsigned char, mech_props->keylen); + if (keybuf == NULL) { + ret = ENOMEM; + goto done; + } + + ivbuf = talloc_array(tmp_ctx, unsigned char, mech_props->bsize); + if (ivbuf == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_generate_csprng_buffer((uint8_t *)keybuf, mech_props->keylen); + if (ret != EOK) { + goto done; + } + ret = sss_generate_csprng_buffer((uint8_t *)ivbuf, mech_props->bsize); + if (ret != EOK) { + goto done; + } + + /* cryptotext buffer must be at least len(plaintext)+blocksize */ + ct_maxsize = plen + (mech_props->bsize); + cryptotext = talloc_array(tmp_ctx, unsigned char, ct_maxsize); + if (!cryptotext) { + ret = ENOMEM; + goto done; + } + + if (!EVP_EncryptInit_ex(ctx, mech_props->cipher(), 0, keybuf, ivbuf)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failure to initialize cipher contex\n"); + ret = EIO; + goto done; + } + + /* sample data we'll encrypt and decrypt */ + if (!EVP_EncryptUpdate(ctx, cryptotext, &ctlen, (const unsigned char *)password, plen)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot execute the encryption operation\n"); + ret = EIO; + goto done; + } + + if (!EVP_EncryptFinal_ex(ctx, cryptotext + ctlen, &digestlen)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot finialize the encryption operation\n"); + ret = EIO; + goto done; + } + + result_len = ctlen + digestlen; + if (result_len < 0 || result_len > UINT16_MAX) { + ret = ERANGE; + goto done; + } + + /* Pack the obfuscation buffer */ + /* The buffer consists of: + * uint16_t the type of the cipher + * uint16_t length of the cryptotext in bytes (clen) + * uint8_t[klen] key + * uint8_t[blen] IV + * uint8_t[clen] cryptotext + * 4 bytes of "sentinel" denoting end of the buffer + */ + obufsize = sizeof(uint16_t) + sizeof(uint16_t) + + mech_props->keylen + mech_props->bsize + + result_len + OBF_BUFFER_SENTINEL_SIZE; + obfbuf = talloc_array(tmp_ctx, unsigned char, obufsize); + if (!obfbuf) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Writing method: %d\n", meth); + SAFEALIGN_SET_UINT16(&obfbuf[p], meth, &p); + DEBUG(SSSDBG_TRACE_FUNC, "Writing bufsize: %d\n", result_len); + SAFEALIGN_SET_UINT16(&obfbuf[p], result_len, &p); + safealign_memcpy(&obfbuf[p], keybuf, mech_props->keylen, &p); + safealign_memcpy(&obfbuf[p], ivbuf, mech_props->bsize, &p); + safealign_memcpy(&obfbuf[p], cryptotext, result_len, &p); + safealign_memcpy(&obfbuf[p], OBF_BUFFER_SENTINEL, + OBF_BUFFER_SENTINEL_SIZE, &p); + + /* Base64 encode the resulting buffer */ + *obfpwd = sss_base64_encode(mem_ctx, obfbuf, obufsize); + if (*obfpwd == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + EVP_CIPHER_CTX_free(ctx); + return ret; +} + +int sss_password_decrypt(TALLOC_CTX *mem_ctx, char *b64encoded, + char **password) +{ + int ret; + EVP_CIPHER_CTX *ctx; + TALLOC_CTX *tmp_ctx = NULL; + struct crypto_mech_data *mech_props; + + int plainlen; + int digestlen; + unsigned char *obfbuf = NULL; + size_t obflen; + char *pwdbuf; + + /* for unmarshaling data */ + uint16_t meth; + uint16_t ctsize; + size_t p = 0; + unsigned char *cryptotext; + unsigned char *keybuf; + unsigned char *ivbuf; + unsigned char sentinel_check[OBF_BUFFER_SENTINEL_SIZE]; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ret = ENOMEM; + goto done; + } + + /* Base64 decode the incoming buffer */ + obfbuf = sss_base64_decode(tmp_ctx, b64encoded, &obflen); + if (!obfbuf) { + ret = ENOMEM; + goto done; + } + + /* unpack obfuscation buffer */ + SAFEALIGN_COPY_UINT16_CHECK(&meth, obfbuf+p, obflen, &p); + DEBUG(SSSDBG_TRACE_FUNC, "Read method: %d\n", meth); + SAFEALIGN_COPY_UINT16_CHECK(&ctsize, obfbuf+p, obflen, &p); + DEBUG(SSSDBG_TRACE_FUNC, "Read bufsize: %d\n", ctsize); + + mech_props = get_crypto_mech_data(meth); + if (mech_props == NULL) { + ret = EINVAL; + goto done; + } + + /* check that we got sane mechanism properties and cryptotext size */ + memcpy(sentinel_check, + obfbuf + p + mech_props->keylen + mech_props->bsize + ctsize, + OBF_BUFFER_SENTINEL_SIZE); + if (memcmp(sentinel_check, OBF_BUFFER_SENTINEL, OBF_BUFFER_SENTINEL_SIZE) != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Obfuscation buffer seems corrupt, aborting\n"); + ret = EFAULT; + goto done; + } + + /* copy out key, ivbuf and cryptotext */ + keybuf = talloc_array(tmp_ctx, unsigned char, mech_props->keylen); + if (keybuf == NULL) { + ret = ENOMEM; + goto done; + } + safealign_memcpy(keybuf, obfbuf+p, mech_props->keylen, &p); + + ivbuf = talloc_array(tmp_ctx, unsigned char, mech_props->bsize); + if (ivbuf == NULL) { + ret = ENOMEM; + goto done; + } + safealign_memcpy(ivbuf, obfbuf+p, mech_props->bsize, &p); + + cryptotext = talloc_array(tmp_ctx, unsigned char, ctsize); + if (cryptotext == NULL) { + ret = ENOMEM; + goto done; + } + safealign_memcpy(cryptotext, obfbuf+p, ctsize, &p); + + pwdbuf = talloc_array(tmp_ctx, char, ctsize); + if (!pwdbuf) { + ret = ENOMEM; + goto done; + } + + if (!EVP_DecryptInit_ex(ctx, mech_props->cipher(), 0, keybuf, ivbuf)) { + ret = EIO; + goto done; + } + + /* sample data we'll encrypt and decrypt */ + if (!EVP_DecryptUpdate(ctx, (unsigned char *)pwdbuf, &plainlen, cryptotext, ctsize)) { + ret = EIO; + goto done; + } + + if (!EVP_DecryptFinal_ex(ctx, (unsigned char *)pwdbuf + plainlen, &digestlen)) { + ret = EIO; + goto done; + } + + *password = talloc_move(mem_ctx, &pwdbuf); + ret = EOK; +done: + talloc_free(tmp_ctx); + EVP_CIPHER_CTX_free(ctx); + return ret; +} diff --git a/src/util/crypto/libcrypto/crypto_prng.c b/src/util/crypto/libcrypto/crypto_prng.c new file mode 100644 index 0000000..32e5b60 --- /dev/null +++ b/src/util/crypto/libcrypto/crypto_prng.c @@ -0,0 +1,42 @@ +/* + Copyright (C) Red Hat 2019 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#include <errno.h> +#include <limits.h> +#include <stdlib.h> +#include <stdbool.h> +#include <time.h> +#include <sys/types.h> +#include <unistd.h> +#include <openssl/rand.h> + +#include "util/util_errors.h" +#include "util/crypto/sss_crypto.h" + +int sss_generate_csprng_buffer(uint8_t *buf, size_t size) +{ + if ((buf == NULL) || (size > INT_MAX)) { + return EINVAL; + } + + if (RAND_bytes((unsigned char *)buf, (int)size) == 1) { + return EOK; + } + + return EAGAIN; +} diff --git a/src/util/crypto/libcrypto/crypto_sha512crypt.c b/src/util/crypto/libcrypto/crypto_sha512crypt.c new file mode 100644 index 0000000..c816d26 --- /dev/null +++ b/src/util/crypto/libcrypto/crypto_sha512crypt.c @@ -0,0 +1,382 @@ +/* This file is based on nss_sha512crypt.c which is based on the work of + * Ulrich Drepper (http://people.redhat.com/drepper/SHA-crypt.txt). + * + * libcrypto is used to provide SHA512 and random number generation. + * (http://www.openssl.org/docs/crypto/crypto.html). + * + * Sumit Bose <sbose@redhat.com> + * George McCollister <georgem@novatech-llc.com> + */ +/* SHA512-based UNIX crypt implementation. + Released into the Public Domain by Ulrich Drepper <drepper@redhat.com>. */ + +#include "config.h" + +#include <errno.h> +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/param.h> +#include <sys/types.h> + +#include "util/util.h" +#include "util/sss_endian.h" +#include "util/crypto/sss_crypto.h" + +#include <openssl/evp.h> +#include <openssl/rand.h> + +#include "sss_openssl.h" + + +/* Define our magic string to mark salt for SHA512 "encryption" replacement. */ +const char sha512_salt_prefix[] = "$6$"; +#define SALT_PREF_SIZE (sizeof(sha512_salt_prefix) - 1) + +/* Prefix for optional rounds specification. */ +const char sha512_rounds_prefix[] = "rounds="; +#define ROUNDS_SIZE (sizeof(sha512_rounds_prefix) - 1) + +#define SALT_LEN_MAX 16 +#define ROUNDS_DEFAULT 5000 +#define ROUNDS_MIN 1000 +#define ROUNDS_MAX 999999999 + +/* Table with characters for base64 transformation. */ +const char b64t[64] = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +/* base64 conversion function */ +static inline void b64_from_24bit(char **dest, size_t *len, size_t n, + uint8_t b2, uint8_t b1, uint8_t b0) +{ + uint32_t w; + size_t i; + + if (*len < n) n = *len; + + w = (b2 << 16) | (b1 << 8) | b0; + for (i = 0; i < n; i++) { + (*dest)[i] = b64t[w & 0x3f]; + w >>= 6; + } + + *len -= i; + *dest += i; +} + +static int sha512_crypt_r(const char *key, + const char *salt, + char *buffer, size_t buflen) +{ + unsigned char temp_result[64]; + unsigned char alt_result[64]; + size_t rounds = ROUNDS_DEFAULT; + bool rounds_custom = false; + EVP_MD_CTX *alt_ctx = NULL; + EVP_MD_CTX *ctx; + size_t salt_len; + size_t key_len; + size_t cnt; + char *p_bytes = NULL; + char *s_bytes = NULL; + int p1, p2, p3, pt, n; + unsigned int part; + char *cp; + int ret; + + /* Find beginning of salt string. The prefix should normally always be + * present. Just in case it is not. */ + if (strncmp(salt, sha512_salt_prefix, SALT_PREF_SIZE) == 0) { + /* Skip salt prefix. */ + salt += SALT_PREF_SIZE; + } + + if (strncmp(salt, sha512_rounds_prefix, ROUNDS_SIZE) == 0) { + unsigned long int srounds; + const char *num; + char *endp; + + num = salt + ROUNDS_SIZE; + errno = 0; + srounds = strtoul(num, &endp, 10); + if (!errno && (*endp == '$')) { + salt = endp + 1; + if (srounds < ROUNDS_MIN) srounds = ROUNDS_MIN; + if (srounds > ROUNDS_MAX) srounds = ROUNDS_MAX; + rounds = srounds; + rounds_custom = true; + } + } + + salt_len = MIN(strcspn(salt, "$"), SALT_LEN_MAX); + key_len = strlen(key); + + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + ret = ENOMEM; + goto done; + } + + alt_ctx = EVP_MD_CTX_new(); + if (alt_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + /* Prepare for the real work. */ + if (!EVP_DigestInit_ex(ctx, EVP_sha512(), NULL)) { + ret = EIO; + goto done; + } + + /* Add the key string. */ + EVP_DigestUpdate(ctx, (const unsigned char *)key, key_len); + + /* The last part is the salt string. This must be at most 16 + * characters and it ends at the first `$' character (for + * compatibility with existing implementations). */ + EVP_DigestUpdate(ctx, (const unsigned char *)salt, salt_len); + + /* Compute alternate SHA512 sum with input KEY, SALT, and KEY. + * The final result will be added to the first context. */ + if (!EVP_DigestInit_ex(alt_ctx, EVP_sha512(), NULL)) { + ret = EIO; + goto done; + } + + /* Add key. */ + EVP_DigestUpdate(alt_ctx, (const unsigned char *)key, key_len); + + /* Add salt. */ + EVP_DigestUpdate(alt_ctx, (const unsigned char *)salt, salt_len); + + /* Add key again. */ + EVP_DigestUpdate(alt_ctx, (const unsigned char *)key, key_len); + + /* Now get result of this (64 bytes) and add it to the other context. */ + EVP_DigestFinal_ex(alt_ctx, alt_result, &part); + + /* Add for any character in the key one byte of the alternate sum. */ + for (cnt = key_len; cnt > 64; cnt -= 64) { + EVP_DigestUpdate(ctx, alt_result, 64); + } + EVP_DigestUpdate(ctx, alt_result, cnt); + + /* Take the binary representation of the length of the key and for every + * 1 add the alternate sum, for every 0 the key. */ + for (cnt = key_len; cnt > 0; cnt >>= 1) { + if ((cnt & 1) != 0) { + EVP_DigestUpdate(ctx, alt_result, 64); + } else { + EVP_DigestUpdate(ctx, (const unsigned char *)key, key_len); + } + } + + /* Create intermediate result. */ + EVP_DigestFinal_ex(ctx, alt_result, &part); + + /* Start computation of P byte sequence. */ + if (!EVP_DigestInit_ex(alt_ctx, EVP_sha512(), NULL)) { + ret = EIO; + goto done; + } + + /* For every character in the password add the entire password. */ + for (cnt = 0; cnt < key_len; cnt++) { + EVP_DigestUpdate(alt_ctx, (const unsigned char *)key, key_len); + } + + /* Finish the digest. */ + EVP_DigestFinal_ex(alt_ctx, temp_result, &part); + + /* Create byte sequence P. */ + cp = p_bytes = alloca(key_len); + for (cnt = key_len; cnt >= 64; cnt -= 64) { + cp = mempcpy(cp, temp_result, 64); + } + memcpy(cp, temp_result, cnt); + + /* Start computation of S byte sequence. */ + if (!EVP_DigestInit_ex(alt_ctx, EVP_sha512(), NULL)) { + ret = EIO; + goto done; + } + + for (cnt = 0; cnt < 16 + alt_result[0]; cnt++) { + EVP_DigestUpdate(alt_ctx, (const unsigned char *)salt, salt_len); + } + + /* Finish the digest. */ + EVP_DigestFinal_ex(alt_ctx, temp_result, &part); + + /* Create byte sequence S. */ + cp = s_bytes = alloca(salt_len); + for (cnt = salt_len; cnt >= 64; cnt -= 64) { + cp = mempcpy(cp, temp_result, 64); + } + memcpy(cp, temp_result, cnt); + + /* Repeatedly run the collected hash value through SHA512 to burn CPU cycles. */ + for (cnt = 0; cnt < rounds; cnt++) { + + if (!EVP_DigestInit_ex(ctx, EVP_sha512(), NULL)) { + ret = EIO; + goto done; + } + + /* Add key or last result. */ + if ((cnt & 1) != 0) { + EVP_DigestUpdate(ctx, (const unsigned char *)p_bytes, key_len); + } else { + EVP_DigestUpdate(ctx, alt_result, 64); + } + + /* Add salt for numbers not divisible by 3. */ + if (cnt % 3 != 0) { + EVP_DigestUpdate(ctx, (const unsigned char *)s_bytes, salt_len); + } + + /* Add key for numbers not divisible by 7. */ + if (cnt % 7 != 0) { + EVP_DigestUpdate(ctx, (const unsigned char *)p_bytes, key_len); + } + + /* Add key or last result. */ + if ((cnt & 1) != 0) { + EVP_DigestUpdate(ctx, alt_result, 64); + } else { + EVP_DigestUpdate(ctx, (const unsigned char *)p_bytes, key_len); + } + + /* Create intermediate result. */ + EVP_DigestFinal_ex(ctx, alt_result, &part); + } + + /* Now we can construct the result string. + * It consists of three parts. */ + if (buflen <= SALT_PREF_SIZE) { + ret = ERANGE; + goto done; + } + + cp = memcpy(buffer, sha512_salt_prefix, SALT_PREF_SIZE); + cp += SALT_PREF_SIZE; + buflen -= SALT_PREF_SIZE; + + if (rounds_custom) { + n = snprintf(cp, buflen, "%s%zu$", + sha512_rounds_prefix, rounds); + if (n < 0 || n >= buflen) { + ret = ERANGE; + goto done; + } + cp += n; + buflen -= n; + } + + if (buflen <= salt_len + 1) { + ret = ERANGE; + goto done; + } + cp = stpncpy(cp, salt, salt_len); + *cp++ = '$'; + buflen -= salt_len + 1; + + /* fuzzyfill the base 64 string */ + p1 = 0; + p2 = 21; + p3 = 42; + for (n = 0; n < 21; n++) { + b64_from_24bit(&cp, &buflen, 4, alt_result[p1], alt_result[p2], alt_result[p3]); + if (buflen == 0) { + ret = ERANGE; + goto done; + } + pt = p1; + p1 = p2 + 1; + p2 = p3 + 1; + p3 = pt + 1; + } + /* 64th and last byte */ + b64_from_24bit(&cp, &buflen, 2, 0, 0, alt_result[p3]); + if (buflen == 0) { + ret = ERANGE; + goto done; + } + + *cp = '\0'; + ret = EOK; + +done: + /* Clear the buffer for the intermediate result so that people attaching + * to processes or reading core dumps cannot get any information. We do it + * in this way to clear correct_words[] inside the SHA512 implementation + * as well. */ + EVP_MD_CTX_free(ctx); + EVP_MD_CTX_free(alt_ctx); + if (p_bytes != NULL) { + sss_erase_mem_securely(p_bytes, key_len); + } + if (s_bytes != NULL) { + sss_erase_mem_securely(s_bytes, salt_len); + } + sss_erase_mem_securely(temp_result, sizeof(temp_result)); + sss_erase_mem_securely(alt_result, sizeof(alt_result)); + + return ret; +} + +int s3crypt_sha512(TALLOC_CTX *memctx, + const char *key, const char *salt, char **_hash) +{ + char *hash; + int hlen = (sizeof (sha512_salt_prefix) - 1 + + sizeof (sha512_rounds_prefix) + 9 + 1 + + strlen (salt) + 1 + 86 + 1); + int ret; + + hash = talloc_size(memctx, hlen); + if (!hash) return ENOMEM; + + ret = sha512_crypt_r(key, salt, hash, hlen); + if (ret) return ret; + + *_hash = hash; + return ret; +} + +#define SALT_RAND_LEN 12 + +int s3crypt_gen_salt(TALLOC_CTX *memctx, char **_salt) +{ + uint8_t rb[SALT_RAND_LEN]; + char *salt, *cp; + size_t slen; + int ret; + + ret = sss_generate_csprng_buffer(rb, SALT_RAND_LEN); + if (ret != EOK) { + return ret; + } + + salt = talloc_size(memctx, SALT_LEN_MAX + 1); + if (!salt) { + return ENOMEM; + } + + slen = SALT_LEN_MAX; + cp = salt; + b64_from_24bit(&cp, &slen, 4, rb[0], rb[1], rb[2]); + b64_from_24bit(&cp, &slen, 4, rb[3], rb[4], rb[5]); + b64_from_24bit(&cp, &slen, 4, rb[6], rb[7], rb[8]); + b64_from_24bit(&cp, &slen, 4, rb[9], rb[10], rb[11]); + *cp = '\0'; + + *_salt = salt; + + return EOK; +} diff --git a/src/util/crypto/libcrypto/sss_openssl.h b/src/util/crypto/libcrypto/sss_openssl.h new file mode 100644 index 0000000..a2e2d85 --- /dev/null +++ b/src/util/crypto/libcrypto/sss_openssl.h @@ -0,0 +1,39 @@ +/* + Authors: + Lukas Slebodnik <lslebodn@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SSS_LIBCRYTPO_SSS_OPENSSL_H_ +#define _SSS_LIBCRYTPO_SSS_OPENSSL_H_ + +#include <openssl/evp.h> + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + +/* EVP_MD_CTX_create and EVP_MD_CTX_destroy are deprecated macros + * in openssl-1.1 but openssl-1.0 does not know anything about + * newly added functions EVP_MD_CTX_new, EVP_MD_CTX_free in 1.1 + */ + +# define EVP_MD_CTX_new() EVP_MD_CTX_create() +# define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy((ctx)) + +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + + +#endif /* _SSS_LIBCRYTPO_SSS_OPENSSL_H_ */ diff --git a/src/util/crypto/sss_crypto.h b/src/util/crypto/sss_crypto.h new file mode 100644 index 0000000..db6b154 --- /dev/null +++ b/src/util/crypto/sss_crypto.h @@ -0,0 +1,63 @@ +/* + Copyright (C) 2009-2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SSS_CRYPTO_H_ +#define _SSS_CRYPTO_H_ + +#include <talloc.h> +#include <stdint.h> + +/* Guaranteed either to fill given buffer with crypto strong random data + * or to fail with error code (for example in the case of the lack of + * proper entropy) + * Suitable to be used in security relevant context. + */ +int sss_generate_csprng_buffer(uint8_t *buf, size_t size); + +int s3crypt_sha512(TALLOC_CTX *mmectx, + const char *key, const char *salt, char **_hash); +int s3crypt_gen_salt(TALLOC_CTX *memctx, char **_salt); + +/* Methods of obfuscation. */ +enum obfmethod { + AES_256, + NUM_OBFMETHODS +}; + +char *sss_base64_encode(TALLOC_CTX *mem_ctx, + const unsigned char *in, + size_t insize); + +unsigned char *sss_base64_decode(TALLOC_CTX *mem_ctx, + const char *in, + size_t *outsize); + +#define SSS_SHA1_LENGTH 20 + +int sss_hmac_sha1(const unsigned char *key, + size_t key_len, + const unsigned char *in, + size_t in_len, + unsigned char *out); + +int sss_password_encrypt(TALLOC_CTX *mem_ctx, const char *password, int plen, + enum obfmethod meth, char **obfpwd); + +int sss_password_decrypt(TALLOC_CTX *mem_ctx, char *b64encoded, + char **password); + +#endif /* _SSS_CRYPTO_H_ */ diff --git a/src/util/debug.c b/src/util/debug.c new file mode 100644 index 0000000..7dcf64f --- /dev/null +++ b/src/util/debug.c @@ -0,0 +1,519 @@ +/* + Authors: + Simo Sorce <ssorce@redhat.com> + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> + +#ifdef WITH_JOURNALD +#include <systemd/sd-journal.h> +#endif + +#include "util/util.h" + +/* from debug_backtrace.h */ +void sss_debug_backtrace_init(void); +void sss_debug_backtrace_vprintf(int level, const char *format, va_list ap); +void sss_debug_backtrace_printf(int level, const char *format, ...); +void sss_debug_backtrace_endmsg(const char *file, long line, int level); + +const char *debug_prg_name = "sssd"; + +int debug_level = SSSDBG_UNRESOLVED; +int debug_timestamps = SSSDBG_TIMESTAMP_UNRESOLVED; +int debug_microseconds = SSSDBG_MICROSECONDS_UNRESOLVED; +enum sss_logger_t sss_logger = STDERR_LOGGER; +const char *debug_log_file = "sssd"; +FILE *_sss_debug_file; +uint64_t debug_chain_id; +/* Default value says 'BUG' because this is just a precautionary measure and + * it is expected value is always set explicitly by every SSSD process. 'BUG' + * should make any potential oversight more prominent in the logs. */ +const char *debug_chain_id_fmt = "BUG%lu %s"; + +const char *sss_logger_str[] = { + [STDERR_LOGGER] = "stderr", + [FILES_LOGGER] = "files", +#ifdef WITH_JOURNALD + [JOURNALD_LOGGER] = "journald", +#endif + NULL, +}; + + +/* this function isn't static to let access from debug-tests.c */ +void sss_set_logger(const char *logger) +{ + if (logger == NULL) { + sss_logger = STDERR_LOGGER; +#ifdef WITH_JOURNALD + sss_logger = JOURNALD_LOGGER; +#endif + } else if (strcmp(logger, "stderr") == 0) { + sss_logger = STDERR_LOGGER; + } else if (strcmp(logger, "files") == 0) { + sss_logger = FILES_LOGGER; +#ifdef WITH_JOURNALD + } else if (strcmp(logger, "journald") == 0) { + sss_logger = JOURNALD_LOGGER; +#endif + } else { + /* unexpected value */ + fprintf(stderr, "Unexpected logger: %s\nExpected: ", logger); + fprintf(stderr, "%s", sss_logger_str[STDERR_LOGGER]); + fprintf(stderr, "|%s", sss_logger_str[FILES_LOGGER]); +#ifdef WITH_JOURNALD + fprintf(stderr, "|%s", sss_logger_str[JOURNALD_LOGGER]); +#endif + fprintf(stderr, "\n"); + sss_logger = STDERR_LOGGER; + } +} + +static int _sss_open_debug_file(void) +{ + return open_debug_file_ex(NULL, NULL, true); +} + +void _sss_debug_init(int dbg_lvl, const char *logger) +{ + if (dbg_lvl != SSSDBG_INVALID) { + debug_level = debug_convert_old_level(dbg_lvl); + } else { + debug_level = SSSDBG_UNRESOLVED; + } + + sss_set_logger(logger); + + /* if 'FILES_LOGGER' is requested then open log file, if it wasn't + * initialized before via set_debug_file_from_fd(). + */ + if ((sss_logger == FILES_LOGGER) && (_sss_debug_file == NULL)) { + if (_sss_open_debug_file() != 0) { + ERROR("Error opening log file, falling back to stderr\n"); + sss_logger = STDERR_LOGGER; + } + } + + sss_debug_backtrace_init(); +} + +errno_t set_debug_file_from_fd(const int fd) +{ + FILE *dummy; + errno_t ret; + + errno = 0; + dummy = fdopen(fd, "a"); + if (dummy == NULL) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fdopen failed [%d][%s].\n", ret, strerror(ret)); + sss_log(SSS_LOG_ERR, + "Could not open debug file descriptor [%d]. " + "Debug messages will not be written to the file " + "for this child process [%s][%s]\n", + fd, debug_prg_name, strerror(ret)); + return ret; + } + + _sss_debug_file = dummy; + + return EOK; +} + +int get_fd_from_debug_file(void) +{ + if (_sss_debug_file == NULL) { + return STDERR_FILENO; + } + + return fileno(_sss_debug_file); +} + +int debug_convert_old_level(int old_level) +{ + if ((old_level != 0) && !(old_level & 0x000F)) + return old_level; + + int new_level = SSSDBG_FATAL_FAILURE; + + if (old_level <= 0) + return new_level; + + if (old_level >= 1) + new_level |= SSSDBG_CRIT_FAILURE; + + if (old_level >= 2) + new_level |= SSSDBG_OP_FAILURE; + + if (old_level >= 3) + new_level |= SSSDBG_MINOR_FAILURE; + + if (old_level >= 4) + new_level |= SSSDBG_CONF_SETTINGS; + + if (old_level >= 5) + new_level |= SSSDBG_FUNC_DATA; + + if (old_level >= 6) + new_level |= SSSDBG_TRACE_FUNC; + + if (old_level >= 7) + new_level |= SSSDBG_TRACE_LIBS; + + if (old_level >= 8) + new_level |= SSSDBG_TRACE_INTERNAL; + + if (old_level >= 9) + new_level |= SSSDBG_TRACE_ALL | SSSDBG_BE_FO | SSSDBG_PERF_STAT; + + if (old_level >= 10) + new_level |= SSSDBG_TRACE_LDB; + + return new_level; +} + + +#ifdef WITH_JOURNALD +static errno_t journal_send(const char *file, + long line, + const char *function, + int level, + const char *format, + va_list ap) +{ + errno_t ret; + int res; + char *message = NULL; + char *code_file = NULL; + char *code_line = NULL; + const char *domain; + + /* First, evaluate the message to be sent */ + ret = vasprintf(&message, format, ap); + if (ret == -1) { + /* ENOMEM, just return */ + return ENOMEM; + } + + res = asprintf(&code_file, "CODE_FILE=%s", file); + if (res == -1) { + ret = ENOMEM; + goto journal_done; + } + + res = asprintf(&code_line, "CODE_LINE=%ld", line); + if (res == -1) { + ret = ENOMEM; + goto journal_done; + } + + /* If this log message was sent from a provider, + * track the domain. + */ + domain = getenv(SSS_DOM_ENV); + if (domain == NULL) { + domain = ""; + } + + /* Send the log message to journald, specifying the + * source code location and other tracking data. + */ + res = sd_journal_send_with_location( + code_file, code_line, function, + "MESSAGE=%s", message, + "PRIORITY=%i", LOG_DEBUG, + "SSSD_DOMAIN=%s", domain, + "SSSD_PRG_NAME=sssd[%s]", debug_prg_name, + "SSSD_DEBUG_LEVEL=%x", level, + NULL); + ret = -res; + +journal_done: + free(code_line); + free(code_file); + free(message); + return ret; +} +#endif /* WiTH_JOURNALD */ + +void sss_vdebug_fn(const char *file, + long line, + const char *function, + int level, + int flags, + const char *format, + va_list ap) +{ + static time_t last_time; + static char last_time_str[128]; + struct timeval tv; + struct tm tm; + time_t t; + +#ifdef WITH_JOURNALD + char chain_id_fmt_fixed[256]; + char *chain_id_fmt_dyn = NULL; + char *result_fmt; + errno_t ret; + va_list ap_fallback; + + if (sss_logger == JOURNALD_LOGGER) { + if (!DEBUG_IS_SET(level)) return; /* no debug backtrace with journald atm */ + /* If we are not outputting logs to files, we should be sending them + * to journald. + * NOTE: on modern systems, this is where stdout/stderr will end up + * from system services anyway. The only difference here is that we + * can also provide extra structuring data to make it more easily + * searchable. + */ + va_copy(ap_fallback, ap); + if (debug_chain_id > 0 && debug_chain_id_fmt != NULL) { + result_fmt = chain_id_fmt_fixed; + ret = snprintf(chain_id_fmt_fixed, sizeof(chain_id_fmt_fixed), + debug_chain_id_fmt, debug_chain_id, format); + if (ret < 0) { + va_end(ap_fallback); + return; + } else if (ret >= sizeof(chain_id_fmt_fixed)) { + ret = asprintf(&chain_id_fmt_dyn, debug_chain_id_fmt, + debug_chain_id, format); + if (ret < 0) { + va_end(ap_fallback); + return; + } + result_fmt = chain_id_fmt_dyn; + } + + ret = journal_send(file, line, function, level, result_fmt, ap); + free(chain_id_fmt_dyn); + } else { + ret = journal_send(file, line, function, level, format, ap); + } + if (ret != EOK) { + /* Emergency fallback, send to STDERR */ + vfprintf(stderr, format, ap_fallback); + fflush(stderr); + } + va_end(ap_fallback); + return; + } +#endif + + if (debug_timestamps == SSSDBG_TIMESTAMP_ENABLED) { + if (debug_microseconds == SSSDBG_MICROSECONDS_ENABLED) { + gettimeofday(&tv, NULL); + t = tv.tv_sec; + } else { + t = time(NULL); + } + if (t != last_time) { + last_time = t; + localtime_r(&t, &tm); + snprintf(last_time_str, sizeof(last_time_str), + "(%d-%02d-%02d %2d:%02d:%02d", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + } + if (debug_microseconds == SSSDBG_MICROSECONDS_ENABLED) { + sss_debug_backtrace_printf(level, "%s:%.6ld): ", + last_time_str, tv.tv_usec); + } else { + sss_debug_backtrace_printf(level, "%s): ", last_time_str); + } + } + + sss_debug_backtrace_printf(level, "[%s] [%s] (%#.4x): ", + debug_prg_name, function, level); + + if (debug_chain_id > 0 && debug_chain_id_fmt != NULL) { + sss_debug_backtrace_printf(level, debug_chain_id_fmt, debug_chain_id, ""); + } + + sss_debug_backtrace_vprintf(level, format, ap); + + if (flags & APPEND_LINE_FEED) { + sss_debug_backtrace_printf(level, "\n"); + } + sss_debug_backtrace_endmsg(file, line, level); +} + +void sss_debug_fn(const char *file, + long line, + const char *function, + int level, + const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + sss_vdebug_fn(file, line, function, level, 0, format, ap); + va_end(ap); +} + +/* In cases SSSD used to run as the root user, but runs as the SSSD user now, + * we need to chown the log files + */ +int chown_debug_file(const char *filename, + uid_t uid, gid_t gid) +{ + char *logpath; + const char *log_file; + errno_t ret; + + if (filename == NULL) { + log_file = debug_log_file; + } else { + log_file = filename; + } + + ret = asprintf(&logpath, "%s/%s.log", LOG_PATH, log_file); + if (ret == -1) { + return ENOMEM; + } + + ret = chown(logpath, uid, gid); + free(logpath); + if (ret != 0) { + ret = errno; + if (ret == ENOENT) { + /* Log does not exist. We might log to journald + * or starting for first time. + * It's not a failure. */ + return EOK; + } + + DEBUG(SSSDBG_FATAL_FAILURE, "chown failed for [%s]: [%d]\n", + log_file, ret); + return ret; + } + + return EOK; +} + +int open_debug_file_ex(const char *filename, FILE **filep, bool want_cloexec) +{ + FILE *f = NULL; + char *logpath; + const char *log_file; + mode_t old_umask; + int ret; + int debug_fd; + int flags; + + if (filename == NULL) { + log_file = debug_log_file; + } else { + log_file = filename; + } + + ret = asprintf(&logpath, "%s/%s.log", LOG_PATH, log_file); + if (ret == -1) { + return ENOMEM; + } + + if (_sss_debug_file && !filep) fclose(_sss_debug_file); + + old_umask = umask(SSS_DFL_UMASK); + errno = 0; + f = fopen(logpath, "a"); + if (f == NULL) { + sss_log(SSS_LOG_EMERG, "Could not open file [%s]. Error: [%d][%s]\n", + logpath, errno, strerror(errno)); + free(logpath); + return EIO; + } + umask(old_umask); + + debug_fd = fileno(f); + if (debug_fd == -1) { + fclose(f); + free(logpath); + return EIO; + } + + if(want_cloexec) { + flags = fcntl(debug_fd, F_GETFD, 0); + (void) fcntl(debug_fd, F_SETFD, flags | FD_CLOEXEC); + } + + if (filep == NULL) { + _sss_debug_file = f; + } else { + *filep = f; + } + free(logpath); + return EOK; +} + +int rotate_debug_files(void) +{ + int ret; + errno_t error; + + if (sss_logger != FILES_LOGGER) return EOK; + + if (_sss_debug_file != NULL) { + do { + error = 0; + ret = fclose(_sss_debug_file); + if (ret != 0) { + error = errno; + } + + /* Check for EINTR, which means we should retry + * because the system call was interrupted by a + * signal + */ + } while (error == EINTR); + + if (error != 0) { + /* Even if we were unable to close the debug log, we need to make + * sure that we open up a new one. Log rotation will remove the + * current file, so all debug messages will be disappearing. + * + * We should write an error to the syslog warning of the resource + * leak and then proceed with opening the new file. + */ + sss_log(SSS_LOG_ALERT, "Could not close debug file [%s]. [%d][%s]\n", + debug_log_file, error, strerror(error)); + sss_log(SSS_LOG_ALERT, "Attempting to open new file anyway. " + "Be aware that this is a resource leak\n"); + } + } + + _sss_debug_file = NULL; + + return _sss_open_debug_file(); +} + +void _sss_talloc_log_fn(const char *message) +{ + DEBUG(SSSDBG_FATAL_FAILURE, "%s\n", message); +} diff --git a/src/util/debug.h b/src/util/debug.h new file mode 100644 index 0000000..54f9d11 --- /dev/null +++ b/src/util/debug.h @@ -0,0 +1,204 @@ +/* + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __SSSD_DEBUG_H__ +#define __SSSD_DEBUG_H__ + +#include "config.h" + +#include <stdio.h> +#include <stdbool.h> +#include <sys/types.h> + +#include "util/util_errors.h" + +#define SSSDBG_TIMESTAMP_UNRESOLVED -1 +#define SSSDBG_TIMESTAMP_DISABLED 0 +#define SSSDBG_TIMESTAMP_ENABLED 1 +#define SSSDBG_TIMESTAMP_DEFAULT SSSDBG_TIMESTAMP_ENABLED + +#define SSSDBG_MICROSECONDS_UNRESOLVED -1 +#define SSSDBG_MICROSECONDS_DISABLED 0 +#define SSSDBG_MICROSECONDS_ENABLED 1 +#define SSSDBG_MICROSECONDS_DEFAULT SSSDBG_MICROSECONDS_DISABLED + + +enum sss_logger_t { + STDERR_LOGGER = 0, + FILES_LOGGER, +#ifdef WITH_JOURNALD + JOURNALD_LOGGER, +#endif +}; + +extern const char *sss_logger_str[]; /* mapping: sss_logger_t -> string */ +extern const char *debug_prg_name; +extern int debug_level; +extern int debug_timestamps; +extern int debug_microseconds; +extern enum sss_logger_t sss_logger; +extern const char *debug_log_file; /* only file name, excluding path */ + + +/* converts log level from "old" notation and opens log file if needed */ +#define DEBUG_INIT(dbg_lvl, logger) do { \ + _sss_debug_init(dbg_lvl, logger); \ + talloc_set_log_fn(_sss_talloc_log_fn); \ +} while (0) + +/* CLI tools shall debug to stderr */ +#define DEBUG_CLI_INIT(dbg_lvl) do { \ + DEBUG_INIT(dbg_lvl, sss_logger_str[STDERR_LOGGER]); \ +} while (0) + +void sss_debug_backtrace_enable(bool enable); + +/* debug_convert_old_level() converts "old" style decimal notation + * to bitmask composed of SSSDBG_* + * Used explicitly, for example, while processing user input + * in sssctl_logs. + */ +int debug_convert_old_level(int old_level); + +/* set_debug_file_from_fd() is used by *_child processes as those + * don't manage logs files on their own but instead receive fd arg + * on command line. + */ +errno_t set_debug_file_from_fd(const int fd); + +/* get_fd_from_debug_file() is used to redirect STDERR_FILENO + * to currently open log file fd while running external helpers + * (e.g. nsupdate, ipa_get_keytab) + */ +int get_fd_from_debug_file(void); + +/* chown_debug_file() uses 'debug_log_file' in case 'filename == NULL' */ +int chown_debug_file(const char *filename, uid_t uid, gid_t gid); + +/* open_debug_file_ex() is used to open log file for *_child processes */ +int open_debug_file_ex(const char *filename, FILE **filep, bool want_cloexec); + +int rotate_debug_files(void); + +#define SSS_DOM_ENV "_SSS_DOM" + +/* 0x0800 isn't used for historical reasons */ +#define SSSDBG_FATAL_FAILURE 0x0010 /* level 0 */ +#define SSSDBG_CRIT_FAILURE 0x0020 /* level 1 */ +#define SSSDBG_OP_FAILURE 0x0040 /* level 2 */ +#define SSSDBG_MINOR_FAILURE 0x0080 /* level 3 */ +#define SSSDBG_CONF_SETTINGS 0x0100 /* level 4 */ +#define SSSDBG_FUNC_DATA 0x0200 /* level 5 */ +#define SSSDBG_TRACE_FUNC 0x0400 /* level 6 */ +#define SSSDBG_TRACE_LIBS 0x1000 /* level 7 */ +#define SSSDBG_TRACE_INTERNAL 0x2000 /* level 8 */ +#define SSSDBG_TRACE_ALL 0x4000 /* level 9 */ +#define SSSDBG_BE_FO 0x8000 /* level 9 */ +#define SSSDBG_TRACE_LDB 0x10000 /* level 10 */ +#define SSSDBG_PERF_STAT 0x20000 /* level 9 */ + +/* IMPORTANT_INFO will be logged if any of bits >= OP_FAILURE are on: */ +#define SSSDBG_IMPORTANT_INFO (SSSDBG_OP_FAILURE|SSSDBG_MINOR_FAILURE|\ + SSSDBG_CONF_SETTINGS|SSSDBG_FUNC_DATA|\ + SSSDBG_TRACE_FUNC|SSSDBG_TRACE_LIBS|\ + SSSDBG_TRACE_INTERNAL|SSSDBG_TRACE_ALL|\ + SSSDBG_BE_FO|SSSDBG_TRACE_LDB|SSSDBG_PERF_STAT) + +#define SSSDBG_INVALID -1 +#define SSSDBG_UNRESOLVED 0 +#define SSSDBG_DEFAULT (SSSDBG_FATAL_FAILURE|SSSDBG_CRIT_FAILURE|SSSDBG_OP_FAILURE) +#define SSSDBG_TOOLS_DEFAULT (SSSDBG_FATAL_FAILURE) + + +/** \def DEBUG(level, format, ...) + \brief macro to generate debug messages + + \param level the debug level, please use one of the SSSDBG_* macros + \param format the debug message format string, should result in a + newline-terminated message + \param ... the debug message format arguments +*/ +#define DEBUG(level, format, ...) do { \ + sss_debug_fn(__FILE__, __LINE__, __FUNCTION__, \ + level, \ + format, ##__VA_ARGS__); \ +} while (0) + + +/* SSSD_*_OPTS are used as 'poptOption' entries */ +#define SSSD_LOGGER_OPTS \ + {"logger", '\0', POPT_ARG_STRING, &opt_logger, 0, \ + _("Set logger"), "stderr|files|journald"}, + +#define SSSD_DEBUG_OPTS \ + {"debug-level", 'd', POPT_ARG_INT, &debug_level, 0, \ + _("Debug level"), NULL}, \ + {"debug-timestamps", 0, POPT_ARG_INT, &debug_timestamps, 0, \ + _("Add debug timestamps"), NULL}, \ + {"debug-microseconds", 0, POPT_ARG_INT, &debug_microseconds, 0, \ + _("Show timestamps with microseconds"), NULL}, + + +#define PRINT(fmt, ...) fprintf(stdout, gettext(fmt), ##__VA_ARGS__) +#define ERROR(fmt, ...) fprintf(stderr, gettext(fmt), ##__VA_ARGS__) + + +#ifdef HAVE_FUNCTION_ATTRIBUTE_FORMAT +#define SSS_ATTRIBUTE_PRINTF(a1, a2) __attribute__((format (printf, a1, a2))) +#else +#define SSS_ATTRIBUTE_PRINTF(a1, a2) +#endif + +/* sss_*debug_fn() are rarely needed to be used explicitly + * (common example: provision of a logger function to 3rd party lib) + * For normal logs use DEBUG() instead. + */ +void sss_vdebug_fn(const char *file, + long line, + const char *function, + int level, + int flags, + const char *format, + va_list ap); +void sss_debug_fn(const char *file, + long line, + const char *function, + int level, + const char *format, ...) SSS_ATTRIBUTE_PRINTF(5, 6); + +#define APPEND_LINE_FEED 0x1 /* can be used as a sss_vdebug_fn() flag */ + +/* Checks whether level is set in generic debug_level. + Rarely needed to be used explicitly as everything + should go to backtrace buffer anyway (regardless debug_level) + Deciding if "--verbose" should be passed to `adcli` child process + is one of usage examples. + */ +#define DEBUG_IS_SET(level) (debug_level & (level) || \ + (debug_level == SSSDBG_UNRESOLVED && \ + (level & (SSSDBG_FATAL_FAILURE | \ + SSSDBG_CRIT_FAILURE)))) + + +/* not to be used explictly, use 'DEBUG_INIT' instead */ +void _sss_debug_init(int dbg_lvl, const char *logger); +void _sss_talloc_log_fn(const char *msg); + +#endif /* __SSSD_DEBUG_H__ */ diff --git a/src/util/debug_backtrace.c b/src/util/debug_backtrace.c new file mode 100644 index 0000000..e376f81 --- /dev/null +++ b/src/util/debug_backtrace.c @@ -0,0 +1,331 @@ +/* + Copyright (C) 2021 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdbool.h> +#include <stdlib.h> +#include <libintl.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include "util/debug.h" + +extern FILE *_sss_debug_file; + + +static const unsigned SSS_DEBUG_BACKTRACE_DEFAULT_SIZE = 100*1024; /* bytes */ +static const unsigned SSS_DEBUG_BACKTRACE_LEVEL = SSSDBG_BE_FO; + +/* Size of locations history to keep to avoid duplicating backtraces */ +#define SSS_DEBUG_BACKTRACE_LOCATIONS 5 + + +/* --> + * ring buffer = [*******t...\n............e000] + * where: + * "t" - 'tail', "e" - 'end' + * "......" - "old" part of buffer + * "******" - "new" part of buffer + * "000" - unoccupied space + */ +static struct { + bool enabled; + bool initialized; + int size; + char *buffer; /* buffer start */ + char *end; /* end data border */ + char *tail; /* tail of "current" message */ + + /* locations where last backtraces happened */ + struct { + const char *file; + long line; + } locations[SSS_DEBUG_BACKTRACE_LOCATIONS]; + unsigned last_location_idx; +} _bt; + + +static inline bool _all_levels_enabled(void); +static inline bool _backtrace_is_enabled(int level); +static inline bool _is_trigger_level(int level); +static void _store_location(const char *file, long line); +static bool _is_recent_location(const char *file, long line); +static void _backtrace_vprintf(const char *format, va_list ap); +static void _backtrace_printf(const char *format, ...); +static void _backtrace_dump(void); +static inline void _debug_vprintf(const char *format, va_list ap); +static inline void _debug_fwrite(const char *ptr, const char *end); +static inline void _debug_fflush(void); + + +void sss_debug_backtrace_init(void) +{ + _bt.size = SSS_DEBUG_BACKTRACE_DEFAULT_SIZE; + _bt.buffer = (char *)malloc(_bt.size); + if (!_bt.buffer) { + ERROR("Failed to allocate debug backtrace buffer, feature is off\n"); + return; + } + + _bt.end = _bt.buffer; + _bt.tail = _bt.buffer; + + _bt.enabled = true; + _bt.initialized = true; + + /* locations[] & last_location_idx are zero-initialized */ + + _backtrace_printf(" * "); +} + + +void sss_debug_backtrace_enable(bool enable) +{ + _bt.enabled = enable; +} + + +void sss_debug_backtrace_vprintf(int level, const char *format, va_list ap) +{ + va_list ap_copy; + + /* Potential optimization: only print to file here if backtrace is disabled, + * otherwise always print message to backtrace only and then copy message + * from backtrace to file in sss_debug_backtrace_endmsg(). + * This saves va_copy and another round of format parsing inside printf but + * results in a little bit less readable output. + */ + if (DEBUG_IS_SET(level)) { + va_copy(ap_copy, ap); + _debug_vprintf(format, ap_copy); + va_end(ap_copy); + } + + if (_backtrace_is_enabled(level)) { + _backtrace_vprintf(format, ap); + } +} + + +void sss_debug_backtrace_printf(int level, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + sss_debug_backtrace_vprintf(level, format, ap); + va_end(ap); +} + + +void sss_debug_backtrace_endmsg(const char *file, long line, int level) +{ + if (DEBUG_IS_SET(level)) { + _debug_fflush(); + } + + if (_backtrace_is_enabled(level)) { + if (_is_trigger_level(level)) { + if (!_is_recent_location(file, line)) { + _backtrace_dump(); + _store_location(file, line); + } else { + fprintf(_sss_debug_file ? _sss_debug_file : stderr, + " * ... skipping repetitive backtrace ...\n"); + /* and reset */ + _bt.end = _bt.buffer; + _bt.tail = _bt.buffer; + } + } + _backtrace_printf(" * "); + } +} + + + + +/* ********** Helpers ********** */ + + +static inline void _debug_vprintf(const char *format, va_list ap) +{ + vfprintf(_sss_debug_file ? _sss_debug_file : stderr, format, ap); +} + + +static inline void _debug_fwrite(const char *begin, const char *end) +{ + if (end <= begin) { + return; + } + size_t size = (end - begin); + fwrite_unlocked(begin, size, 1, _sss_debug_file ? _sss_debug_file : stderr); +} + + +static inline void _debug_fflush(void) +{ + fflush(_sss_debug_file ? _sss_debug_file : stderr); +} + + + /* does 'level' trigger backtrace dump? */ +static inline bool _is_trigger_level(int level) +{ + return ((level <= SSSDBG_OP_FAILURE) && + (level <= debug_level)); +} + + +/* checks if global 'debug_level' has all levels up to 9 enabled */ +static inline bool _all_levels_enabled(void) +{ + static const unsigned all_levels = + SSSDBG_FATAL_FAILURE|SSSDBG_CRIT_FAILURE|SSSDBG_OP_FAILURE| + SSSDBG_MINOR_FAILURE|SSSDBG_CONF_SETTINGS|SSSDBG_FUNC_DATA| + SSSDBG_TRACE_FUNC|SSSDBG_TRACE_LIBS|SSSDBG_TRACE_INTERNAL| + SSSDBG_TRACE_ALL|SSSDBG_BE_FO; + + return ((debug_level & all_levels) == all_levels); +} + + +/* should message of this 'level' go to backtrace? */ +static inline bool _backtrace_is_enabled(int level) +{ + /* Store message in backtrace buffer if: */ + return (_bt.initialized && /* backtrace is initialized */ + _bt.enabled && /* backtrace is enabled */ + sss_logger != STDERR_LOGGER && + !_all_levels_enabled() && /* generic log doesn't cover everything */ + level <= SSS_DEBUG_BACKTRACE_LEVEL); /* skip SSSDBG_TRACE_LDB */ +} + + +static void _store_location(const char *file, long line) +{ + _bt.last_location_idx = (_bt.last_location_idx + 1) % SSS_DEBUG_BACKTRACE_LOCATIONS; + /* __FILE__ is a character string literal with static storage duration. */ + _bt.locations[_bt.last_location_idx].file = file; + _bt.locations[_bt.last_location_idx].line = line; +} + + +static bool _is_recent_location(const char *file, long line) +{ + for (unsigned idx = 0; idx < SSS_DEBUG_BACKTRACE_LOCATIONS; ++idx) { + if ((line == _bt.locations[idx].line) && + (_bt.locations[idx].file != NULL) && + (strcmp(file, _bt.locations[idx].file) == 0)) { + return true; + } + } + return false; +} + + +/* prints to buffer */ +static void _backtrace_vprintf(const char *format, va_list ap) +{ + int buff_tail_size = _bt.size - (_bt.tail - _bt.buffer); + int written; + + /* make sure there is at least 1kb available to avoid truncation; + * putting a sane limit on the size of single message (1kb in a worst case) + * makes logic simpler and avoids performance hit + */ + if (buff_tail_size < 1024) { + /* let's wrap */ + _bt.end = _bt.tail; + _bt.tail = _bt.buffer; + buff_tail_size = _bt.size; + } + + written = vsnprintf(_bt.tail, buff_tail_size, format, ap); + if (written >= buff_tail_size) { + /* message is > 1kb, just discard */ + return; + } + + _bt.tail += written; + if (_bt.tail > _bt.end) { + _bt.end = _bt.tail; + } +} + + +static void _backtrace_printf(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + _backtrace_vprintf(format, ap); + va_end(ap); +} + + +static bool _bt_empty(const char *begin, const char *end) +{ + int counter = 0; + + while (begin < end) { + if (*begin == '\n') { + counter++; + if (counter == 2) { + /* there is least one line in addition to trigger msg */ + return false; + } + } + begin++; + } + + return true; +} + + +static void _backtrace_dump(void) +{ + const char *start = NULL; + static const char *start_marker = + "********************** PREVIOUS MESSAGE WAS TRIGGERED BY THE FOLLOWING BACKTRACE:\n"; + static const char *end_marker = + "********************** BACKTRACE DUMP ENDS HERE *********************************\n\n"; + + if (_bt.end > _bt.tail) { + /* there is something in the "old" part, but don't start mid message */ + start = _bt.tail + 1; + while ((start < _bt.end) && (*start != '\n')) start++; + if (start >= _bt.end) start = NULL; + } + + if (!start) { + /* do we have anything to dump at all? */ + if (_bt_empty(_bt.buffer, _bt.tail)) { + return; + } + } + + fprintf(_sss_debug_file ? _sss_debug_file : stderr, "%s", start_marker); + + if (start) { + _debug_fwrite(start + 1, _bt.end); /* dump "old" part of buffer */ + } + _debug_fwrite(_bt.buffer, _bt.tail); /* dump "new" part of buffer */ + + fprintf(_sss_debug_file ? _sss_debug_file : stderr, "%s", end_marker); + _debug_fflush(); + + _bt.end = _bt.buffer; + _bt.tail = _bt.buffer; +} diff --git a/src/util/dlinklist.h b/src/util/dlinklist.h new file mode 100644 index 0000000..017c604 --- /dev/null +++ b/src/util/dlinklist.h @@ -0,0 +1,155 @@ +/* + Unix SMB/CIFS implementation. + some simple double linked list macros + Copyright (C) Andrew Tridgell 1998 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* To use these macros you must have a structure containing a next and + prev pointer */ + +#ifndef _DLINKLIST_H +#define _DLINKLIST_H + + +/* hook into the front of the list */ +#define DLIST_ADD(list, p) \ +do { \ + if (!(list)) { \ + (list) = (p); \ + (p)->next = (p)->prev = NULL; \ + } else { \ + (list)->prev = (p); \ + (p)->next = (list); \ + (p)->prev = NULL; \ + (list) = (p); \ + } \ +} while (0) + +/* remove an element from a list - element doesn't have to be in list. */ +#define DLIST_REMOVE(list, p) \ +do { \ + if ((p) == (list)) { \ + (list) = (p)->next; \ + if (list) { \ + (list)->prev = NULL; \ + } \ + } else { \ + if ((p)->prev) { \ + (p)->prev->next = (p)->next; \ + } \ + if ((p)->next) { \ + (p)->next->prev = (p)->prev; \ + } \ + } \ + if ((p) != (list)) { \ + (p)->next = (p)->prev = NULL; \ + } \ +} while (0) + +/* promote an element to the top of the list */ +#define DLIST_PROMOTE(list, p) \ +do { \ + DLIST_REMOVE(list, p); \ + DLIST_ADD(list, p); \ +} while (0) + +/* hook into the end of the list - needs a tmp pointer */ +#define DLIST_ADD_END(list, p, type) \ +do { \ + if (!(list)) { \ + (list) = (p); \ + (p)->next = (p)->prev = NULL; \ + } else { \ + type tmp; \ + for (tmp = (list); tmp->next; tmp = tmp->next) { \ + /* no op */ \ + } \ + tmp->next = (p); \ + (p)->next = NULL; \ + (p)->prev = tmp; \ + } \ +} while (0) + +/* insert 'p' after the given element 'el' in a list. If el is NULL then + this is the same as a DLIST_ADD() */ +#define DLIST_ADD_AFTER(list, p, el) \ +do { \ + if (!(list) || !(el)) { \ + DLIST_ADD(list, p); \ + } else { \ + p->prev = el; \ + p->next = el->next; \ + el->next = p; \ + if (p->next) { \ + p->next->prev = p; \ + } \ + } \ +} while (0) + +/* demote an element to the end of the list, needs a tmp pointer */ +#define DLIST_DEMOTE(list, p, type) \ +do { \ + DLIST_REMOVE(list, p); \ + DLIST_ADD_END(list, p, type); \ +} while (0) + +/* concatenate two lists - putting all elements of the 2nd list at the + end of the first list */ +#define DLIST_CONCATENATE(list1, list2, type) \ +do { \ + if (!(list1)) { \ + (list1) = (list2); \ + } else { \ + type tmp; \ + for (tmp = (list1); tmp->next; tmp = tmp->next) { \ + /* no op */ \ + } \ + tmp->next = (list2); \ + if (list2) { \ + (list2)->prev = tmp; \ + } \ + } \ +} while (0) + +/* insert all elements from list2 after the given element 'el' in the + * first list */ +#define DLIST_ADD_LIST_AFTER(list1, el, list2, type) \ +do { \ + if (!(list1) || !(el) || !(list2)) { \ + DLIST_CONCATENATE(list1, list2, type); \ + } else { \ + type tmp; \ + for (tmp = (list2); tmp->next; tmp = tmp->next) { \ + /* no op */ \ + } \ + (list2)->prev = (el); \ + tmp->next = (el)->next; \ + (el)->next = (list2); \ + if (tmp->next != NULL) { \ + tmp->next->prev = tmp; \ + } \ + } \ +} while (0); + +#define DLIST_FOR_EACH(p, list) \ + for ((p) = (list); (p) != NULL; (p) = (p)->next) + +#define DLIST_FOR_EACH_SAFE(p, q, list) \ + for ((p) = (list), (q) = (p) != NULL ? (p)->next : NULL; \ + (p) != NULL; \ + (p) = (q), (q) = (p) != NULL ? (p)->next : NULL) + +#endif /* _DLINKLIST_H */ diff --git a/src/util/domain_info_utils.c b/src/util/domain_info_utils.c new file mode 100644 index 0000000..a6fae06 --- /dev/null +++ b/src/util/domain_info_utils.c @@ -0,0 +1,1044 @@ +/* + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <ctype.h> +#include <utime.h> + +#include "confdb/confdb.h" +#include "db/sysdb.h" +#include "util/util.h" + +struct sss_domain_info *get_domains_head(struct sss_domain_info *domain) +{ + struct sss_domain_info *dom = NULL; + + /* get to the top level domain */ + for (dom = domain; dom->parent != NULL; dom = dom->parent); + + return dom; +} + +struct sss_domain_info *get_next_domain(struct sss_domain_info *domain, + uint32_t gnd_flags) +{ + struct sss_domain_info *dom; + bool descend = gnd_flags & (SSS_GND_DESCEND | SSS_GND_SUBDOMAINS); + bool include_disabled = gnd_flags & SSS_GND_INCLUDE_DISABLED; + bool only_subdomains = gnd_flags & SSS_GND_SUBDOMAINS; + + dom = domain; + while (dom) { + if (descend && dom->subdomains) { + dom = dom->subdomains; + } else if (dom->next && only_subdomains && IS_SUBDOMAIN(dom)) { + dom = dom->next; + } else if (dom->next && !only_subdomains) { + dom = dom->next; + } else if (descend && !only_subdomains && IS_SUBDOMAIN(dom) + && dom->parent->next) { + dom = dom->parent->next; + } else { + dom = NULL; + } + + if (dom) { + if (sss_domain_get_state(dom) == DOM_DISABLED + && !include_disabled) { + continue; + } else { + /* Next domain found. */ + break; + } + } + } + + return dom; +} + +bool subdomain_enumerates(struct sss_domain_info *parent, + const char *sd_name) +{ + if (parent->sd_enumerate == NULL + || parent->sd_enumerate[0] == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Subdomain_enumerate not set\n"); + return false; + } + + if (strcasecmp(parent->sd_enumerate[0], "all") == 0) { + return true; + } else if (strcasecmp(parent->sd_enumerate[0], "none") == 0) { + return false; + } else { + for (int i=0; parent->sd_enumerate[i]; i++) { + if (strcasecmp(parent->sd_enumerate[i], sd_name) == 0) { + return true; + } + } + } + + return false; +} + +struct sss_domain_info *find_domain_by_name_ex(struct sss_domain_info *domain, + const char *name, + bool match_any, + uint32_t gnd_flags) +{ + struct sss_domain_info *dom = domain; + + if (name == NULL) { + return NULL; + } + + if (!(gnd_flags & SSS_GND_INCLUDE_DISABLED)) { + while (dom && sss_domain_get_state(dom) == DOM_DISABLED) { + dom = get_next_domain(dom, gnd_flags); + } + } + + while (dom) { + if (strcasecmp(dom->name, name) == 0 || + ((match_any == true) && (dom->flat_name != NULL) && + (strcasecmp(dom->flat_name, name) == 0))) { + return dom; + } + dom = get_next_domain(dom, gnd_flags); + } + + return NULL; +} + +struct sss_domain_info *find_domain_by_name(struct sss_domain_info *domain, + const char *name, + bool match_any) +{ + return find_domain_by_name_ex(domain, name, match_any, SSS_GND_DESCEND); +} + +struct sss_domain_info *find_domain_by_sid(struct sss_domain_info *domain, + const char *sid) +{ + struct sss_domain_info *dom = domain; + size_t sid_len; + size_t dom_sid_len; + + if (sid == NULL) { + return NULL; + } + + sid_len = strlen(sid); + + while (dom && sss_domain_get_state(dom) == DOM_DISABLED) { + dom = get_next_domain(dom, SSS_GND_DESCEND); + } + + while (dom) { + if (dom->domain_id != NULL) { + dom_sid_len = strlen(dom->domain_id); + + if (strncasecmp(dom->domain_id, sid, dom_sid_len) == 0) { + if (dom_sid_len == sid_len) { + /* sid is domain sid */ + return dom; + } + + /* sid is object sid, check if domain sid is align with + * sid first subauthority component */ + if (sid[dom_sid_len] == '-') { + return dom; + } + } + } + + dom = get_next_domain(dom, SSS_GND_DESCEND); + } + + return NULL; +} + +struct sss_domain_info* +sss_get_domain_by_sid_ldap_fallback(struct sss_domain_info *domain, + const char* sid) +{ + /* LDAP provider doesn't know about sub-domains and hence can only + * have one configured domain + */ + if (strcmp(domain->provider, "ldap") == 0) { + return domain; + } else { + return find_domain_by_sid(get_domains_head(domain), sid); + } +} + +struct sss_domain_info * +find_domain_by_object_name_ex(struct sss_domain_info *domain, + const char *object_name, bool strict, + uint32_t gnd_flags) +{ + TALLOC_CTX *tmp_ctx; + struct sss_domain_info *dom = NULL; + char *domainname = NULL; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return NULL; + } + + ret = sss_parse_internal_fqname(tmp_ctx, object_name, + NULL, &domainname); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to parse name '%s' [%d]: %s\n", + object_name, ret, sss_strerror(ret)); + goto done; + } + + if (domainname == NULL) { + if (strict) { + dom = NULL; + } else { + dom = domain; + } + } else { + dom = find_domain_by_name_ex(domain, domainname, true, gnd_flags); + } + +done: + talloc_free(tmp_ctx); + return dom; +} + +struct sss_domain_info * +find_domain_by_object_name(struct sss_domain_info *domain, + const char *object_name) +{ + return find_domain_by_object_name_ex(domain, object_name, false, + SSS_GND_DESCEND); +} + +errno_t sssd_domain_init(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *domain_name, + const char *db_path, + struct sss_domain_info **_domain) +{ + int ret; + struct sss_domain_info *dom; + struct sysdb_ctx *sysdb; + + ret = confdb_get_domain(cdb, domain_name, &dom); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Error retrieving domain configuration.\n"); + return ret; + } + + if (dom->sysdb != NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Sysdb context already initialized.\n"); + return EEXIST; + } + + ret = sysdb_domain_init(mem_ctx, dom, db_path, &sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Error opening cache database.\n"); + return ret; + } + + dom->sysdb = talloc_steal(dom, sysdb); + + *_domain = dom; + + return EOK; +} + +static errno_t +sss_krb5_touch_config(void) +{ + const char *config = NULL; + errno_t ret; + + config = getenv("KRB5_CONFIG"); + if (config == NULL) { + config = KRB5_CONF_PATH; + } + + ret = utime(config, NULL); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to change mtime of \"%s\" " + "[%d]: %s\n", config, ret, strerror(ret)); + return ret; + } + + return EOK; +} + +errno_t sss_get_domain_mappings_content(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + char **content) +{ + int ret; + char *o = NULL; + struct sss_domain_info *dom; + struct sss_domain_info *parent_dom; + char *uc_parent = NULL; + char *uc_forest = NULL; + char *parent_capaths = NULL; + bool capaths_started = false; + + if (domain == NULL || content == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing parameter.\n"); + return EINVAL; + } + + o = talloc_strdup(mem_ctx, "[domain_realm]\n"); + if (o == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + /* Start with the parent domain */ + if (domain->realm != NULL) { + o = talloc_asprintf_append(o, ".%s = %s\n%s = %s\n", + domain->name, domain->realm, domain->name, + domain->realm); + if (o == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf_append failed.\n"); + ret = ENOMEM; + goto done; + } + } + /* This loops skips the starting parent and starts right with the first + * subdomain, if any. */ + for (dom = get_next_domain(domain, SSS_GND_DESCEND); + dom && IS_SUBDOMAIN(dom); /* if we get back to a parent, stop */ + dom = get_next_domain(dom, 0)) { + if (dom->realm != NULL) { + o = talloc_asprintf_append(o, ".%s = %s\n%s = %s\n", + dom->name, dom->realm, dom->name, dom->realm); + if (o == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf_append failed.\n"); + ret = ENOMEM; + goto done; + } + } + } + + parent_dom = domain; + uc_parent = get_uppercase_realm(mem_ctx, parent_dom->name); + if (uc_parent == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "get_uppercase_realm failed.\n"); + ret = ENOMEM; + goto done; + } + + for (dom = get_next_domain(domain, SSS_GND_DESCEND); + dom && IS_SUBDOMAIN(dom); /* if we get back to a parent, stop */ + dom = get_next_domain(dom, 0)) { + + if (dom->forest == NULL) { + continue; + } + + talloc_free(uc_forest); + uc_forest = get_uppercase_realm(mem_ctx, dom->forest); + if (uc_forest == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "get_uppercase_realm failed.\n"); + ret = ENOMEM; + goto done; + } + + if (!capaths_started) { + o = talloc_asprintf_append(o, "[capaths]\n"); + if (o == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf_append failed.\n"); + ret = ENOMEM; + goto done; + } + capaths_started = true; + } + + o = talloc_asprintf_append(o, "%s = {\n %s = %s\n}\n", + dom->realm, uc_parent, uc_forest); + if (o == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf_append failed.\n"); + ret = ENOMEM; + goto done; + } + + if (parent_capaths == NULL) { + parent_capaths = talloc_asprintf(mem_ctx, " %s = %s\n", dom->realm, + uc_forest); + } else { + parent_capaths = talloc_asprintf_append(parent_capaths, + " %s = %s\n", dom->realm, + uc_forest); + } + if (parent_capaths == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "talloc_asprintf/talloc_asprintf_append failed.\n"); + ret = ENOMEM; + goto done; + } + } + + if (parent_capaths != NULL) { + o = talloc_asprintf_append(o, "%s = {\n%s}\n", uc_parent, + parent_capaths); + if (o == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf_append failed.\n"); + ret = ENOMEM; + goto done; + } + } + + ret = EOK; + +done: + talloc_free(parent_capaths); + talloc_free(uc_parent); + talloc_free(uc_forest); + + if (ret == EOK) { + *content = o; + } else { + talloc_free(o); + } + + return ret; +} + +errno_t +sss_write_domain_mappings(struct sss_domain_info *domain) +{ + errno_t ret; + errno_t err; + TALLOC_CTX *tmp_ctx; + const char *mapping_file; + char *sanitized_domain; + char *tmp_file = NULL; + int fd = -1; + mode_t old_mode; + FILE *fstream = NULL; + int i; + char *content = NULL; + + if (domain == NULL || domain->name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No domain name provided\n"); + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + ret = sss_get_domain_mappings_content(tmp_ctx, domain, &content); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_get_domain_mappings_content failed.\n"); + goto done; + } + + sanitized_domain = talloc_strdup(tmp_ctx, domain->name); + if (sanitized_domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + return ENOMEM; + } + + /* only alpha-numeric chars, dashes and underscores are allowed in + * krb5 include directory */ + for (i = 0; sanitized_domain[i] != '\0'; i++) { + if (!isalnum(sanitized_domain[i]) + && sanitized_domain[i] != '-' && sanitized_domain[i] != '_') { + sanitized_domain[i] = '_'; + } + } + + mapping_file = talloc_asprintf(tmp_ctx, "%s/domain_realm_%s", + KRB5_MAPPING_DIR, sanitized_domain); + if (!mapping_file) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Mapping file for domain [%s] is [%s]\n", + domain->name, mapping_file); + + tmp_file = get_hidden_tmp_path(tmp_ctx, mapping_file); + if (tmp_file == NULL) { + ret = ENOMEM; + goto done; + } + + old_mode = umask(SSS_DFL_UMASK); + fd = mkstemp(tmp_file); + umask(old_mode); + if (fd < 0) { + DEBUG(SSSDBG_OP_FAILURE, + "creating the temp file [%s] for domain-realm mappings " + "failed.\n", tmp_file); + ret = EIO; + talloc_zfree(tmp_ctx); + goto done; + } + + fstream = fdopen(fd, "a"); + if (!fstream) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, "fdopen failed [%d]: %s\n", + ret, strerror(ret)); + ret = close(fd); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fclose failed [%d][%s].\n", ret, strerror(ret)); + /* Nothing to do here, just report the failure */ + } + ret = EIO; + goto done; + } + + ret = fprintf(fstream, "%s", content); + if (ret < 0) { + DEBUG(SSSDBG_OP_FAILURE, "fprintf failed\n"); + ret = EIO; + goto done; + } + + ret = fclose(fstream); + fstream = NULL; + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fclose failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + ret = rename(tmp_file, mapping_file); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "rename failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + talloc_zfree(tmp_file); + + ret = chmod(mapping_file, 0644); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fchmod failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + ret = EOK; +done: + err = sss_krb5_touch_config(); + if (err != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to change last modification time " + "of krb5.conf. Created mappings may not be loaded.\n"); + /* Ignore */ + } + + if (fstream) { + err = fclose(fstream); + if (err != 0) { + err = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fclose failed [%d][%s].\n", err, strerror(err)); + /* Nothing to do here, just report the failure */ + } + } + + if (tmp_file) { + err = unlink(tmp_file); + if (err < 0) { + err = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not remove file [%s]: [%d]: %s\n", + tmp_file, err, strerror(err)); + } + } + talloc_free(tmp_ctx); + return ret; +} + +/* Save domain names, do not descend. */ +errno_t get_dom_names(TALLOC_CTX *mem_ctx, + struct sss_domain_info *start_dom, + char ***_dom_names, + int *_dom_names_count) +{ + struct sss_domain_info *dom; + TALLOC_CTX *tmp_ctx; + char **dom_names; + size_t count, i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + /* get count of domains*/ + count = 0; + dom = start_dom; + while (dom) { + count++; + dom = get_next_domain(dom, 0); + } + + dom_names = talloc_array(tmp_ctx, char*, count); + if (dom_names == NULL) { + ret = ENOMEM; + goto done; + } + + /* copy names */ + i = 0; + dom = start_dom; + while (dom) { + dom_names[i] = talloc_strdup(dom_names, dom->name); + if (dom_names[i] == NULL) { + ret = ENOMEM; + goto done; + } + dom = get_next_domain(dom, 0); + i++; + } + + if (_dom_names != NULL ) { + *_dom_names = talloc_steal(mem_ctx, dom_names); + } + + if (_dom_names_count != NULL ) { + *_dom_names_count = count; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +char *get_hidden_tmp_path(TALLOC_CTX *mem_ctx, const char *path) +{ + const char *s; + + if (path == NULL) { + return NULL; + } + + s = strrchr(path, '/'); + if (s == NULL) { + /* No path, just file name */ + return talloc_asprintf(mem_ctx, ".%sXXXXXX", path); + } else if ( *(s + 1) == '\0') { + /* '/' is the last character, there is no filename */ + DEBUG(SSSDBG_OP_FAILURE, "Missing file name in [%s].\n", path); + return NULL; + } + + return talloc_asprintf(mem_ctx, "%.*s.%sXXXXXX", (int)(s - path + 1), + path, s+1); +} + +static errno_t sss_write_krb5_snippet_common(const char *file_name, + const char *content) +{ + int ret; + errno_t err; + TALLOC_CTX *tmp_ctx = NULL; + char *tmp_file = NULL; + int fd = -1; + mode_t old_mode; + ssize_t written; + size_t size; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + tmp_file = get_hidden_tmp_path(tmp_ctx, file_name); + if (tmp_file == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + old_mode = umask(SSS_DFL_UMASK); + fd = mkstemp(tmp_file); + umask(old_mode); + if (fd < 0) { + DEBUG(SSSDBG_OP_FAILURE, "creating the temp file [%s] for " + "krb5 config snippet failed.\n", tmp_file); + ret = EIO; + talloc_zfree(tmp_ctx); + goto done; + } + + size = strlen(content); + written = sss_atomic_write_s(fd, discard_const(content), size); + close(fd); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "write failed [%d][%s]\n", ret, sss_strerror(ret)); + goto done; + } + + if (written != size) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Wrote %zd bytes expected %zu\n", written, size); + ret = EIO; + goto done; + } + + ret = chmod(tmp_file, 0644); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "chmod failed [%d][%s].\n", ret, sss_strerror(ret)); + goto done; + } + + ret = rename(tmp_file, file_name); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "rename failed [%d][%s].\n", ret, sss_strerror(ret)); + goto done; + } + tmp_file = NULL; + +done: + if (tmp_file != NULL) { + err = unlink(tmp_file); + if (err == -1) { + err = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not remove file [%s]: [%d]: %s\n", + tmp_file, err, sss_strerror(err)); + } + } + + talloc_free(tmp_ctx); + return ret; +} + +#define LOCALAUTH_PLUGIN_CONFIG \ +"[plugins]\n" \ +" localauth = {\n" \ +" module = sssd:"APP_MODULES_PATH"/sssd_krb5_localauth_plugin.so\n" \ +" }\n" + +static errno_t sss_write_krb5_localauth_snippet(const char *path) +{ +#ifdef HAVE_KRB5_LOCALAUTH_PLUGIN + int ret; + TALLOC_CTX *tmp_ctx = NULL; + const char *file_name; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + file_name = talloc_asprintf(tmp_ctx, "%s/localauth_plugin", path); + if (file_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "File for localauth plugin configuration is [%s]\n", + file_name); + + ret = sss_write_krb5_snippet_common(file_name, LOCALAUTH_PLUGIN_CONFIG); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_write_krb5_snippet_common failed.\n"); + goto done; + } + +done: + + talloc_free(tmp_ctx); + return ret; + +#else + DEBUG(SSSDBG_TRACE_ALL, "Kerberos localauth plugin not available.\n"); + return EOK; +#endif +} + +static errno_t sss_write_krb5_libdefaults_snippet(const char *path, + bool canonicalize, + bool udp_limit) +{ + int ret; + TALLOC_CTX *tmp_ctx = NULL; + const char *file_name; + char *file_contents; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + file_name = talloc_asprintf(tmp_ctx, "%s/krb5_libdefaults", path); + if (file_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "File for KRB5 libdefaults configuration is [%s]\n", + file_name); + + file_contents = talloc_strdup(tmp_ctx, "[libdefaults]\n"); + if (file_contents == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "talloc_asprintf failed while creating the content\n"); + ret = ENOMEM; + goto done; + } + + if (canonicalize == true) { + file_contents = talloc_asprintf_append(file_contents, + " canonicalize = true\n"); + if (file_contents == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "talloc_asprintf failed while appending to the content\n"); + ret = ENOMEM; + goto done; + } + } + + if (udp_limit == true) { + file_contents = talloc_asprintf_append(file_contents, + " udp_preference_limit = 0\n"); + if (file_contents == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "talloc_asprintf failed while appending to the content\n"); + ret = ENOMEM; + goto done; + } + } + + ret = sss_write_krb5_snippet_common(file_name, file_contents); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_write_krb5_snippet_common failed.\n"); + goto done; + } + +done: + + talloc_free(tmp_ctx); + return ret; +} + +errno_t sss_write_krb5_conf_snippet(const char *path, bool canonicalize, + bool udp_limit) +{ + errno_t ret; + errno_t err; + + if (path != NULL && (*path == '\0' || strcasecmp(path, "none") == 0)) { + DEBUG(SSSDBG_TRACE_FUNC, "Empty path, nothing to do.\n"); + return EOK; + } + + if (path == NULL || *path != '/') { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid or missing path [%s]-\n", + path == NULL ? "missing" : path); + return EINVAL; + } + + ret = sss_write_krb5_localauth_snippet(path); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_write_krb5_localauth_snippet failed.\n"); + goto done; + } + + ret = sss_write_krb5_libdefaults_snippet(path, canonicalize, udp_limit); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_write_krb5_libdefaults_snippet failed.\n"); + goto done; + } + + ret = EOK; + +done: + err = sss_krb5_touch_config(); + if (err != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to change last modification time " + "of krb5.conf. Created mappings may not be loaded.\n"); + /* Ignore */ + } + + return ret; +} + +static const char *domain_state_str(struct sss_domain_info *dom) +{ + switch (dom->state) { + case DOM_ACTIVE: + return "Active"; + case DOM_DISABLED: + return "Disabled"; + case DOM_INACTIVE: + return "Inactive"; + case DOM_INCONSISTENT: + return "Inconsistent"; + } + return "Unknown"; +} + +enum sss_domain_state sss_domain_get_state(struct sss_domain_info *dom) +{ + DEBUG(SSSDBG_TRACE_LIBS, + "Domain %s is %s\n", dom->name, domain_state_str(dom)); + return dom->state; +} + +void sss_domain_set_state(struct sss_domain_info *dom, + enum sss_domain_state state) +{ + dom->state = state; + DEBUG(SSSDBG_TRACE_LIBS, + "Domain %s is %s\n", dom->name, domain_state_str(dom)); +} + +#ifdef BUILD_FILES_PROVIDER +bool sss_domain_fallback_to_nss(struct sss_domain_info *dom) +{ + return dom->fallback_to_nss; +} +#endif + +bool sss_domain_is_forest_root(struct sss_domain_info *dom) +{ + return (dom->forest_root == dom); +} + +char *subdomain_create_conf_path_from_str(TALLOC_CTX *mem_ctx, + const char *parent_name, + const char *subdom_name) +{ + return talloc_asprintf(mem_ctx, CONFDB_DOMAIN_PATH_TMPL "/%s", + parent_name, subdom_name); +} + +char *subdomain_create_conf_path(TALLOC_CTX *mem_ctx, + struct sss_domain_info *subdomain) +{ + if (!IS_SUBDOMAIN(subdomain)) { + DEBUG(SSSDBG_OP_FAILURE, + "The domain \"%s\" is not a subdomain.\n", + subdomain->name); + return NULL; + } + + return subdomain_create_conf_path_from_str(mem_ctx, + subdomain->parent->name, + subdomain->name); +} + +const char *sss_domain_type_str(struct sss_domain_info *dom) +{ + if (dom == NULL) { + return "BUG: Invalid domain"; + } + switch (dom->type) { + case DOM_TYPE_POSIX: + return "POSIX"; + case DOM_TYPE_APPLICATION: + return "Application"; + } + return "Unknown"; +} + +void sss_domain_info_set_output_fqnames(struct sss_domain_info *domain, + bool output_fqnames) +{ + domain->output_fqnames = output_fqnames; +} + +bool sss_domain_info_get_output_fqnames(struct sss_domain_info *domain) +{ + return domain->output_fqnames; +} + +bool sss_domain_is_mpg(struct sss_domain_info *domain) +{ + return domain->mpg_mode == MPG_ENABLED; +} + +bool sss_domain_is_hybrid(struct sss_domain_info *domain) +{ + return domain->mpg_mode == MPG_HYBRID; +} + +enum sss_domain_mpg_mode get_domain_mpg_mode(struct sss_domain_info *domain) +{ + return domain->mpg_mode; +} + +const char *str_domain_mpg_mode(enum sss_domain_mpg_mode mpg_mode) +{ + switch (mpg_mode) { + case MPG_ENABLED: + return "true"; + case MPG_DISABLED: + return "false"; + case MPG_HYBRID: + return "hybrid"; + case MPG_DEFAULT: + return "default"; + } + + return NULL; +} + +enum sss_domain_mpg_mode str_to_domain_mpg_mode(const char *str_mpg_mode) +{ + if (strcasecmp(str_mpg_mode, "FALSE") == 0) { + return MPG_DISABLED; + } else if (strcasecmp(str_mpg_mode, "TRUE") == 0) { + return MPG_ENABLED; + } else if (strcasecmp(str_mpg_mode, "HYBRID") == 0) { + return MPG_HYBRID; + } else if (strcasecmp(str_mpg_mode, "DEFAULT") == 0) { + return MPG_DEFAULT; + } + + + DEBUG(SSSDBG_MINOR_FAILURE, + "Invalid value for %s\n, assuming disabled", + SYSDB_SUBDOMAIN_MPG); + return MPG_DISABLED; +} diff --git a/src/util/file_watch.c b/src/util/file_watch.c new file mode 100644 index 0000000..d19fdcc --- /dev/null +++ b/src/util/file_watch.c @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2022, Red Hat Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> +#include <sys/time.h> +#include <errno.h> + +#include "util/inotify.h" +#include "util/util.h" +#include "util/file_watch.h" + + + +#define MISSING_FILE_POLL_TIME 10 /* seconds */ +#define FILE_WATCH_POLL_INTERVAL 5 /* seconds */ + + +struct file_watch_ctx { + struct tevent_context *ev; + const char *filename; + bool use_inotify; + + struct config_file_inotify_check { + struct snotify_ctx *snctx; + } inotify_check; + + struct config_file_poll_check { + struct tevent_timer *timer; + time_t modified; + } poll_check; + + fw_callback cb; + void *cb_arg; +}; + + +static void poll_watched_file(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr); + +static errno_t create_poll_timer(struct file_watch_ctx *fw_ctx) +{ + struct timeval tv; + + tv = tevent_timeval_current_ofs(FILE_WATCH_POLL_INTERVAL, 0); + + fw_ctx->poll_check.timer = tevent_add_timer(fw_ctx->ev, + fw_ctx, + tv, + poll_watched_file, + fw_ctx); + if (!fw_ctx->poll_check.timer) { + return EIO; + } + + return EOK; +} + +static void poll_watched_file(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + int ret, err; + struct stat file_stat; + struct file_watch_ctx *fw_ctx; + + fw_ctx = talloc_get_type(ptr, struct file_watch_ctx); + + ret = stat(fw_ctx->filename, &file_stat); + if (ret < 0) { + err = errno; + DEBUG(SSSDBG_IMPORTANT_INFO, + "Could not stat file [%s]. Error [%d:%s]\n", + fw_ctx->filename, err, strerror(err)); + return; + } + + if (file_stat.st_mtime != fw_ctx->poll_check.modified) { + /* Parse the file and invoke the callback */ + /* Note: this will fire if the modification time changes into the past + * as well as the future. + */ + DEBUG(SSSDBG_TRACE_INTERNAL, "File [%s] changed\n", fw_ctx->filename); + fw_ctx->poll_check.modified = file_stat.st_mtime; + + /* Tell the caller the file changed */ + fw_ctx->cb(fw_ctx->filename, fw_ctx->cb_arg); + } + + ret = create_poll_timer(fw_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Error: File [%s] no longer monitored for changes!\n", + fw_ctx->filename); + } +} + + +static int watched_file_inotify_cb(const char *filename, + uint32_t flags, + void *pvt) +{ + static char received[PATH_MAX + 1]; + static char expected[PATH_MAX + 1]; + struct file_watch_ctx *fw_ctx; + char *res; + + DEBUG(SSSDBG_TRACE_LIBS, + "Received inotify notification for %s\n", filename); + + fw_ctx = talloc_get_type(pvt, struct file_watch_ctx); + if (fw_ctx == NULL) { + return EINVAL; + } + + res = realpath(fw_ctx->filename, expected); + if (res == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, + "Normalization failed for expected %s. Skipping the callback.\n", + fw_ctx->filename); + goto done; + } + + res = realpath(filename, received); + if (res == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, + "Normalization failed for received %s. Skipping the callback.\n", + filename); + goto done; + } + + if (strcmp(expected, received) == 0) { + if (access(received, F_OK) == 0) { + fw_ctx->cb(received, fw_ctx->cb_arg); + } else { + DEBUG(SSSDBG_TRACE_LIBS, + "File %s is missing. Skipping the callback.\n", filename); + } + } + +done: + return EOK; +} + + +static int try_inotify(struct file_watch_ctx *fw_ctx) +{ +#ifdef HAVE_INOTIFY + struct snotify_ctx *snctx; + /* We will queue the file for update in one second. + * This way, if there is a script writing to the file + * repeatedly, we won't be attempting to update multiple + * times. + */ + struct timeval delay = { .tv_sec = 1, .tv_usec = 0 }; + + snctx = snotify_create(fw_ctx, fw_ctx->ev, SNOTIFY_WATCH_DIR, + fw_ctx->filename, &delay, + IN_DELETE_SELF | IN_CLOSE_WRITE | IN_MOVE_SELF | \ + IN_CREATE | IN_MOVED_TO | IN_IGNORED, + watched_file_inotify_cb, fw_ctx); + if (snctx == NULL) { + return EIO; + } + + return EOK; +#else + return EINVAL; +#endif /* HAVE_INOTIFY */ +} + +static errno_t fw_watch_file_poll(struct file_watch_ctx *fw_ctx) +{ + struct stat file_stat; + int ret, err; + + ret = stat(fw_ctx->filename, &file_stat); + if (ret < 0) { + err = errno; + if (err == ENOENT) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "File [%s] is missing. Will try again later.\n", + fw_ctx->filename); + } else { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Could not stat file [%s]. Error [%d:%s]\n", + fw_ctx->filename, err, strerror(err)); + } + return err; + } + + fw_ctx->poll_check.modified = file_stat.st_mtime; + + if(!fw_ctx->poll_check.timer) { + ret = create_poll_timer(fw_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, "Cannot create poll timer\n"); + return ret; + } + } + + return EOK; +} + + +static int watch_file(struct file_watch_ctx *fw_ctx) +{ + int ret = EOK; + bool use_inotify; + + use_inotify = fw_ctx->use_inotify; + if (use_inotify) { + ret = try_inotify(fw_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, "Falling back to polling\n"); + use_inotify = false; + } + } + + if (!use_inotify) { + ret = fw_watch_file_poll(fw_ctx); + } + + return ret; +} + + +static void set_file_watching(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *data) +{ + int ret; + struct file_watch_ctx *fw_ctx = talloc_get_type(data, struct file_watch_ctx); + + ret = watch_file(fw_ctx); + if (ret == EOK) { + if (access(fw_ctx->filename, F_OK) == 0) { + fw_ctx->cb(fw_ctx->filename, fw_ctx->cb_arg); + } + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_LIBS, + "%s missing. Waiting for it to appear.\n", + fw_ctx->filename); + tv = tevent_timeval_current_ofs(MISSING_FILE_POLL_TIME, 0); + te = tevent_add_timer(fw_ctx->ev, fw_ctx, tv, set_file_watching, fw_ctx); + if (te == NULL) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "tevent_add_timer failed. %s will be ignored.\n", + fw_ctx->filename); + } + } else { + DEBUG(SSSDBG_IMPORTANT_INFO, + "watch_file failed. %s will be ignored: [%i] %s\n", + fw_ctx->filename, ret, sss_strerror(ret)); + } +} + + +struct file_watch_ctx *fw_watch_file(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *filename, + bool use_inotify, + fw_callback cb, + void *cb_arg) +{ + int ret; + struct timeval tv; + struct file_watch_ctx *fw_ctx; + + if (ev == NULL || filename == NULL || cb == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, "Invalid parameter\n"); + return NULL; + } + + fw_ctx = talloc_zero(mem_ctx, struct file_watch_ctx); + if (fw_ctx == NULL) { + DEBUG(SSSDBG_IMPORTANT_INFO, "Failed to allocate the context\n"); + return NULL; + } + + fw_ctx->ev = ev; + fw_ctx->use_inotify = use_inotify; + fw_ctx->cb = cb; + fw_ctx->cb_arg = cb_arg; + fw_ctx->filename = talloc_strdup(fw_ctx, filename); + if (fw_ctx->filename == NULL) { + DEBUG(SSSDBG_IMPORTANT_INFO, "talloc_strdup() failed\n"); + ret = ENOMEM; + goto done; + } + + /* Watch for changes to the requested file, and retry periodically + * if the file does not exist */ + tv = tevent_timeval_current_ofs(0, 0); // Not actually used + set_file_watching(fw_ctx->ev, NULL, tv, fw_ctx); + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(fw_ctx); + fw_ctx = NULL; + } + + return fw_ctx; +} diff --git a/src/util/file_watch.h b/src/util/file_watch.h new file mode 100644 index 0000000..7f81852 --- /dev/null +++ b/src/util/file_watch.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 Red Hat Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _FILE_WATCH_H +#define _FILE_WATCH_H + +#include <stdbool.h> +#include <talloc.h> +#include <tevent.h> + + +typedef void (*fw_callback)(const char *filename, void *arg); +struct file_watch_ctx; + +/* + * This function configures the watching of a file. When the file is created + * or modified, the provided callback function is invoked. + * + * If the file exists at the moment this function is called, the callback + * will be invoked once. A first processing of the file can be done in that + * case. If the file does not exist at that moment, the callback will be + * invoked when the file is created. + * + * inotify will be used to watch the file unless 'use_notify' is set to 'false' + * in sssd.conf or inotify fails (not installed). In those two cases, the + * file state will be polled every 10 seconds when the file doesn't exist + * to detect its creation, and every 5 seconds when the file exists to detect + * changes. + */ +struct file_watch_ctx *fw_watch_file(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *filename, + bool use_inotify, + fw_callback cb, + void *cb_arg); + + + +#endif /* _FILE_WATCH_H */ diff --git a/src/util/files.c b/src/util/files.c new file mode 100644 index 0000000..5b7fbc8 --- /dev/null +++ b/src/util/files.c @@ -0,0 +1,892 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright (c) 1991 - 1994, Julianne Frances Haugh + * Copyright (c) 1996 - 2001, Marek Michałkiewicz + * Copyright (c) 2003 - 2006, Tomasz Kłoczko + * Copyright (c) 2007 - 2008, Nicolas François + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the copyright holders or contributors may not be used to + * endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/time.h> +#include <dirent.h> +#include <fcntl.h> +#include <errno.h> +#include <talloc.h> + +#include "util/util.h" + +struct copy_ctx { + const char *src_orig; + const char *dst_orig; + dev_t src_dev; + uid_t uid; + gid_t gid; +}; + +static int sss_timeat_set(int dir_fd, const char *path, + const struct stat *statp, + int flags) +{ + int ret; + +#ifdef HAVE_UTIMENSAT + struct timespec timebuf[2]; + + timebuf[0] = statp->st_atim; + timebuf[1] = statp->st_mtim; + + ret = utimensat(dir_fd, path, timebuf, flags); +#else + struct timeval tv[2]; + + tv[0].tv_sec = statp->st_atime; + tv[0].tv_usec = 0; + tv[1].tv_sec = statp->st_mtime; + tv[1].tv_usec = 0; + + ret = futimesat(dir_fd, path, tv); +#endif + if (ret == -1) { + return errno; + } + + return EOK; +} + +static int sss_futime_set(int fd, const struct stat *statp) +{ + int ret; + +#ifdef HAVE_FUTIMENS + struct timespec timebuf[2]; + + timebuf[0] = statp->st_atim; + timebuf[1] = statp->st_mtim; + ret = futimens(fd, timebuf); +#else + struct timeval tv[2]; + + tv[0].tv_sec = statp->st_atime; + tv[0].tv_usec = 0; + tv[1].tv_sec = statp->st_mtime; + tv[1].tv_usec = 0; + + ret = futimes(fd, tv); +#endif + if (ret == -1) { + return errno; + } + + return EOK; +} + +/* wrapper in order not to create a temporary context in + * every iteration */ +static int remove_tree_with_ctx(TALLOC_CTX *mem_ctx, + int parent_fd, + const char *dir_name, + dev_t parent_dev, + bool keep_root_dir); + +int sss_remove_tree(const char *root) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = remove_tree_with_ctx(tmp_ctx, AT_FDCWD, root, 0, false); + talloc_free(tmp_ctx); + return ret; +} + +int sss_remove_subtree(const char *root) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = remove_tree_with_ctx(tmp_ctx, AT_FDCWD, root, 0, true); + talloc_free(tmp_ctx); + return ret; +} + +/* + * The context is not freed in case of error + * because this is a recursive function, will be freed when we + * reach the top level remove_tree() again + */ +static int remove_tree_with_ctx(TALLOC_CTX *mem_ctx, + int parent_fd, + const char *dir_name, + dev_t parent_dev, + bool keep_root_dir) +{ + struct dirent *result; + struct stat statres; + DIR *rootdir = NULL; + int ret, err; + int dir_fd; + int log_level; + + dir_fd = sss_openat_cloexec(parent_fd, dir_name, + O_RDONLY | O_DIRECTORY | O_NOFOLLOW, &ret); + if (dir_fd == -1) { + ret = errno; + if (ret == ENOENT) { + log_level = SSSDBG_TRACE_FUNC; + } else { + log_level = SSSDBG_MINOR_FAILURE; + } + DEBUG(log_level, "Cannot open %s: [%d]: %s\n", + dir_name, ret, strerror(ret)); + return ret; + } + + rootdir = fdopendir(dir_fd); + if (rootdir == NULL) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot open directory: [%d][%s]\n", ret, strerror(ret)); + close(dir_fd); + goto fail; + } + + while ((result = readdir(rootdir)) != NULL) { + if (strcmp(result->d_name, ".") == 0 || + strcmp(result->d_name, "..") == 0) { + continue; + } + + ret = fstatat(dir_fd, result->d_name, + &statres, AT_SYMLINK_NOFOLLOW); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "stat failed: [%d][%s]\n", ret, strerror(ret)); + goto fail; + } + + if (S_ISDIR(statres.st_mode)) { + /* if directory, recursively descend, but check if on the same FS */ + if (parent_dev && parent_dev != statres.st_dev) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Directory %s is on different filesystem, " + "will not follow\n", result->d_name); + ret = EFAULT; + goto fail; + } + + ret = remove_tree_with_ctx(mem_ctx, dir_fd, result->d_name, + statres.st_dev, false); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Removing subdirectory failed: [%d][%s]\n", + ret, strerror(ret)); + goto fail; + } + } else { + ret = unlinkat(dir_fd, result->d_name, 0); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "Removing file failed '%s': [%d][%s]\n", + result->d_name, ret, strerror(ret)); + goto fail; + } + } + } + + ret = closedir(rootdir); + rootdir = NULL; + if (ret != 0) { + ret = errno; + goto fail; + } + + if (!keep_root_dir) { + /* Remove also root directory. */ + ret = unlinkat(parent_fd, dir_name, AT_REMOVEDIR); + if (ret == -1) { + ret = errno; + } + } + + ret = EOK; +fail: + if (rootdir) { /* clean up on abnormal exit but retain return code */ + err = closedir(rootdir); + if (err) { + DEBUG(SSSDBG_MINOR_FAILURE, "closedir failed, bad dirp?\n"); + } + } + return ret; +} + +static char *talloc_readlinkat(TALLOC_CTX *mem_ctx, int dir_fd, + const char *filename) +{ + size_t size = 1024; + ssize_t nchars; + char *buffer; + char *new_buffer; + + buffer = talloc_array(mem_ctx, char, size); + if (!buffer) { + return NULL; + } + + while (1) { + nchars = readlinkat(dir_fd, filename, buffer, size); + if (nchars < 0) { + talloc_free(buffer); + return NULL; + } + + if ((size_t) nchars < size) { + /* The buffer was large enough */ + break; + } + + /* Try again with a bigger buffer */ + size *= 2; + new_buffer = talloc_realloc(mem_ctx, buffer, char, size); + if (!new_buffer) { + talloc_free(buffer); + return NULL; + } + buffer = new_buffer; + } + + /* readlink does not nul-terminate */ + buffer[nchars] = '\0'; + return buffer; +} + +static int +copy_symlink(int src_dir_fd, + int dst_dir_fd, + const char *file_name, + const char *full_path, + const struct stat *statp, + uid_t uid, gid_t gid) +{ + char *buf; + errno_t ret; + + buf = talloc_readlinkat(NULL, src_dir_fd, file_name); + if (!buf) { + return ENOMEM; + } + + ret = selinux_file_context(full_path); + if (ret != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to set SELinux context for [%s]\n", full_path); + /* Not fatal */ + } + + ret = symlinkat(buf, dst_dir_fd, file_name); + talloc_free(buf); + if (ret == -1) { + ret = errno; + if (ret == EEXIST) { + DEBUG(SSSDBG_MINOR_FAILURE, + "symlink pointing to already exists at '%s'\n", full_path); + return EOK; + } + + DEBUG(SSSDBG_CRIT_FAILURE, "symlinkat failed: %s\n", strerror(ret)); + return ret; + } + + ret = fchownat(dst_dir_fd, file_name, + uid, gid, AT_SYMLINK_NOFOLLOW); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fchownat failed: %s\n", strerror(ret)); + return ret; + } + + ret = sss_timeat_set(dst_dir_fd, file_name, statp, + AT_SYMLINK_NOFOLLOW); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "utimensat failed [%d]: %s\n", + ret, strerror(ret)); + /* Do not fail */ + } + + return EOK; +} + +static int +copy_file_contents(int ifd, + int ofd, + mode_t mode, + uid_t uid, gid_t gid) +{ + errno_t ret; + char buf[1024]; + ssize_t cnt, written; + + while ((cnt = sss_atomic_read_s(ifd, buf, sizeof(buf))) != 0) { + if (cnt == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot read() from source file: [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + errno = 0; + written = sss_atomic_write_s(ofd, buf, cnt); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot write() to destination file: [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + if (written != cnt) { + ret = EINVAL; + DEBUG(SSSDBG_CRIT_FAILURE, + "Wrote %zd bytes, expected %zd\n", written, cnt); + goto done; + } + } + + /* Set the ownership; permissions are still + * restrictive. */ + ret = fchown(ofd, uid, gid); + if (ret == -1 && errno != EPERM) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "Error changing owner: %s\n", + strerror(ret)); + goto done; + } + + /* Set the desired mode. */ + ret = fchmod(ofd, mode); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, "Error changing mode: %s\n", + strerror(ret)); + goto done; + } + + ret = EOK; + +done: + return ret; +} + + +/* Copy bytes from input file descriptor ifd into file named + * dst_named under directory with dest_dir_fd. Own the new file + * by uid/gid + */ +static int +copy_file(int ifd, + int dest_dir_fd, + const char *file_name, + const char *full_path, + const struct stat *statp, + uid_t uid, gid_t gid) +{ + int ofd = -1; + errno_t ret; + + ret = selinux_file_context(full_path); + if (ret != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to set SELinux context for [%s]\n", full_path); + /* Not fatal */ + } + + /* Start with absolutely restrictive permissions */ + ofd = openat(dest_dir_fd, file_name, + O_EXCL | O_CREAT | O_WRONLY | O_NOFOLLOW, + 0); + if (ofd < 0 && errno != EEXIST) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "Cannot open() destination file '%s': [%d][%s].\n", + full_path, ret, strerror(ret)); + goto done; + } + + ret = copy_file_contents(ifd, ofd, statp->st_mode, uid, gid); + if (ret != EOK) goto done; + + + ret = sss_futime_set(ofd, statp); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "sss_futime_set failed [%d]: %s\n", + ret, strerror(ret)); + /* Do not fail */ + } + ret = EOK; + +done: + if (ofd != -1) close(ofd); + return ret; +} + +int +sss_copy_file_secure(const char *src, + const char *dest, + mode_t mode, + uid_t uid, gid_t gid, + bool force) +{ + int ifd = -1; + int ofd = -1; + int dest_flags = 0; + errno_t ret; + + ret = selinux_file_context(dest); + if (ret != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to set SELinux context for [%s]\n", dest); + /* Not fatal */ + } + + /* Start with absolutely restrictive permissions */ + dest_flags = O_CREAT | O_WRONLY | O_NOFOLLOW; + if (!force) { + dest_flags |= O_EXCL; + } + + ofd = open(dest, dest_flags, mode); + if (ofd < 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot open() destination file '%s': [%d][%s].\n", + dest, errno, strerror(errno)); + goto done; + } + + ifd = sss_open_cloexec(src, O_RDONLY | O_NOFOLLOW, &ret); + if (ifd < 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot open() source file '%s': [%d][%s].\n", + src, ret, strerror(ret)); + goto done; + } + + ret = copy_file_contents(ifd, ofd, mode, uid, gid); + +done: + if (ifd != -1) close(ifd); + if (ofd != -1) close(ofd); + return ret; +} + +static errno_t +copy_dir(struct copy_ctx *cctx, + int src_dir_fd, const char *src_dir_path, + int dest_parent_fd, const char *dest_dir_name, + const char *dest_dir_path, + mode_t mode, + const struct stat *src_dir_stat); + +static errno_t +copy_entry(struct copy_ctx *cctx, + int src_dir_fd, + const char *src_dir_path, + int dest_dir_fd, + const char *dest_dir_path, + const char *ent_name) +{ + char *src_ent_path = NULL; + char *dest_ent_path = NULL; + int ifd = -1; + errno_t ret; + struct stat st; + + /* Build the path of the source file or directory and its + * corresponding member in the new tree. */ + src_ent_path = talloc_asprintf(cctx, "%s/%s", src_dir_path, ent_name); + dest_ent_path = talloc_asprintf(cctx, "%s/%s", dest_dir_path, ent_name); + if (!src_ent_path || !dest_ent_path) { + ret = ENOMEM; + goto done; + } + + /* Open the input entry first, then we can fstat() it and be + * certain that it is still the same file. O_NONBLOCK protects + * us against FIFOs and perhaps side-effects of the open() of a + * device file if there ever was one here, and doesn't matter + * for regular files or directories. */ + ifd = sss_openat_cloexec(src_dir_fd, ent_name, + O_RDONLY | O_NOFOLLOW | O_NONBLOCK, &ret); + if (ifd == -1 && ret != ELOOP) { + /* openat error */ + DEBUG(SSSDBG_CRIT_FAILURE, "openat failed on '%s': %s\n", + src_ent_path, strerror(ret)); + goto done; + } else if (ifd == -1 && ret == ELOOP) { + /* Should be a symlink.. */ + ret = fstatat(src_dir_fd, ent_name, &st, AT_SYMLINK_NOFOLLOW); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fstatat failed on '%s': %s\n", + src_ent_path, strerror(ret)); + goto done; + } + + /* Handle symlinks */ + ret = copy_symlink(src_dir_fd, dest_dir_fd, ent_name, + dest_ent_path, &st, cctx->uid, cctx->gid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot copy '%s' to '%s'\n", + src_ent_path, dest_ent_path); + } + goto done; + } + + ret = fstat(ifd, &st); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "couldn't stat '%s': %s\n", src_ent_path, strerror(ret)); + goto done; + } + + if (S_ISDIR(st.st_mode)) { + /* If it's a directory, descend into it. */ + ret = copy_dir(cctx, ifd, src_ent_path, + dest_dir_fd, ent_name, + dest_ent_path, st.st_mode & 07777, + &st); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Couldn't recursively copy '%s' to '%s': %s\n", + src_ent_path, dest_ent_path, strerror(ret)); + goto done; + } + } else if (S_ISREG(st.st_mode)) { + /* Copy a regular file */ + ret = copy_file(ifd, dest_dir_fd, ent_name, dest_ent_path, + &st, cctx->uid, cctx->gid); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot copy '%s' to '%s'\n", + src_ent_path, dest_ent_path); + goto done; + } + } else { + /* Is a special file */ + DEBUG(SSSDBG_FUNC_DATA, "'%s' is a special file, skipping.\n", + src_ent_path); + } + + ret = EOK; +done: + talloc_free(src_ent_path); + talloc_free(dest_ent_path); + if (ifd != -1) close(ifd); + return ret; +} + +static errno_t +copy_dir(struct copy_ctx *cctx, + int src_dir_fd, const char *src_dir_path, + int dest_parent_fd, const char *dest_dir_name, + const char *dest_dir_path, + mode_t mode, + const struct stat *src_dir_stat) +{ + errno_t ret; + errno_t dret; + int dest_dir_fd = -1; + DIR *dir = NULL; + struct dirent *ent; + + if (!dest_dir_path) { + return EINVAL; + } + + dir = fdopendir(src_dir_fd); + if (dir == NULL) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Error reading '%s': %s\n", src_dir_path, strerror(ret)); + goto done; + } + + /* Create the directory. It starts owned by us (presumably root), with + * fairly restrictive permissions that still allow us to use the + * directory. + * */ + errno = 0; + ret = mkdirat(dest_parent_fd, dest_dir_name, S_IRWXU); + if (ret == -1 && errno != EEXIST) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Error reading '%s': %s\n", dest_dir_path, strerror(ret)); + goto done; + } + + dest_dir_fd = sss_openat_cloexec(dest_parent_fd, dest_dir_name, + O_RDONLY | O_DIRECTORY | O_NOFOLLOW, &ret); + if (dest_dir_fd == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Error opening '%s': %s\n", dest_dir_path, strerror(ret)); + goto done; + } + + while ((ent = readdir(dir)) != NULL) { + /* Iterate through each item in the directory. */ + /* Skip over self and parent hard links. */ + if (strcmp(ent->d_name, ".") == 0 || + strcmp(ent->d_name, "..") == 0) { + continue; + } + + ret = copy_entry(cctx, + src_dir_fd, src_dir_path, + dest_dir_fd, dest_dir_path, + ent->d_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not copy [%s] to [%s]\n", + src_dir_path, dest_dir_path); + goto done; + } + } + + /* Set the ownership on the directory. Permissions are still + * fairly restrictive. */ + ret = fchown(dest_dir_fd, cctx->uid, cctx->gid); + if (ret == -1 && errno != EPERM) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "Error changing owner of '%s': %s\n", + dest_dir_path, strerror(ret)); + goto done; + } + + /* Set the desired mode. Do this explicitly to preserve S_ISGID and + * other bits. Do this after chown, because chown is permitted to + * reset these bits. */ + ret = fchmod(dest_dir_fd, mode); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "Error setting mode of '%s': %s\n", + dest_dir_path, strerror(ret)); + goto done; + } + + sss_futime_set(dest_dir_fd, src_dir_stat); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "sss_futime_set failed [%d]: %s\n", + ret, strerror(ret)); + /* Do not fail */ + } + + ret = EOK; +done: + if (dir) { + dret = closedir(dir); + if (dret != 0) { + dret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to close directory: %s.\n", strerror(dret)); + } + } + + if (dest_dir_fd != -1) { + close(dest_dir_fd); + } + return ret; +} + +/* NOTE: + * For several reasons, including the fact that we copy even special files + * (pipes, etc) from the skeleton directory, the skeldir needs to be trusted + */ +int sss_copy_tree(const char *src_root, + const char *dst_root, + mode_t mode_root, + uid_t uid, gid_t gid) +{ + int ret = EOK; + struct copy_ctx *cctx = NULL; + int fd = -1; + struct stat s_src; + + fd = sss_open_cloexec(src_root, O_RDONLY | O_DIRECTORY, &ret); + if (fd == -1) { + goto fail; + } + + ret = fstat(fd, &s_src); + if (ret == -1) { + ret = errno; + goto fail; + } + + cctx = talloc_zero(NULL, struct copy_ctx); + if (!cctx) { + ret = ENOMEM; + goto fail; + } + + cctx->src_orig = src_root; + cctx->dst_orig = dst_root; + cctx->src_dev = s_src.st_dev; + cctx->uid = uid; + cctx->gid = gid; + + ret = copy_dir(cctx, fd, src_root, AT_FDCWD, + dst_root, dst_root, mode_root, &s_src); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "copy_dir failed: [%d][%s]\n", ret, strerror(ret)); + goto fail; + } + +fail: + if (fd != -1) close(fd); + reset_selinux_file_context(); + talloc_free(cctx); + return ret; +} + +int sss_create_dir(const char *parent_dir_path, + const char *dir_name, + mode_t mode, + uid_t uid, gid_t gid) +{ + TALLOC_CTX *tmp_ctx; + char *dir_path; + int ret = EOK; + int parent_dir_fd = -1; + int dir_fd = -1; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + parent_dir_fd = sss_open_cloexec(parent_dir_path, O_RDONLY | O_DIRECTORY, + &ret); + if (parent_dir_fd == -1) { + DEBUG(SSSDBG_TRACE_FUNC, + "Cannot open() directory '%s' [%d]: %s\n", + parent_dir_path, ret, sss_strerror(ret)); + goto fail; + } + + dir_path = talloc_asprintf(tmp_ctx, "%s/%s", parent_dir_path, dir_name); + if (dir_path == NULL) { + ret = ENOMEM; + goto fail; + } + + errno = 0; + ret = mkdirat(parent_dir_fd, dir_name, mode); + if (ret == -1) { + if (errno == EEXIST) { + ret = EOK; + DEBUG(SSSDBG_TRACE_FUNC, + "Directory '%s' already created!\n", dir_path); + } else { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Error reading '%s': %s\n", parent_dir_path, strerror(ret)); + goto fail; + } + } + + dir_fd = sss_open_cloexec(dir_path, O_RDONLY | O_DIRECTORY, &ret); + if (dir_fd == -1) { + DEBUG(SSSDBG_TRACE_FUNC, + "Cannot open() directory '%s' [%d]: %s\n", + dir_path, ret, sss_strerror(ret)); + goto fail; + } + + errno = 0; + ret = fchown(dir_fd, uid, gid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to own the newly created directory '%s' [%d]: %s\n", + dir_path, ret, sss_strerror(ret)); + goto fail; + } + + ret = EOK; + +fail: + if (parent_dir_fd != -1) { + close(parent_dir_fd); + } + if (dir_fd != -1) { + close(dir_fd); + } + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/util/find_uid.c b/src/util/find_uid.c new file mode 100644 index 0000000..1b506df --- /dev/null +++ b/src/util/find_uid.c @@ -0,0 +1,381 @@ +/* + SSSD + + Create uid table + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdio.h> +#include <sys/types.h> +#include <dirent.h> +#include <errno.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <talloc.h> +#include <ctype.h> +#include <sys/time.h> +#include <dhash.h> + +#include "util/find_uid.h" +#include "util/util.h" +#include "util/strtonum.h" + +#ifdef HAVE_SYSTEMD_LOGIN +#include <systemd/sd-login.h> +#endif + +#define PATHLEN (NAME_MAX + 14) +#define BUFSIZE 4096 + +static void *hash_talloc(const size_t size, void *pvt) +{ + return talloc_size(pvt, size); +} + +static void hash_talloc_free(void *ptr, void *pvt) +{ + talloc_free(ptr); +} + +static errno_t get_uid_from_pid(const pid_t pid, uid_t *uid, bool *is_systemd) +{ + int ret; + char path[PATHLEN]; + struct stat stat_buf; + int fd; + char buf[BUFSIZE]; + char *p; + char *e; + char *endptr; + uint32_t num=0; + errno_t error; + + ret = snprintf(path, PATHLEN, "/proc/%d/status", pid); + if (ret < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "snprintf failed\n"); + return EINVAL; + } else if (ret >= PATHLEN) { + DEBUG(SSSDBG_CRIT_FAILURE, "path too long?!?!\n"); + return EINVAL; + } + + fd = open(path, O_RDONLY); + if (fd == -1) { + error = errno; + if (error == ENOENT) { + DEBUG(SSSDBG_TRACE_LIBS, + "Proc file [%s] is not available anymore.\n", + path); + } else if (error == EPERM) { + /* case of hidepid=1 mount option for /proc */ + DEBUG(SSSDBG_TRACE_LIBS, + "Proc file [%s] is not permissible.\n", + path); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "open failed [%s][%d][%s].\n", path, error, strerror(error)); + } + return error; + } + + ret = fstat(fd, &stat_buf); + if (ret == -1) { + error = errno; + if (error == ENOENT) { + DEBUG(SSSDBG_TRACE_LIBS, + "Proc file [%s] is not available anymore.\n", + path); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "fstat failed [%d][%s].\n", error, strerror(error)); + } + goto fail_fd; + } + + if (!S_ISREG(stat_buf.st_mode)) { + DEBUG(SSSDBG_CRIT_FAILURE, "not a regular file\n"); + error = EINVAL; + goto fail_fd; + } + + errno = 0; + ret = sss_atomic_read_s(fd, buf, BUFSIZE); + if (ret == -1) { + error = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "read failed [%d][%s].\n", error, strerror(error)); + goto fail_fd; + } + + /* Guarantee NULL-termination in case we read the full BUFSIZE somehow */ + buf[BUFSIZE-1] = '\0'; + + ret = close(fd); + if (ret == -1) { + error = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "close failed [%d][%s].\n", error, strerror(error)); + } + + /* Get uid */ + p = strstr(buf, "\nUid:\t"); + if (p != NULL) { + p += 6; + e = strchr(p,'\t'); + if (e == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "missing delimiter.\n"); + return EINVAL; + } else { + *e = '\0'; + } + num = (uint32_t) strtoint32(p, &endptr, 10); + error = errno; + if (error != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "strtol failed [%s].\n", strerror(error)); + return error; + } + if (*endptr != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "uid contains extra characters\n"); + return EINVAL; + } + + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "format error\n"); + return EINVAL; + } + + /* Get process name. */ + p = strstr(buf, "Name:\t"); + if (p == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "format error\n"); + return EINVAL; + } + p += 6; + e = strchr(p,'\n'); + if (e == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "format error\n"); + return EINVAL; + } + if (strncmp(p, "systemd", e-p) == 0 || strncmp(p, "(sd-pam)", e-p) == 0) { + *is_systemd = true; + } else { + *is_systemd = false; + } + + *uid = num; + + return EOK; + +fail_fd: + close(fd); + return error; +} + +static errno_t name_to_pid(const char *name, pid_t *pid) +{ + long num; + char *endptr; + errno_t error; + + errno = 0; + num = strtol(name, &endptr, 10); + error = errno; + if (error == ERANGE) { + perror("strtol"); + return error; + } + + if (*endptr != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "pid string contains extra characters.\n"); + return EINVAL; + } + + if (num <= 0 || num >= INT_MAX) { + DEBUG(SSSDBG_CRIT_FAILURE, "pid out of range.\n"); + return ERANGE; + } + + *pid = num; + + return EOK; +} + +static int only_numbers(char *p) +{ + while(*p!='\0' && isdigit(*p)) ++p; + return *p; +} + +static errno_t get_active_uid_linux(hash_table_t *table, uid_t search_uid) +{ + DIR *proc_dir = NULL; + struct dirent *dirent; + int ret, err; + pid_t pid = -1; + bool is_systemd; + uid_t uid; + + hash_key_t key; + hash_value_t value; + + proc_dir = opendir("/proc"); + if (proc_dir == NULL) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot open proc dir.\n"); + goto done; + }; + + errno = 0; + while ((dirent = readdir(proc_dir)) != NULL) { + if (only_numbers(dirent->d_name) != 0) { + continue; + } + ret = name_to_pid(dirent->d_name, &pid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "name_to_pid failed.\n"); + goto done; + } + + ret = get_uid_from_pid(pid, &uid, &is_systemd); + if (ret != EOK) { + /* Most probably this /proc entry disappeared. + Anyway, just skip it. + */ + DEBUG(SSSDBG_TRACE_ALL, "get_uid_from_pid() failed.\n"); + errno = 0; + continue; + } + + if (is_systemd) { + /* Systemd process may linger for a while even when user. + * is logged out. Lets ignore it and focus only + * on non-systemd processes. */ + continue; + } + + if (table != NULL) { + key.type = HASH_KEY_ULONG; + key.ul = (unsigned long) uid; + value.type = HASH_VALUE_ULONG; + value.ul = (unsigned long) uid; + + ret = hash_enter(table, &key, &value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "cannot add to table [%s]\n", hash_error_string(ret)); + ret = ENOMEM; + goto done; + } + } else { + if (uid == search_uid) { + ret = EOK; + goto done; + } + } + + errno = 0; + } + if (errno != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "readdir failed.\n"); + goto done; + } + + ret = closedir(proc_dir); + proc_dir = NULL; + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "closedir failed, watch out.\n"); + } + + if (table != NULL) { + ret = EOK; + } else { + ret = ENOENT; + } + +done: + if (proc_dir != NULL) { + err = closedir(proc_dir); + if (err) { + DEBUG(SSSDBG_CRIT_FAILURE, "closedir failed, bad dirp?\n"); + } + } + return ret; +} + +errno_t get_uid_table(TALLOC_CTX *mem_ctx, hash_table_t **table) +{ +#ifdef __linux__ + int ret; + + ret = hash_create_ex(0, table, 0, 0, 0, 0, + hash_talloc, hash_talloc_free, mem_ctx, + NULL, NULL); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "hash_create_ex failed [%s]\n", hash_error_string(ret)); + return ENOMEM; + } + + return get_active_uid_linux(*table, 0); +#else + return ENOSYS; +#endif +} + +errno_t check_if_uid_is_active(uid_t uid, bool *result) +{ + int ret; + +#ifdef HAVE_SYSTEMD_LOGIN + ret = sd_uid_get_sessions(uid, 0, NULL); + if (ret > 0) { + *result = true; + return EOK; + } + if (ret == 0) { + *result = false; + } + if (ret < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "systemd-login gave error %d: %s\n", + -ret, strerror(-ret)); + } + /* fall back to the old method */ +#endif + + ret = get_active_uid_linux(NULL, uid); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "get_active_uid_linux() failed.\n"); + return ret; + } + + if (ret == EOK) { + *result = true; + } else { + *result = false; + } + + return EOK; +} diff --git a/src/util/find_uid.h b/src/util/find_uid.h new file mode 100644 index 0000000..e01b3fc --- /dev/null +++ b/src/util/find_uid.h @@ -0,0 +1,36 @@ +/* + SSSD + + Create uid table + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef __FIND_UID_H__ +#define __FIND_UID_H__ + +#include <talloc.h> +#include <sys/types.h> +#include <dhash.h> + +#include "util/util.h" + +errno_t get_uid_table(TALLOC_CTX *mem_ctx, hash_table_t **table); +errno_t check_if_uid_is_active(uid_t uid, bool *result); + +#endif /* __FIND_UID_H__ */ diff --git a/src/util/inotify.c b/src/util/inotify.c new file mode 100644 index 0000000..a3c33ed --- /dev/null +++ b/src/util/inotify.c @@ -0,0 +1,609 @@ +/* + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <talloc.h> +#include <errno.h> +#include <libgen.h> +#include <limits.h> +#include <sys/inotify.h> +#include <sys/time.h> + +#include "util/inotify.h" +#include "util/util.h" + +/* For parent directories, we want to know if a file was moved there or + * created there + */ +#define PARENT_DIR_MASK (IN_CREATE | IN_MOVED_TO) + +/* This structure is recreated if we need to rewatch the file and/or + * directory + */ +struct snotify_watch_ctx { + int inotify_fd; /* The inotify_fd */ + struct tevent_fd *tfd; /* Activity on the fd */ + + struct snotify_ctx *snctx; /* Pointer up to the main snotify struct */ + + /* In case we're also watching the parent directory, otherwise -1. + * We keep the variable here and not in snctx so that we're able + * to catch even changes to the parent directory + */ + int dir_wd; + /* The file watch */ + int file_wd; +}; + +/* This is what we call when an event we're interested in arrives */ +struct snotify_cb_ctx { + snotify_cb_fn fn; + const char *fn_name; + uint32_t mask; + void *pvt; +}; + +/* One instance of a callback. We hoard the inotify notifications + * until timer fires in caught_flags + */ +struct snotify_dispatcher { + struct tevent_timer *te; + uint32_t caught_flags; +}; + +struct snotify_ctx { + struct tevent_context *ev; + + /* The full path of the file we're watching, + * its file and directory components */ + const char *filename; + const char *dir_name; + const char *base_name; + + /* Private pointer passed to the callback */ + struct snotify_cb_ctx cb; + /* A singleton callback dispatcher */ + struct snotify_dispatcher *disp; + + /* Internal snotify flags */ + uint16_t snotify_flags; + /* The caller might decide to batch the updates and receive + * them all together with a delay + */ + struct timeval delay; + /* We keep the structure that actually does the work + * separately to be able to reinitialize it when the + * file is recreated or moved to the directory + */ + struct snotify_watch_ctx *wctx; +}; + +struct flg2str { + uint32_t flg; + const char *str; +} flg_table[] = { + { 0x00000001, "IN_ACCESS" }, + { 0x00000002, "IN_MODIFY" }, + { 0x00000004, "IN_ATTRIB" }, + { 0x00000008, "IN_CLOSE_WRITE" }, + { 0x00000010, "IN_CLOSE_NOWRITE" }, + { 0x00000020, "IN_OPEN" }, + { 0x00000040, "IN_MOVED_FROM" }, + { 0x00000080, "IN_MOVED_TO" }, + { 0x00000100, "IN_CREATE" }, + { 0x00000200, "IN_DELETE" }, + { 0x00000400, "IN_DELETE_SELF" }, + { 0x00000800, "IN_MOVE_SELF" }, + { 0x00002000, "IN_UNMOUNT" }, + { 0x00004000, "IN_Q_OVERFLOW" }, + { 0x00008000, "IN_IGNORED" }, + { 0x01000000, "IN_ONLYDIR" }, + { 0x02000000, "IN_DONT_FOLLOW" }, + { 0x04000000, "IN_EXCL_UNLINK" }, + { 0x20000000, "IN_MASK_ADD" }, + { 0x40000000, "IN_ISDIR" }, + { 0x80000000, "IN_ONESHOT" }, + { 0, NULL }, +}; + +#if 0 +static void debug_flags(uint32_t flags, const char *file) +{ + char msgbuf[1024]; + size_t total = 0; + + if (!DEBUG_IS_SET(SSSDBG_TRACE_LIBS)) { + return; + } + + for (int i = 0; flg_table[i].flg != 0; i++) { + if (flags & flg_table[i].flg) { + total += snprintf(msgbuf+total, + sizeof(msgbuf)-total, + "%s ", flg_table[i].str); + } + } + + if (total == 0) { + snprintf(msgbuf, sizeof(msgbuf), "NONE\n"); + } + DEBUG(SSSDBG_TRACE_LIBS, "Inotify event: %s on %s\n", msgbuf, file); +} +#endif + +static void snotify_process_callbacks(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, + void *ptr) +{ + struct snotify_ctx *snctx; + + snctx = talloc_get_type(ptr, struct snotify_ctx); + if (snctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Bad pointer\n"); + return; + } + + snctx->cb.fn(snctx->filename, + snctx->disp->caught_flags, + snctx->cb.pvt); + + talloc_zfree(snctx->disp); +} + +static struct snotify_dispatcher *create_dispatcher(struct snotify_ctx *snctx) +{ + struct snotify_dispatcher *disp; + struct timeval tv; + + disp = talloc_zero(snctx, struct snotify_dispatcher); + if (disp == NULL) { + return NULL; + } + + gettimeofday(&tv, NULL); + tv.tv_sec += snctx->delay.tv_sec; + tv.tv_usec += snctx->delay.tv_usec; + + DEBUG(SSSDBG_TRACE_FUNC, + "Running a timer with delay %ld.%ld\n", + (unsigned long) snctx->delay.tv_sec, + (unsigned long) snctx->delay.tv_usec); + + disp->te = tevent_add_timer(snctx->ev, disp, tv, + snotify_process_callbacks, + snctx); + if (disp->te == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to queue file update!\n"); + talloc_free(disp); + return NULL; + } + + return disp; +} + +static struct snotify_dispatcher *get_dispatcher(struct snotify_ctx *snctx) +{ + if (snctx->disp != NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Reusing existing dispatcher\n"); + return snctx->disp; + } + + return create_dispatcher(snctx); +} + +static errno_t dispatch_event(struct snotify_ctx *snctx, + uint32_t ev_flags) +{ + struct snotify_dispatcher *disp; + + if ((snctx->cb.mask & ev_flags) == 0) { + return EOK; + } + + disp = get_dispatcher(snctx); + if (disp == NULL) { + return ENOMEM; + } + + disp->caught_flags |= ev_flags; + DEBUG(SSSDBG_TRACE_FUNC, + "Dispatched an event with combined flags 0x%X\n", + disp->caught_flags); + + snctx->disp = disp; + return EOK; +} + +static errno_t process_dir_event(struct snotify_ctx *snctx, + const struct inotify_event *in_event) +{ + errno_t ret; + + DEBUG(SSSDBG_TRACE_ALL, "inotify name: %s\n", in_event->name); + if (in_event->len == 0 \ + || strcmp(in_event->name, snctx->base_name) != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Not interested in %s\n", in_event->name); + return EOK; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "received notification for watched file [%s] under %s\n", + in_event->name, snctx->dir_name); + + /* file the event for the file to see if the caller is interested in it */ + ret = dispatch_event(snctx, in_event->mask); + if (ret == EOK) { + /* Tells the outer loop to re-initialize flags once the loop is finished. + * However, finish reading all the events first to make sure we don't + * miss any + */ + return EAGAIN; + } + + return ret; +} + +static errno_t process_file_event(struct snotify_ctx *snctx, + const struct inotify_event *in_event) +{ + if (in_event->mask & IN_IGNORED) { + DEBUG(SSSDBG_TRACE_FUNC, + "Will reopen moved or deleted file %s\n", snctx->filename); + /* Notify caller of the event, don't quit */ + return EAGAIN; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "received notification for watched file %s\n", snctx->filename); + + return dispatch_event(snctx, in_event->mask); +} + +static errno_t snotify_rewatch(struct snotify_ctx *snctx); + +static void snotify_internal_cb(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *data) +{ + char ev_buf[sizeof(struct inotify_event) + PATH_MAX]; + const char *ptr; + const struct inotify_event *in_event; + struct snotify_ctx *snctx; + ssize_t len; + errno_t ret; + bool rewatch = false; + + snctx = talloc_get_type(data, struct snotify_ctx); + if (snctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Bad pointer\n"); + return; + } + + while (1) { + len = read(snctx->wctx->inotify_fd, ev_buf, sizeof(ev_buf)); + if (len == -1) { + ret = errno; + if (ret != EAGAIN) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot read inotify_event [%d]: %s\n", + ret, strerror(ret)); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, "All inotify events processed\n"); + } + break; + } + + if ((size_t) len < sizeof(struct inotify_event)) { + /* Did not even read the required amount of data, move on.. */ + continue; + } + + for (ptr = ev_buf; + ptr < ev_buf + len; + ptr += sizeof(struct inotify_event) + in_event->len) { + + in_event = (const struct inotify_event *) ptr; + +#if 0 + debug_flags(in_event->mask, in_event->name); +#endif + + if (snctx->wctx->dir_wd == in_event->wd) { + ret = process_dir_event(snctx, in_event); + } else if (snctx->wctx->file_wd == in_event->wd) { + ret = process_file_event(snctx, in_event); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unknown watch %d\n", in_event->wd); + ret = EOK; + } + + if (ret == EAGAIN) { + rewatch = true; + /* Continue with the loop and read all the events from + * this descriptor first, then rewatch when done + */ + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to process inotify event\n"); + } + } + } + + if (rewatch) { + ret = snotify_rewatch(snctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to re-set watch"); + } + } +} + +static int watch_ctx_destructor(void *memptr) +{ + struct snotify_watch_ctx *wctx; + + wctx = talloc_get_type(memptr, struct snotify_watch_ctx); + if (wctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Bad pointer\n"); + return 1; + } + + /* We don't need to close the watches explicitly. man 7 inotify says: + * When all file descriptors referring to an inotify instance + * have been closed (using close(2)), the underlying object + * and its resources are freed for reuse by the kernel; all + * associated watches are automatically freed. + */ + if (wctx->inotify_fd != -1) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Closing inotify fd %d\n", wctx->inotify_fd); + close(wctx->inotify_fd); + } + + return 0; +} + +static errno_t resolve_filename(struct snotify_ctx *snctx, + const char *filename, + char *resolved, + size_t resolved_size) +{ + /* NOTE: The code below relies in the GNU extensions for realpath, + * which will store in 'resolved' the prefix of 'filename' that does + * not exists if realpath call fails and errno is set to ENOENT */ + if (realpath(filename, resolved) == NULL) { + char fcopy[PATH_MAX + 1]; + char *p; + struct stat st; + + if (errno != ENOENT) { + return errno; + } + + /* Check if the unique missing component is the basename. The + * dirname must exist to be notified watching the parent dir. */ + strncpy(fcopy, filename, sizeof(fcopy) - 1); + fcopy[PATH_MAX] = '\0'; + + p = dirname(fcopy); + if (p == NULL) { + return EIO; + } + + if (stat(p, &st) == -1) { + return errno; + } + + /* The basedir exist, check the caller requested to watch it. + * Otherwise return error as never will be notified. */ + + if ((snctx->snotify_flags & SNOTIFY_WATCH_DIR) == 0) { + return ENOENT; + } + } + + return EOK; +} + +static errno_t copy_filenames(struct snotify_ctx *snctx, + const char *filename) +{ + char *p; + char resolved[PATH_MAX + 1]; + char fcopy[PATH_MAX + 1]; + errno_t ret; + + ret = resolve_filename(snctx, filename, resolved, sizeof(resolved)); + if (ret != EOK) { + return ret; + } + + strncpy(fcopy, resolved, sizeof(fcopy) - 1); + fcopy[PATH_MAX] = '\0'; + + p = dirname(fcopy); + if (p == NULL) { + return EIO; + } + + snctx->dir_name = talloc_strdup(snctx, p); + if (snctx->dir_name == NULL) { + return ENOMEM; + } + + strncpy(fcopy, resolved, sizeof(fcopy) - 1); + fcopy[PATH_MAX] = '\0'; + + p = basename(fcopy); + if (p == NULL) { + return EIO; + } + + snctx->base_name = talloc_strdup(snctx, p); + if (snctx->base_name == NULL) { + return ENOMEM; + } + + snctx->filename = talloc_strdup(snctx, resolved); + if (snctx->filename == NULL) { + return ENOMEM; + } + + return EOK; +} + +static struct snotify_watch_ctx *snotify_watch(struct snotify_ctx *snctx, + uint32_t mask) +{ + struct snotify_watch_ctx *wctx; + errno_t ret; + + wctx = talloc_zero(snctx, struct snotify_watch_ctx); + if (wctx == NULL) { + return NULL; + } + wctx->inotify_fd = -1; + wctx->dir_wd = -1; + wctx->file_wd = -1; + wctx->snctx = snctx; + talloc_set_destructor((TALLOC_CTX *)wctx, watch_ctx_destructor); + + wctx->inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + if (wctx->inotify_fd == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "inotify_init1 failed: %d: %s\n", ret, strerror(ret)); + goto fail; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Opened inotify fd %d\n", wctx->inotify_fd); + + wctx->tfd = tevent_add_fd(snctx->ev, wctx, wctx->inotify_fd, + TEVENT_FD_READ, snotify_internal_cb, + snctx); + if (wctx->tfd == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot add tevent fd watch for %s\n", + snctx->filename); + goto fail; + } + + wctx->file_wd = inotify_add_watch(wctx->inotify_fd, snctx->filename, mask); + if (wctx->file_wd == -1) { + ret = errno; + if (ret != ENOENT || (!(snctx->snotify_flags & SNOTIFY_WATCH_DIR))) { + DEBUG(SSSDBG_MINOR_FAILURE, + "inotify_add_watch failed [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Opened file watch %d\n", wctx->file_wd); + + if (snctx->snotify_flags & SNOTIFY_WATCH_DIR) { + /* Create a watch for the parent directory. This is useful for cases + * where we start watching a file before it's created, but still want + * a notification when the file is moved in + */ + wctx->dir_wd = inotify_add_watch(wctx->inotify_fd, + snctx->dir_name, PARENT_DIR_MASK); + if (wctx->dir_wd == -1) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "inotify_add_watch failed [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + DEBUG(SSSDBG_TRACE_INTERNAL, + "Opened directory watch %d\n", wctx->dir_wd); + } + + return wctx; + +fail: + talloc_free(wctx); + return NULL; +} + +static errno_t snotify_rewatch(struct snotify_ctx *snctx) +{ + talloc_free(snctx->wctx); + + snctx->wctx = snotify_watch(snctx, snctx->cb.mask); + if (snctx->wctx == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Recreated watch\n"); + return EOK; +} + +struct snotify_ctx *_snotify_create(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint16_t snotify_flags, + const char *filename, + struct timeval *delay, + uint32_t mask, + snotify_cb_fn fn, + const char *fn_name, + void *pvt) +{ + errno_t ret; + struct snotify_ctx *snctx; + + snctx = talloc_zero(mem_ctx, struct snotify_ctx); + if (snctx == NULL) { + return NULL; + } + + snctx->ev = ev; + snctx->snotify_flags = snotify_flags; + if (delay) { + snctx->delay.tv_sec = delay->tv_sec; + snctx->delay.tv_usec = delay->tv_usec; + } + + snctx->cb.fn = fn; + snctx->cb.fn_name = fn_name; + snctx->cb.mask = mask; + snctx->cb.pvt = pvt; + + ret = copy_filenames(snctx, filename); + if (ret != EOK) { + talloc_free(snctx); + return NULL; + } + + snctx->wctx = snotify_watch(snctx, mask); + if (snctx->wctx == NULL) { + talloc_free(snctx); + return NULL; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Added a watch for %s with inotify flags 0x%X " + "internal flags 0x%X " + "using function %s after delay %ld.%ld\n", + snctx->filename, + mask, + snotify_flags, + fn_name, + (unsigned long) snctx->delay.tv_sec, + (unsigned long) snctx->delay.tv_usec); + + return snctx; +} diff --git a/src/util/inotify.h b/src/util/inotify.h new file mode 100644 index 0000000..3592944 --- /dev/null +++ b/src/util/inotify.h @@ -0,0 +1,61 @@ +/* + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __INOTIFY_H_ +#define __INOTIFY_H_ + +#include <talloc.h> +#include <tevent.h> +#include <sys/inotify.h> + + +typedef int (*snotify_cb_fn)(const char *filename, + uint32_t caught_flags, + void *pvt); + +#define SNOTIFY_WATCH_DIR 0x0001 + +/* + * Set up an inotify watch for file at filename. When an inotify + * event is caught, it must match the "mask" parameter. The watch + * would then call snotify_cb_fn() and include the caught flags. + * + * If snotify_flags includes SNOTIFY_WATCH_DIR, also the parent directory + * of this file would be watched to cover cases where the file might not + * exist when the watch is created. + * + * If you wish to batch inotify requests to avoid hammering the caller + * with several successive requests, use the delay parameter. The function + * would then only send invoke the callback after the delay and the caught + * flags would be OR-ed. By default, the callback is invoked immediately. + * + * Use the pvt parameter to pass a private context to the function + */ +struct snotify_ctx *_snotify_create(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint16_t snotify_flags, + const char *filename, + struct timeval *delay, + uint32_t mask, + snotify_cb_fn fn, + const char *fn_name, + void *pvt); + +#define snotify_create(mem_ctx, ev, snotify_flags, filename, delay, mask, fn, pvt) \ + _snotify_create(mem_ctx, ev, snotify_flags, filename, delay, mask, fn, #fn, pvt); + +#endif /* __INOTIFY_H_ */ diff --git a/src/util/io.c b/src/util/io.c new file mode 100644 index 0000000..4d442b4 --- /dev/null +++ b/src/util/io.c @@ -0,0 +1,98 @@ +/* + SSSD + + io.c + + Authors: + Lukas Slebodnik <lslebodn@redhat.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include "shared/io.h" + +/* CAUTION: + * This file have to be minimalist and cannot include DEBUG macros + * or header file util.h. + */ + +int sss_open_cloexec(const char *pathname, int flags, int *ret) +{ + int fd; + int oflags; + + oflags = flags; +#ifdef O_CLOEXEC + oflags |= O_CLOEXEC; +#endif + + errno = 0; + fd = open(pathname, oflags); + if (fd == -1) { + if (ret) { + *ret = errno; + } + return -1; + } + +#ifndef O_CLOEXEC + int v; + + v = fcntl(fd, F_GETFD, 0); + /* we ignore an error, it's not fatal and there is nothing we + * can do about it anyways */ + (void)fcntl(fd, F_SETFD, v | FD_CLOEXEC); +#endif + + return fd; +} + +int sss_openat_cloexec(int dir_fd, const char *pathname, int flags, int *ret) +{ + int fd; + int oflags; + + oflags = flags; +#ifdef O_CLOEXEC + oflags |= O_CLOEXEC; +#endif + + errno = 0; + fd = openat(dir_fd, pathname, oflags); + if (fd == -1) { + if (ret) { + *ret = errno; + } + return -1; + } + +#ifndef O_CLOEXEC + int v; + + v = fcntl(fd, F_GETFD, 0); + /* we ignore an error, it's not fatal and there is nothing we + * can do about it anyways */ + (void)fcntl(fd, F_SETFD, v | FD_CLOEXEC); +#endif + + return fd; +} diff --git a/src/util/memory.c b/src/util/memory.c new file mode 100644 index 0000000..4fb8cfa --- /dev/null +++ b/src/util/memory.c @@ -0,0 +1,97 @@ +/* + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <talloc.h> + +#include "util/util.h" + + +#ifdef HAVE_EXPLICIT_BZERO + +#include <string.h> + +#else + +typedef void *(*_sss_memset_t)(void *, int, size_t); + +static volatile _sss_memset_t memset_func = memset; + +static void explicit_bzero(void *s, size_t n) +{ + memset_func(s, 0, n); +} + +#endif + + +int sss_erase_talloc_mem_securely(void *p) +{ + if (p == NULL) { + return 0; + } + + size_t size = talloc_get_size(p); + if (size == 0) { + return 0; + } + + explicit_bzero(p, size); + + return 0; +} + +void sss_erase_mem_securely(void *p, size_t size) +{ + if ((p == NULL) || (size == 0)) { + return; + } + + explicit_bzero(p, size); +} + + +struct mem_holder { + void *mem; + void_destructor_fn_t *fn; +}; + +static int mem_holder_destructor(void *ptr) +{ + struct mem_holder *h; + + h = talloc_get_type(ptr, struct mem_holder); + return h->fn(h->mem); +} + +int sss_mem_attach(TALLOC_CTX *mem_ctx, void *ptr, void_destructor_fn_t *fn) +{ + struct mem_holder *h; + + if (!ptr || !fn) return EINVAL; + + h = talloc(mem_ctx, struct mem_holder); + if (!h) return ENOMEM; + + h->mem = ptr; + h->fn = fn; + talloc_set_destructor((TALLOC_CTX *)h, mem_holder_destructor); + + return EOK; +} diff --git a/src/util/mmap_cache.h b/src/util/mmap_cache.h new file mode 100644 index 0000000..3f2b6c2 --- /dev/null +++ b/src/util/mmap_cache.h @@ -0,0 +1,161 @@ +/* + SSSD + + Mmap Cache Common header + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _MMAP_CACHE_H_ +#define _MMAP_CACHE_H_ + +#include "shared/murmurhash3.h" + + +/* NOTE: all the code here assumes that writing a uint32_t nto mmapped + * memory is an atomic operation and can't be split in multiple + * non-atomic operations */ +typedef uint32_t rel_ptr_t; + +/* align macros */ +#define MC_8 sizeof(uint8_t) +#define MC_32 sizeof(uint32_t) +#define MC_64 sizeof(uint64_t) +#define MC_ALIGN32(size) ( ((size) + MC_32 -1) & (~(MC_32 -1)) ) +#define MC_ALIGN64(size) ( ((size) + MC_64 -1) & (~(MC_64 -1)) ) +#define MC_HEADER_SIZE MC_ALIGN64(sizeof(struct sss_mc_header)) + +#define MC_HT_SIZE(elems) ( (elems) * MC_32 ) +#define MC_HT_ELEMS(size) ( (size) / MC_32 ) + +#define MC_PTR_ADD(ptr, bytes) (void *)((uint8_t *)(ptr) + (bytes)) +#define MC_PTR_DIFF(ptr, base) ((uint8_t *)(ptr) - (uint8_t *)(base)) + +#define MC_INVALID_VAL64 ((uint64_t)-1) +#define MC_INVALID_VAL32 ((uint32_t)-1) +#define MC_INVALID_VAL8 ((uint8_t)-1) +#define MC_INVALID_VAL MC_INVALID_VAL32 + +/* + * 40 seem a good compromise for slot size + * 4 blocks are enough for the average passwd entry of 42 bytes + * passwd records have 84 bytes of overhead, 160 - 82 = 78 bytes + * 3 blocks can contain a very minimal entry, 120 - 82 = 38 bytes + * + * 3 blocks are enough for groups w/o users (private user groups) + * group records have 68 bytes of overhead, 120 - 66 = 54 bytes + */ +#define MC_SLOT_SIZE 40 +#define MC_SIZE_TO_SLOTS(len) (((len) + (MC_SLOT_SIZE - 1)) / MC_SLOT_SIZE) +#define MC_PTR_TO_SLOT(base, ptr) (MC_PTR_DIFF(ptr, base) / MC_SLOT_SIZE) +#define MC_SLOT_TO_PTR(base, slot, type) \ + (type *)((base) + ((slot) * MC_SLOT_SIZE)) + +#define MC_SLOT_WITHIN_BOUNDS(slot, dt_size) \ + ((slot) < ((dt_size) / MC_SLOT_SIZE)) + +#define MC_VALID_BARRIER(val) (((val) & 0xff000000) == 0xf0000000) + +#define MC_CHECK_RECORD_LENGTH(mc_ctx, rec) \ + ((rec)->len >= MC_HEADER_SIZE && (rec)->len != MC_INVALID_VAL32 \ + && ((rec)->len <= ((mc_ctx)->dt_size \ + - MC_PTR_DIFF(rec, (mc_ctx)->data_table)))) + + +#define SSS_MC_MAJOR_VNO 1 +#define SSS_MC_MINOR_VNO 1 + +#define SSS_MC_HEADER_UNINIT 0 /* after ftruncate or before reset */ +#define SSS_MC_HEADER_ALIVE 1 /* current and in use */ +#define SSS_MC_HEADER_RECYCLED 2 /* file was recycled, reopen asap */ + +#pragma pack(1) +struct sss_mc_header { + uint32_t b1; /* barrier 1 */ + uint32_t major_vno; /* major version number */ + uint32_t minor_vno; /* minor version number */ + uint32_t status; /* database status */ + uint32_t seed; /* random seed used to avoid collision attacks */ + uint32_t dt_size; /* data table size */ + uint32_t ft_size; /* free table size */ + uint32_t ht_size; /* hash table size */ + rel_ptr_t data_table; /* data table pointer relative to mmap base */ + rel_ptr_t free_table; /* free table pointer relative to mmap base */ + rel_ptr_t hash_table; /* hash table pointer relative to mmap base */ + rel_ptr_t reserved; /* reserved for future changes */ + uint32_t b2; /* barrier 2 */ +}; + +struct sss_mc_rec { + uint32_t b1; /* barrier 1 */ + uint32_t len; /* total record length including record data */ + uint64_t expire; /* record expiration time (cast to time_t) */ + rel_ptr_t next1; /* ptr of next record rel to data_table */ + /* next1 is related to hash1 */ + rel_ptr_t next2; /* ptr of next record rel to data_table */ + /* next2 is related to hash2 */ + uint32_t hash1; /* val of first hash (usually name of record) */ + uint32_t hash2; /* val of second hash (usually id of record) */ + uint32_t padding; /* padding & reserved for future changes */ + uint32_t b2; /* barrier 2 - 32 bytes mark, fits a slot */ + char data[0]; +}; + +struct sss_mc_pwd_data { + rel_ptr_t name; /* ptr to name string, rel. to struct base addr */ + uint32_t uid; + uint32_t gid; + uint32_t strs_len; /* length of strs */ + char strs[0]; /* concatenation of all passwd strings, each + * string is zero terminated ordered as follows: + * name, passwd, gecos, dir, shell */ +}; + +struct sss_mc_grp_data { + rel_ptr_t name; /* ptr to name string, rel. to struct base addr */ + uint32_t gid; + uint32_t members; /* number of members in strs */ + uint32_t strs_len; /* length of strs */ + char strs[0]; /* concatenation of all group strings, each + * string is zero terminated ordered as follows: + * name, passwd, member1, member2, ... */ +}; + +struct sss_mc_initgr_data { + rel_ptr_t unique_name; /* ptr to unique name string, rel. to struct base addr */ + rel_ptr_t name; /* ptr to raw name string, rel. to struct base addr */ + rel_ptr_t strs; /* ptr to concatenation of all strings */ + uint32_t strs_len; /* length of strs */ + uint32_t data_len; /* all initgroups data len */ + uint32_t num_groups; /* number of groups */ + uint32_t gids[0]; /* array of all groups + * string with name and unique_name is stored + * after gids */ +}; + +struct sss_mc_sid_data { + rel_ptr_t name; /* ptr to SID string, rel. to struct base addr */ + uint32_t type; /* enum sss_id_type */ + uint32_t id; /* gid or uid */ + uint32_t populated_by; /* 0 - by_id(), 1 - by_uid/gid() lookup */ + uint32_t sid_len; /* length of sid */ + char sid[0]; +}; + +#pragma pack() + + +#endif /* _MMAP_CACHE_H_ */ diff --git a/src/util/murmurhash3.c b/src/util/murmurhash3.c new file mode 100644 index 0000000..f8db9d2 --- /dev/null +++ b/src/util/murmurhash3.c @@ -0,0 +1,116 @@ +/* This file is based on the public domain MurmurHash3 from Austin Appleby: + * http://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp + * + * We use only the 32 bit variant because the 2 produce different result while + * we need to produce the same result regardless of the architecture as + * clients can be both 64 or 32 bit at the same time. + */ + +#include <stdlib.h> +#include <stdint.h> +#include <string.h> + +#include "config.h" +#include "shared/murmurhash3.h" +#include "util/sss_endian.h" + +static uint32_t rotl(uint32_t x, int8_t r) +{ + return (x << r) | (x >> (32 - r)); +} + +/* slower than original but is endian neutral and handles platforms that + * do only aligned reads */ +__attribute__((always_inline)) +static inline uint32_t getblock(const uint8_t *p, int i) +{ + uint32_t r; + size_t size = sizeof(uint32_t); + + memcpy(&r, &p[i * size], size); + + return le32toh(r); +} + +/* + * Finalization mix - force all bits of a hash block to avalanche + */ + +__attribute__((always_inline)) +static inline uint32_t fmix(uint32_t h) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; +} + + +uint32_t murmurhash3(const char *key, int len, uint32_t seed) +{ + const uint8_t *blocks; + const uint8_t *tail; + int nblocks; + uint32_t h1; + uint32_t k1; + uint32_t c1; + uint32_t c2; + int i; + + blocks = (const uint8_t *)key; + nblocks = len / 4; + h1 = seed; + c1 = 0xcc9e2d51; + c2 = 0x1b873593; + + /* body */ + + for (i = 0; i < nblocks; i++) { + + k1 = getblock(blocks, i); + + k1 *= c1; + k1 = rotl(k1, 15); + k1 *= c2; + + h1 ^= k1; + h1 = rotl(h1, 13); + h1 = h1 * 5 + 0xe6546b64; + } + + /* tail */ + + tail = (const uint8_t *)key + nblocks * 4; + + k1 = 0; + + switch (len & 3) { + case 3: + k1 ^= tail[2] << 16; + SSS_ATTRIBUTE_FALLTHROUGH; + case 2: + k1 ^= tail[1] << 8; + SSS_ATTRIBUTE_FALLTHROUGH; + case 1: + k1 ^= tail[0]; + k1 *= c1; + k1 = rotl(k1, 15); + k1 *= c2; + h1 ^= k1; + break; + default: + break; + } + + /* finalization */ + + h1 ^= len; + h1 = fmix(h1); + + return h1; +} + + diff --git a/src/util/nscd.c b/src/util/nscd.c new file mode 100644 index 0000000..47a1c02 --- /dev/null +++ b/src/util/nscd.c @@ -0,0 +1,146 @@ +/* + SSSD + + nscd.c + + Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <ctype.h> + +#include "util/util.h" + + +/* NSCD config file parse and check */ +static unsigned int sss_nscd_check_service(char* svc_name) +{ + struct sss_nscd_db { + const char *svc_type_name; + unsigned int nscd_service_flag; + }; + + int i; + unsigned int ret = 0; + struct sss_nscd_db db[] = { + { "passwd", 0x0001 }, + { "group", 0x0010 }, + { "netgroup", 0x0100 }, + { "services", 0x1000 }, + { NULL, 0 } + }; + + if (svc_name == NULL) { + return ret; + } + + for (i = 0; db[i].svc_type_name != NULL; i++) { + if (!strcmp(db[i].svc_type_name, svc_name)) { + + ret = db[i].nscd_service_flag; + break; + } + } + + return ret; +} + +errno_t sss_nscd_parse_conf(const char *conf_path) +{ + FILE *fp; + int ret = EOK; + unsigned int occurred = 0; + char *line, *entry, *service, *enabled, *pad; + size_t linelen = 0; + + fp = fopen(conf_path, "r"); + if (fp == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Couldn't open NSCD configuration " + "file [%s]\n", conf_path); + return ENOENT; + } + + while (getline(&line, &linelen, fp) != -1) { + + pad = strchr(line, '#'); + if (pad != NULL) { + *pad = '\0'; + } + + if (line[0] == '\n' || line[0] == '\0') continue; + + entry = line; + while (isspace(*entry) && *entry != '\0') { + entry++; + } + + pad = entry; + while (!isspace(*pad) && *pad != '\0') { + pad++; + } + + service = pad; + while (isspace(*service) && *service != '\0') { + service++; + } + + *pad = '\0'; + pad = service; + while (!isspace(*pad) && *pad != '\0') { + pad++; + } + + enabled = pad; + while (isspace(*enabled) && *enabled != '\0') { + enabled++; + } + + *pad = '\0'; + pad = enabled; + while (!isspace(*pad) && *pad != '\0') { + pad++; + } + *pad = '\0'; + + if (!strcmp(entry, "enable-cache") && + !strcmp(enabled, "yes")) { + + occurred |= sss_nscd_check_service(service); + } + }; + + ret = ferror(fp); + if (ret) { + DEBUG(SSSDBG_MINOR_FAILURE, "Reading NSCD configuration file [%s] " + "ended with failure [%d]: %s.\n", + conf_path, ret, strerror(ret)); + ret = ENOENT; + goto done; + } + + ret = EOK; + if (occurred != 0) { + ret = EEXIST; + goto done; + } + +done: + free(line); + fclose(fp); + + return ret; +} diff --git a/src/util/nss_dl_load.c b/src/util/nss_dl_load.c new file mode 100644 index 0000000..4421083 --- /dev/null +++ b/src/util/nss_dl_load.c @@ -0,0 +1,98 @@ +/* + Copyright (C) 2019 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#include <dlfcn.h> +#include <talloc.h> +#include <stdbool.h> +#include <errno.h> + +#include "util/util_errors.h" +#include "util/debug.h" +#include "nss_dl_load.h" + + +#define NSS_FN_NAME "_nss_%s_%s" + + +static void *proxy_dlsym(void *handle, + const char *name, + const char *libname) +{ + char *funcname; + void *funcptr; + + funcname = talloc_asprintf(NULL, NSS_FN_NAME, libname, name); + if (funcname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + return NULL; + } + + funcptr = dlsym(handle, funcname); + talloc_free(funcname); + + return funcptr; +} + + +errno_t sss_load_nss_symbols(struct sss_nss_ops *ops, const char *libname, + struct sss_nss_symbols *syms, size_t nsyms) +{ + errno_t ret; + char *libpath; + size_t i; + + libpath = talloc_asprintf(NULL, "libnss_%s.so.2", libname); + if (libpath == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + return ENOMEM; + } + + ops->dl_handle = dlopen(libpath, RTLD_NOW); + if (ops->dl_handle == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to load %s module, " + "error: %s\n", libpath, dlerror()); + ret = ELIBACC; + goto out; + } + + for (i = 0; i < nsyms; i++) { + *(syms[i].fptr) = proxy_dlsym(ops->dl_handle, syms[i].fname, + libname); + + if (*(syms[i].fptr) == NULL) { + if (syms[i].mandatory) { + DEBUG(SSSDBG_FATAL_FAILURE, "Library '%s' did not provide " + "mandatory symbol '%s', error: %s.\n", libpath, + syms[i].fname, dlerror()); + ret = ELIBBAD; + goto out; + } else { + DEBUG(SSSDBG_OP_FAILURE, "Library '%s' did not provide " + "optional symbol '%s', error: %s.\n", libpath, + syms[i].fname, dlerror()); + } + } + } + + ret = EOK; + +out: + talloc_free(libpath); + + return ret; +} diff --git a/src/util/nss_dl_load.h b/src/util/nss_dl_load.h new file mode 100644 index 0000000..f1e882b --- /dev/null +++ b/src/util/nss_dl_load.h @@ -0,0 +1,122 @@ +/* + Copyright (C) 2019 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __SSSD_NSS_DL_LOAD_H__ +#define __SSSD_NSS_DL_LOAD_H__ + + +#include <nss.h> +#include <pwd.h> +#include <grp.h> +#include <netdb.h> +#include "util/util_errors.h" +#include "sss_client/nss_compat.h" + + +struct sss_nss_ops { + enum nss_status (*getpwnam_r)(const char *name, struct passwd *result, + char *buffer, size_t buflen, int *errnop); + enum nss_status (*getpwuid_r)(uid_t uid, struct passwd *result, + char *buffer, size_t buflen, int *errnop); + enum nss_status (*setpwent)(void); + enum nss_status (*getpwent_r)(struct passwd *result, + char *buffer, size_t buflen, int *errnop); + enum nss_status (*endpwent)(void); + + enum nss_status (*getgrnam_r)(const char *name, struct group *result, + char *buffer, size_t buflen, int *errnop); + enum nss_status (*getgrgid_r)(gid_t gid, struct group *result, + char *buffer, size_t buflen, int *errnop); + enum nss_status (*setgrent)(void); + enum nss_status (*getgrent_r)(struct group *result, + char *buffer, size_t buflen, int *errnop); + enum nss_status (*endgrent)(void); + enum nss_status (*initgroups_dyn)(const char *user, gid_t group, + long int *start, long int *size, + gid_t **groups, long int limit, + int *errnop); + enum nss_status (*setnetgrent)(const char *netgroup, + struct __netgrent *result); + enum nss_status (*getnetgrent_r)(struct __netgrent *result, char *buffer, + size_t buflen, int *errnop); + enum nss_status (*endnetgrent)(struct __netgrent *result); + + /* Services */ + enum nss_status (*getservbyname_r)(const char *name, + const char *protocol, + struct servent *result, + char *buffer, size_t buflen, + int *errnop); + enum nss_status (*getservbyport_r)(int port, const char *protocol, + struct servent *result, + char *buffer, size_t buflen, + int *errnop); + enum nss_status (*setservent)(void); + enum nss_status (*getservent_r)(struct servent *result, + char *buffer, size_t buflen, + int *errnop); + enum nss_status (*endservent)(void); + + /* Hosts */ + enum nss_status (*gethostbyname_r)(const char *name, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop); + enum nss_status (*gethostbyname2_r)(const char *name, int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop); + enum nss_status (*gethostbyaddr_r)(const void *addr, socklen_t addrlen, + int af, struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop); + enum nss_status (*sethostent)(void); + enum nss_status (*gethostent_r)(struct hostent *ret, + char *buf, size_t buflen, + int *errnop, int *h_errnop); + enum nss_status (*endhostent)(void); + + /* Networks */ + enum nss_status (*getnetbyname_r)(const char *name, + struct netent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop); + enum nss_status (*getnetbyaddr_r)(uint32_t addr, int type, + struct netent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop); + enum nss_status (*setnetent)(void); + enum nss_status (*getnetent_r)(struct netent *ret, + char *buffer, size_t buflen, + int *errnop, int *h_errnop); + enum nss_status (*endnetent)(void); + + void *dl_handle; +}; + + +struct sss_nss_symbols { + void **fptr; + bool mandatory; + const char *fname; +}; + +errno_t sss_load_nss_symbols(struct sss_nss_ops *ops, const char *libname, + struct sss_nss_symbols *syms, size_t nsyms); + + +#endif /* __SSSD_NSS_DL_LOAD_H__ */ diff --git a/src/util/pac_utils.c b/src/util/pac_utils.c new file mode 100644 index 0000000..4499d8d --- /dev/null +++ b/src/util/pac_utils.c @@ -0,0 +1,156 @@ +/* + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2022 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#include <errno.h> +#include <talloc.h> + +#include "config.h" + +#include "util/util.h" + +static errno_t check_check_pac_opt(const char *inp, uint32_t *check_pac_flags) +{ + int ret; + size_t c; + char **list = NULL; + uint32_t flags = 0; + + if (strcasecmp(inp, CHECK_PAC_NO_CHECK_STR) == 0) { + flags = 0; + ret = EOK; + goto done; + } + + ret = split_on_separator(NULL, inp, ',', true, true, &list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to split pac_check value.\n"); + goto done; + } + + for (c = 0; list[c] != NULL; c++) { + if (strcasecmp(list[c], CHECK_PAC_NO_CHECK_STR) == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "pac_check option [%s] can be only used alone.\n", + CHECK_PAC_NO_CHECK_STR); + ret = EINVAL; + goto done; + } else if (strcasecmp(list[c], CHECK_PAC_PRESENT_STR) == 0) { + flags |= CHECK_PAC_PRESENT; + } else if (strcasecmp(list[c], CHECK_PAC_CHECK_UPN_STR) == 0) { + flags |= CHECK_PAC_CHECK_UPN; + } else if (strcasecmp(list[c], CHECK_PAC_UPN_DNS_INFO_PRESENT_STR) == 0) { + flags |= CHECK_PAC_UPN_DNS_INFO_PRESENT; + flags |= CHECK_PAC_CHECK_UPN; + } else if (strcasecmp(list[c], CHECK_PAC_CHECK_UPN_DNS_INFO_EX_STR) == 0) { + flags |= CHECK_PAC_CHECK_UPN_DNS_INFO_EX; + } else if (strcasecmp(list[c], CHECK_PAC_UPN_DNS_INFO_EX_PRESENT_STR) == 0) { + flags |= CHECK_PAC_UPN_DNS_INFO_EX_PRESENT; + flags |= CHECK_PAC_CHECK_UPN_DNS_INFO_EX; + flags |= CHECK_PAC_UPN_DNS_INFO_PRESENT; + flags |= CHECK_PAC_CHECK_UPN; + } else if (strcasecmp(list[c], CHECK_PAC_CHECK_UPN_ALLOW_MISSING_STR) == 0) { + flags |= CHECK_PAC_CHECK_UPN_ALLOW_MISSING; + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unknown value [%s] for pac_check.\n", + list[c]); + ret = EINVAL; + goto done; + } + } + + if ((flags & CHECK_PAC_CHECK_UPN_ALLOW_MISSING) + && !(flags & CHECK_PAC_CHECK_UPN)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "pac_check option '%s' is set but '%s' is not set, this means " + "the UPN is not checked.\n", + CHECK_PAC_CHECK_UPN_ALLOW_MISSING_STR, CHECK_PAC_CHECK_UPN_STR); + } + + ret = EOK; + +done: + talloc_free(list); +#ifndef HAVE_PAC_RESPONDER + if (ret == EOK && flags != 0) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "pac_check is configured but SSSD is build without PAC " + "responder, not all checks will be available.\n"); + } +#endif + if (ret == EOK && check_pac_flags != NULL) { + *check_pac_flags = flags; + } + + return ret; +} + +errno_t get_pac_check_config(struct confdb_ctx *cdb, uint32_t *pac_check_opts) +{ + int ret; + char *dummy; + struct sss_domain_info *dom; + struct sss_domain_info *domains = NULL; + + ret = confdb_get_string(cdb, NULL, CONFDB_PAC_CONF_ENTRY, + CONFDB_PAC_CHECK, NULL, &dummy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get pac_check config option [%d][%s].\n", + ret, sss_strerror(ret)); + return ret; + } + + if (dummy == NULL) { + ret = confdb_get_domains(cdb, &domains); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to get domain list, cannot " + "determine pac_check defaults.\n"); + return ret; + } + + for (dom = domains; dom != NULL; + dom = get_next_domain(dom, SSS_GND_DESCEND + |SSS_GND_INCLUDE_DISABLED)) { + if (strcasecmp(dom->provider, "ad") == 0 + || strcasecmp(dom->provider, "ipa") == 0) { + break; + } + } + + if (dom == NULL) { + /* No AD or IPA provider found */ + dummy = talloc_strdup(NULL, CONFDB_PAC_CHECK_DEFAULT); + } else { + dummy = talloc_strdup(NULL, CONFDB_PAC_CHECK_IPA_AD_DEFAULT); + } + if (dummy == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy pac_check defaults.\n"); + return ENOMEM; + } + } + + ret = check_check_pac_opt(dummy, pac_check_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pac_check option [%s] is invalid.\n", + dummy); + } + talloc_free(dummy); + + return ret; +} diff --git a/src/util/probes.h b/src/util/probes.h new file mode 100644 index 0000000..effce49 --- /dev/null +++ b/src/util/probes.h @@ -0,0 +1,46 @@ +/* + Copyright (C) 2015 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __PROBES_H_ +#define __PROBES_H_ + +#ifdef HAVE_SYSTEMTAP + +#include "stap_generated_probes.h" + +/* Probe expansion inspired by libvirt */ +#define PROBE_EXPAND(NAME, ...) NAME(__VA_ARGS__) + +#define PROBE(NAME, ...) do { \ + if (SSSD_ ## NAME ## _ENABLED()) { \ + PROBE_EXPAND(SSSD_ ## NAME, \ + __VA_ARGS__); \ + } \ +} while(0); + +/* Systemtap doesn't handle copying NULL strings well */ +#define PROBE_SAFE_STR(s) ((s) ? (s) : "") + +#else + +/* No systemtap, define empty macros */ +#define PROBE(NAME, ...) do { \ +} while(0); + +#endif + +#endif /* __PROBES_H_ */ diff --git a/src/util/refcount.c b/src/util/refcount.c new file mode 100644 index 0000000..69873d3 --- /dev/null +++ b/src/util/refcount.c @@ -0,0 +1,92 @@ +/* + SSSD + + Simple reference counting wrappers for talloc. + + Authors: + Martin Nagy <mnagy@redhat.com> + + Copyright (C) Red Hat, Inc 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <talloc.h> + +#include "refcount.h" +#include "util/util.h" + +struct wrapper { + int *refcount; + void *ptr; +}; + +static int +refcount_destructor(struct wrapper *wrapper) +{ + (*wrapper->refcount)--; + if (*wrapper->refcount == 0) { + talloc_free(wrapper->ptr); + }; + + return 0; +} + +void * +_rc_alloc(const void *context, size_t size, size_t refcount_offset, + const char *type_name) +{ + struct wrapper *wrapper; + char *refcount_pos; + + wrapper = talloc(context, struct wrapper); + if (wrapper == NULL) { + return NULL; + } + + wrapper->ptr = talloc_named_const(NULL, size, type_name); + if (wrapper->ptr == NULL) { + talloc_free(wrapper); + return NULL; + }; + + refcount_pos = (char *)wrapper->ptr + refcount_offset; + wrapper->refcount = DISCARD_ALIGN(refcount_pos, int *); + *wrapper->refcount = 1; + + talloc_set_destructor(wrapper, refcount_destructor); + + return wrapper->ptr; +} + +void * +_rc_reference(const void *context, size_t refcount_offset, void *source) +{ + struct wrapper *wrapper; + char *refcount_pos; + + wrapper = talloc(context, struct wrapper); + if (wrapper == NULL) { + return NULL; + } + + wrapper->ptr = source; + refcount_pos = (char *)wrapper->ptr + refcount_offset; + wrapper->refcount = DISCARD_ALIGN(refcount_pos, int *); + (*wrapper->refcount)++; + + talloc_set_destructor(wrapper, refcount_destructor); + + return wrapper->ptr; +} diff --git a/src/util/refcount.h b/src/util/refcount.h new file mode 100644 index 0000000..3dd71cf --- /dev/null +++ b/src/util/refcount.h @@ -0,0 +1,63 @@ +/* + SSSD + + Simple reference counting wrappers for talloc. + + Authors: + Martin Nagy <mnagy@redhat.com> + + Copyright (C) Red Hat, Inc 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __REFCOUNT_H__ +#define __REFCOUNT_H__ + +#include <stddef.h> + +#define REFCOUNT_MEMBER_NAME DO_NOT_TOUCH_THIS_MEMBER_refcount + +/* + * Include this member in your structure in order to be able to use it with + * the refcount_* functions. + */ +#define REFCOUNT_COMMON int REFCOUNT_MEMBER_NAME + +/* + * Allocate a new structure that uses reference counting. The resulting pointer + * returned. You must not free the returned pointer manually. It will be freed + * when 'ctx' is freed with talloc_free() and no other references are left. + */ +#define rc_alloc(ctx, type) \ + (type *)_rc_alloc(ctx, sizeof(type), offsetof(type, REFCOUNT_MEMBER_NAME), \ + #type) + +/* + * Increment the reference count of 'src' and return it back if we are + * successful. The reference count will be decremented after 'ctx' has been + * released by talloc_free(). The function will return NULL in case of failure. + */ +#define rc_reference(ctx, type, src) \ + (type *)_rc_reference(ctx, offsetof(type, REFCOUNT_MEMBER_NAME), src) + +/* + * These functions should not be used directly. Use the above macros instead. + */ +void *_rc_alloc(const void *context, size_t size, size_t refcount_offset, + const char *type_name); +void *_rc_reference(const void *context, size_t refcount_offset, void *source); + + +#endif /* !__REFCOUNT_H__ */ diff --git a/src/util/safe-format-string.c b/src/util/safe-format-string.c new file mode 100644 index 0000000..11532d4 --- /dev/null +++ b/src/util/safe-format-string.c @@ -0,0 +1,309 @@ +/* + * This file originated in the realmd project + * + * Copyright 2013 Red Hat Inc + * + * 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 of the licence or (at + * your option) any later version. + * + * See the included COPYING file for more information. + * + * Author: Stef Walter <stefw@redhat.com> + */ + +/* + * Some snippets of code from gnulib, but have since been refactored + * to within an inch of their life... + * + * vsprintf with automatic memory allocation. + * Copyright (C) 1999, 2002-2003 Free Software Foundation, Inc. + * + * This program 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, 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 + * Library General Public License for more details. + */ + +#include "config.h" + +#include "safe-format-string.h" + +#include <errno.h> +#include <stdarg.h> +#include <string.h> + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +static void +safe_padding (int count, + int *total, + void (* copy_fn) (void *, const char *, size_t), + void *data) +{ + char eight[] = " "; + int num; + + while (count > 0) { + num = MIN (count, 8); + copy_fn (data, eight, num); + count -= num; + *total += num; + } +} + +static void +dummy_copy_fn (void *data, + const char *piece, + size_t len) +{ + +} + +int +safe_format_string_cb (void (* copy_fn) (void *, const char *, size_t), + void *data, + const char *format, + const char * const args[], + int num_args) +{ + int at_arg = 0; + const char *cp; + int precision; + int width; + int len; + const char *value; + int total; + int left; + int i; + + if (!copy_fn) + copy_fn = dummy_copy_fn; + + total = 0; + cp = format; + + while (*cp) { + + /* Piece of raw string */ + if (*cp != '%') { + len = strcspn (cp, "%"); + copy_fn (data, cp, len); + total += len; + cp += len; + continue; + } + + cp++; + + /* An literal percent sign? */ + if (*cp == '%') { + copy_fn (data, "%", 1); + total++; + cp++; + continue; + } + + value = NULL; + left = 0; + precision = -1; + width = -1; + + /* Test for positional argument. */ + if (*cp >= '0' && *cp <= '9') { + /* Look-ahead parsing, otherwise skipped */ + if (cp[strspn (cp, "0123456789")] == '$') { + unsigned int n = 0; + for (i = 0; i < 6 && *cp >= '0' && *cp <= '9'; i++, cp++) { + n = 10 * n + (*cp - '0'); + } + /* Positional argument 0 is invalid. */ + if (n == 0) { + errno = EINVAL; + return -1; + } + /* Positional argument N too high */ + if (n > num_args) { + errno = EINVAL; + return -1; + } + value = args[n - 1]; + cp++; /* $ */ + } + } + + /* Read the supported flags. */ + for (; ; cp++) { + if (*cp == '-') + left = 1; + /* Supported but ignored */ + else if (*cp != ' ') + break; + } + + /* Parse the width. */ + if (*cp >= '0' && *cp <= '9') { + width = 0; + for (i = 0; i < 6 && *cp >= '0' && *cp <= '9'; i++, cp++) { + width = 10 * width + (*cp - '0'); + } + } + + /* Parse the precision. */ + if (*cp == '.') { + precision = 0; + for (i = 0, cp++; i < 6 && *cp >= '0' && *cp <= '9'; cp++, i++) { + precision = 10 * precision + (*cp - '0'); + } + } + + /* Read the conversion character. */ + switch (*cp++) { + case 's': + /* Non-positional argument */ + if (value == NULL) { + /* Too many arguments used */ + if (at_arg == num_args) { + errno = EINVAL; + return -1; + } + value = args[at_arg++]; + } + break; + + /* No other conversion characters are supported */ + default: + errno = EINVAL; + return -1; + } + + /* How many characters are we printing? */ + len = strlen (value); + if (precision >= 0) + len = MIN (precision, len); + + /* Do we need padding? */ + safe_padding (left ? 0 : width - len, &total, copy_fn, data); + + /* The actual data */; + copy_fn (data, value, len); + total += len; + + /* Do we need padding? */ + safe_padding (left ? width - len : 0, &total, copy_fn, data); + } + + return total; +} + +static const char ** +valist_to_args (va_list va, + int *num_args) +{ + int alo_args; + const char **args; + const char *arg; + void *mem; + + *num_args = alo_args = 0; + args = NULL; + + for (;;) { + arg = va_arg (va, const char *); + if (arg == NULL) + break; + if (*num_args == alo_args) { + alo_args += 8; + mem = realloc (args, sizeof (const char *) * alo_args); + if (!mem) { + free (args); + return NULL; + } + args = mem; + } + args[(*num_args)++] = arg; + } + + return args; +} + +struct sprintf_ctx { + char *data; + size_t length; + size_t alloc; +}; + +static void +snprintf_copy_fn (void *data, + const char *piece, + size_t length) +{ + struct sprintf_ctx *cx = data; + + /* Don't copy if too much data */ + if (cx->length > cx->alloc) + length = 0; + else if (cx->length + length > cx->alloc) + length = cx->alloc - cx->length; + + if (length > 0) + memcpy (cx->data + cx->length, piece, length); + + /* Null termination happens later */ + cx->length += length; +} + +int +safe_format_string (char *str, + size_t len, + const char *format, + ...) +{ + struct sprintf_ctx cx; + int num_args; + va_list va; + const char **args; + int error = 0; + int ret; + + cx.data = str; + cx.length = 0; + cx.alloc = len; + + va_start (va, format); + args = valist_to_args (va, &num_args); + va_end (va); + + if (args == NULL) { + errno = ENOMEM; + return -1; + } + + if (len) + cx.data[0] = '\0'; + + ret = safe_format_string_cb (snprintf_copy_fn, &cx, format, args, num_args); + if (ret < 0) { + error = errno; + } else if (len > 0) { + cx.data[MIN (cx.length, len - 1)] = '\0'; + } + + free (args); + + if (error) + errno = error; + return ret; +} diff --git a/src/util/safe-format-string.h b/src/util/safe-format-string.h new file mode 100644 index 0000000..6d3ab5d --- /dev/null +++ b/src/util/safe-format-string.h @@ -0,0 +1,81 @@ +/* + * This file originated in the realmd project + * + * Copyright 2013 Red Hat Inc + * + * 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 of the licence or (at + * your option) any later version. + * + * See the included COPYING file for more information. + * + * Author: Stef Walter <stefw@redhat.com> + */ + +#include "config.h" + +#ifndef __SAFE_FORMAT_STRING_H__ +#define __SAFE_FORMAT_STRING_H__ + +#include <stdlib.h> + +/* + * This is a neutered printf variant that can be used with user-provided + * format strings. + * + * Not only are the normal printf functions not safe to use on user-provided + * input (i.e.: can crash, be abused, etc.), they're also very brittle with + * regards to positional arguments: one must consume them all or printf will + * just abort(). This is because arguments of different sizes are accepted + * in the varargs. So obviously the positional code cannot know the offset + * of the relevant varargs if some are not consumed (i.e.: tagged with a + * field type). + * + * Thus the only accepted field type here is 's'. It's all we need. + * + * In general new code should use a better syntax than printf format strings + * for configuration options. This code is here to facilitate robust processing + * of the full_name_format syntax we already have, which has been documented as + * "printf(3) compatible". + * + * Features: + * - Only string 's' fields are supported + * - All the varargs should be strings, followed by a NULL argument + * - Both positional '%1$s' and non-positional '%s' are supported + * - Field widths '%8s' work as expected + * - Precision '%.8s' works, but precision cannot be read from a field + * - Left alignment flag is supported '%-8s'. + * - The space flag '% 8s' has no effect (it's the default for string fields). + * - No more than six digits are supported for widths, precisions, etc. + * - Percent signs are to be escaped as usual '%%' + * + * Use of other flags or field types will cause the relevant printf call to + * return -1. Using too many arguments or incorrect positional arguments + * will also cause the call to fail. + * + * Functions return -1 on failure and set errno. Otherwise they return + * the full length of the string that would be formatted, with the same + * semantics as snprintf(). + */ + +#ifndef GNUC_NULL_TERMINATED +#if __GNUC__ >= 4 +#define GNUC_NULL_TERMINATED __attribute__((__sentinel__)) +#else +#define GNUC_NULL_TERMINATED +#endif +#endif + +int safe_format_string (char *str, + size_t len, + const char *format, + ...) GNUC_NULL_TERMINATED; + +int safe_format_string_cb (void (* callback) (void *data, const char *piece, size_t len), + void *data, + const char *format, + const char * const args[], + int num_args); + +#endif /* __SAFE_FORMAT_STRING_H__ */ diff --git a/src/util/selinux.c b/src/util/selinux.c new file mode 100644 index 0000000..30d16ac --- /dev/null +++ b/src/util/selinux.c @@ -0,0 +1,122 @@ +/* + SSSD + + selinux.c + + Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#ifdef HAVE_SELINUX +#include <selinux/selinux.h> +#include <selinux/label.h> +#endif + +#include "util/debug.h" + +#ifdef HAVE_SELINUX +/* + * selinux_file_context - Set the security context before any file or + * directory creation. + * + * selinux_file_context () should be called before any creation of file, + * symlink, directory, ... + * + * Callers may have to Reset SELinux to create files with default + * contexts: + * reset_selinux_file_context(); + */ +int selinux_file_context(const char *dst_name) +{ + struct selabel_handle *handle = NULL; + char *scontext = NULL; + char *pathname = NULL; + int ret; + + if (is_selinux_enabled() != 1) { + return EOK; + } + + pathname = realpath(dst_name, NULL); + if (pathname == NULL) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "realpath of %s failed [%d]: %s\n", + dst_name, ret, sss_strerror(ret)); + goto done; + } + + /* Get the default security context for this file */ + handle = selabel_open(SELABEL_CTX_FILE, NULL, 0); + if (handle == NULL) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create selabel context " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = selabel_lookup(handle, &scontext, pathname, 0); + if (ret < 0 && errno == ENOENT) { + scontext = NULL; + } else if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup selinux context " + "[%d]: %s", ret, sss_strerror(ret)); + goto done; + } + + /* Set the security context for the next created file */ + if (setfscreatecon(scontext) < 0) { + if (security_getenforce() != 0) { + ret = EFAULT; + goto done; + } + } + + ret = EOK; + +done: + free(pathname); + freecon(scontext); + + if (handle != NULL) { + selabel_close(handle); + } + + return ret; +} + +int reset_selinux_file_context(void) +{ + setfscreatecon(NULL); + return EOK; +} + +#else /* HAVE_SELINUX */ +int selinux_file_context(const char *dst_name) +{ + return EOK; +} + +int reset_selinux_file_context(void) +{ + return EOK; +} +#endif /* HAVE_SELINUX */ diff --git a/src/util/server.c b/src/util/server.c new file mode 100644 index 0000000..34e8a6d --- /dev/null +++ b/src/util/server.c @@ -0,0 +1,794 @@ +/* + SSSD + + Servers setup routines + + Copyright (C) Andrew Tridgell 1992-2005 + Copyright (C) Martin Pool 2002 + Copyright (C) Jelmer Vernooij 2002 + Copyright (C) James J Myers 2003 <myersjj@samba.org> + Copyright (C) Simo Sorce 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdio.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <signal.h> +#include <sys/prctl.h> +#include <ldb.h> +#include "util/util.h" +#include "confdb/confdb.h" +#include "util/sss_chain_id.h" +#include "util/sss_chain_id_tevent.h" + +#ifdef HAVE_PRCTL +#include <sys/prctl.h> +#endif + +static TALLOC_CTX *autofree_ctx; + +static void server_atexit(void) +{ + talloc_zfree(autofree_ctx); +} + +/******************************************************************* + Close the low 3 FDs and open dev/null in their place. +********************************************************************/ +static void close_low_fds(void) +{ +#ifndef VALGRIND + /* try and use up these file descriptors, so silly + library routines writing to stdout etc. won't cause havoc */ + if (freopen ("/dev/null", "r", stdin) == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Can't freopen() stdin to /dev/null\n"); + } + if (freopen ("/dev/null", "w", stdout) == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Can't freopen() stdout to /dev/null\n"); + } + if (freopen ("/dev/null", "w", stderr) == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Can't freopen() stderr to /dev/null\n"); + } +#endif +} + +static void daemon_parent_sigterm(int sig) +{ + _exit(0); +} + +/** + Become a daemon, discarding the controlling terminal. +**/ + +static void become_daemon(void) +{ + pid_t pid, cpid; + int status; + int ret, error; + + pid = fork(); + if (pid == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, "fork() failed: %d [%s]\n", + ret, strerror(ret)); + sss_log(SSS_LOG_ERR, "can't start: fork() failed"); + _exit(1); + } + if (pid != 0) { + /* Terminate parent process on demand so we can hold systemd + * or initd from starting next service until SSSD is initialized. + * We use signals directly here because we don't have a tevent + * context yet. */ + CatchSignal(SIGTERM, daemon_parent_sigterm); + + /* or exit when child process (i.e. sssd monitor) is terminated + * and return error in this case */ + ret = 1; + do { + error = 0; + cpid = waitpid(pid, &status, 0); + if (cpid == -1) { + /* An error occurred while waiting */ + error = errno; + if (error != EINTR) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error [%d][%s] while waiting for child\n", + error, strerror(error)); + /* Forcibly kill this child */ + kill(pid, SIGKILL); + } + } else { + if (WIFEXITED(status)) { + /* return our exit code if available */ + ret = WEXITSTATUS(status); + } + } + } while (error == EINTR); + + _exit(ret); + } + + /* create new session, process group and detach from the terminal */ + if (setsid() == (pid_t) -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, "setsid() failed: %d [%s]\n", + ret, strerror(ret)); + sss_log(SSS_LOG_ERR, "can't start: setsid() failed"); + _exit(1); + } + + /* chdir to / to be sure we're not on a remote filesystem */ + errno = 0; + if(chdir("/") == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, "Cannot change directory (%d [%s])\n", + ret, strerror(ret)); + } + + /* Close FDs 0,1,2. Needed if started by rsh */ + close_low_fds(); +} + +int check_pidfile(const char *file) +{ + char pid_str[32]; + pid_t pid; + int fd; + int ret, err; + ssize_t len; + ssize_t pidlen = sizeof(pid_str) - 1; + + fd = open(file, O_RDONLY, 0644); + err = errno; + if (fd != -1) { + errno = 0; + len = sss_atomic_read_s(fd, pid_str, pidlen); + ret = errno; + close(fd); + if (len == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "read failed [%d][%s].\n", ret, strerror(ret)); + return EINVAL; + } + + /* Ensure NULL-termination */ + pid_str[len] = '\0'; + + /* let's check the pid */ + pid = (pid_t)atoi(pid_str); + if (pid != 0) { + errno = 0; + ret = kill(pid, 0); + /* succeeded in signaling the process -> another sssd process */ + if (ret == 0) { + return EEXIST; + } + if (ret != 0 && errno != ESRCH) { + err = errno; + return err; + } + } + + /* nothing in the file or no process */ + ret = unlink(file); + /* non-fatal failure */ + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to remove file: %s - %d [%s]!\n", + file, ret, sss_strerror(ret)); + } + } else { + if (err != ENOENT) { + return err; + } + } + + return 0; +} + +int pidfile(const char *file) +{ + char pid_str[32]; + int fd; + int ret, err; + size_t size; + ssize_t written; + + ret = check_pidfile(file); + if (ret != EOK) { + return ret; + } + + fd = open(file, O_CREAT | O_WRONLY | O_EXCL, 0644); + err = errno; + if (fd == -1) { + return err; + } + + memset(pid_str, 0, sizeof(pid_str)); + snprintf(pid_str, sizeof(pid_str) -1, "%u\n", (unsigned int) getpid()); + size = strlen(pid_str); + + errno = 0; + written = sss_atomic_write_s(fd, pid_str, size); + err = errno; + close(fd); + if (written == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "write failed [%d][%s]\n", err, strerror(err)); + return err; + } + + if (written != size) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Wrote %zd bytes expected %zu\n", written, size); + return EIO; + } + + return 0; +} + +void orderly_shutdown(int status) +{ +#if HAVE_GETPGRP + static int sent_sigterm; + int debug; + + if (sent_sigterm == 0 && getpgrp() == getpid()) { + debug = is_socket_activated() ? SSSDBG_TRACE_INTERNAL + : SSSDBG_IMPORTANT_INFO; + DEBUG(debug, "SIGTERM: killing children\n"); + sent_sigterm = 1; + kill(-getpgrp(), SIGTERM); + } +#endif + DEBUG(SSSDBG_IMPORTANT_INFO, "Shutting down (status = %d)\n", status); + sss_log(SSS_LOG_INFO, "Shutting down (status = %d)", status); + exit(status); +} + +static void default_quit(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + struct main_context *ctx = talloc_get_type(private_data, struct main_context); + talloc_free(ctx); + + orderly_shutdown(0); +} + +#ifndef HAVE_PRCTL +static void sig_segv_abrt(int sig) +{ + DEBUG(SSSDBG_FATAL_FAILURE, + "Received signal %s, shutting down\n", strsignal(sig)); + orderly_shutdown(1); +} +#endif /* HAVE_PRCTL */ + +/* + setup signal masks +*/ +static void setup_signals(void) +{ + /* we are never interested in SIGPIPE */ + BlockSignals(true, SIGPIPE); + +#if defined(SIGFPE) + /* we are never interested in SIGFPE */ + BlockSignals(true, SIGFPE); +#endif + + /* We are no longer interested in USR1 */ + BlockSignals(true, SIGUSR1); + + /* We are no longer interested in SIGINT except for monitor */ + BlockSignals(true, SIGINT); + +#if defined(SIGUSR2) + /* We are no longer interested in USR2 */ + BlockSignals(true, SIGUSR2); +#endif + + /* POSIX demands that signals are inherited. If the invoking process has + * these signals masked, we will have problems, as we won't receive them. */ + BlockSignals(false, SIGHUP); + BlockSignals(false, SIGTERM); + +#ifndef HAVE_PRCTL + /* If prctl is not defined on the system, try to handle + * some common termination signals gracefully */ + CatchSignal(SIGSEGV, sig_segv_abrt); + CatchSignal(SIGABRT, sig_segv_abrt); +#endif + +} + +/* + handle io on stdin +*/ +static void server_stdin_handler(struct tevent_context *event_ctx, + struct tevent_fd *fde, + uint16_t flags, void *private) +{ + const char *binary_name = (const char *)private; + uint8_t c; + + errno = 0; + if (sss_atomic_read_s(0, &c, 1) == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "%s: EOF on stdin - terminating\n", + binary_name); +#if HAVE_GETPGRP + if (getpgrp() == getpid()) { + kill(-getpgrp(), SIGTERM); + } +#endif + exit(0); + } +} + +/* + main server helpers. +*/ + +int die_if_parent_died(void) +{ +#ifdef HAVE_PRCTL + int ret; + + errno = 0; + ret = prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, "prctl failed [%d]: %s\n", + ret, strerror(ret)); + return ret; + } +#endif + return EOK; +} + +struct logrotate_ctx { + struct confdb_ctx *confdb; + const char *confdb_path; +}; + +static void te_server_hup(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + errno_t ret; + struct logrotate_ctx *lctx = + talloc_get_type(private_data, struct logrotate_ctx); + + DEBUG(SSSDBG_IMPORTANT_INFO, "Received SIGHUP. Rotating logfiles.\n"); + + ret = server_common_rotate_logs(lctx->confdb, lctx->confdb_path); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not reopen log file [%s]\n", + strerror(ret)); + } +} + +errno_t server_common_rotate_logs(struct confdb_ctx *confdb, + const char *conf_path) +{ + errno_t ret; + int old_debug_level = debug_level; + + ret = rotate_debug_files(); + if (ret) { + sss_log(SSS_LOG_ALERT, "Could not rotate debug files! [%d][%s]\n", + ret, strerror(ret)); + return ret; + } + + /* Get new debug level from the confdb */ + ret = confdb_get_int(confdb, conf_path, + CONFDB_SERVICE_DEBUG_LEVEL, + old_debug_level, + &debug_level); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) [%s]\n", + ret, strerror(ret)); + /* Try to proceed with the old value */ + debug_level = old_debug_level; + } + + if (debug_level != old_debug_level) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Debug level changed to %#.4x\n", debug_level); + debug_level = debug_convert_old_level(debug_level); + } + + return EOK; +} + +errno_t generic_get_debug_level(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + void *pvt_data, + uint32_t *_debug_level) +{ + *_debug_level = debug_level; + return EOK; +} + +errno_t generic_set_debug_level(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + void *pvt_data, + uint32_t new_debug_level) +{ + debug_level = new_debug_level; + return EOK; +} + +static const char *get_db_path(void) +{ +#ifdef UNIT_TESTING +#ifdef TEST_DB_PATH + return TEST_DB_PATH; +#else + #error "TEST_DB_PATH must be defined when unit testing server.c!" +#endif /* TEST_DB_PATH */ +#else + return DB_PATH; +#endif /* UNIT_TESTING */ +} + +static const char *get_pid_path(void) +{ +#ifdef UNIT_TESTING +#ifdef TEST_PID_PATH + return TEST_PID_PATH; +#else + #error "TEST_PID_PATH must be defined when unit testing server.c!" +#endif /* TEST_PID_PATH */ +#else + return PID_PATH; +#endif +} + +int server_setup(const char *name, bool is_responder, + int flags, + uid_t uid, gid_t gid, + const char *conf_entry, + struct main_context **main_ctx, + bool allow_sss_loop) +{ + struct tevent_context *event_ctx; + struct main_context *ctx; + uint16_t stdin_event_flags; + char *conf_db; + int ret = EOK; + bool dt; + bool dm; + bool backtrace_enabled; + struct tevent_signal *tes; + struct logrotate_ctx *lctx; + char *locale; + int watchdog_interval; + pid_t my_pid; + char *pidfile_name; + int cfg_debug_level = SSSDBG_INVALID; + bool dumpable = true; + + if (is_responder) { + sss_chain_id_set_format(DEBUG_CHAIN_ID_FMT_CID); + } else { + sss_chain_id_set_format(DEBUG_CHAIN_ID_FMT_RID); + } + + talloc_enable_null_tracking(); + + autofree_ctx = talloc_named_const(NULL, 0, "autofree_context"); + if (autofree_ctx == NULL) { + return ENOMEM; + } + + atexit(server_atexit); + + debug_prg_name = talloc_strdup(autofree_ctx, name); + if (!debug_prg_name) { + return ENOMEM; + } + + my_pid = getpid(); + ret = setpgid(my_pid, my_pid); + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed setting process group: %s[%d]. " + "We might leak processes in case of failure\n", + sss_strerror(ret), ret); + } + + if (!is_socket_activated()) { + ret = chown_debug_file(NULL, uid, gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot chown the debug files, debugging might not work!\n"); + } + + ret = become_user(uid, gid); + if (ret != EOK) { + DEBUG(SSSDBG_FUNC_DATA, + "Cannot become user [%"SPRIuid"][%"SPRIgid"].\n", uid, gid); + return ret; + } + } + + if (!allow_sss_loop) { + ret = setenv("_SSS_LOOPS", "NO", 0); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to set _SSS_LOOPS.\n"); + return ret; + } + } + /* To make sure the domain cannot be set from the environment, unset the + * variable explicitly when setting up any server. Backends later set the + * value after reading domain from the configuration */ + ret = unsetenv(SSS_DOM_ENV); + if (ret != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unsetting "SSS_DOM_ENV" failed, journald " + "logging might not work as expected\n"); + } + + setup_signals(); + + /* we want default permissions on created files to be very strict */ + umask(SSS_DFL_UMASK); + + if (flags & FLAGS_DAEMON) { + DEBUG(SSSDBG_IMPORTANT_INFO, "Becoming a daemon.\n"); + become_daemon(); + } + + if (flags & FLAGS_PID_FILE) { + pidfile_name = talloc_asprintf(NULL, "%s/%s.pid", get_pid_path(), name); + if (!pidfile_name) { + return ENOMEM; + } + ret = pidfile(pidfile_name); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error creating pidfile: %s! " + "(%d [%s])\n", pidfile_name, ret, strerror(ret)); + talloc_free(pidfile_name); + return ret; + } + talloc_free(pidfile_name); + } + + /* Set up locale */ + locale = setlocale(LC_ALL, ""); + if (locale == NULL) { + /* Just print debug message and continue */ + DEBUG(SSSDBG_TRACE_FUNC, "Unable to set locale\n"); + } + + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + /* the event context is the top level structure. + * Everything else should hang off that */ + event_ctx = tevent_context_init(autofree_ctx); + if (event_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "The event context initialization failed\n"); + return 1; + } + + ctx = talloc(event_ctx, struct main_context); + if (ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory, aborting!\n"); + return ENOMEM; + } + + ctx->parent_pid = getppid(); + ctx->event_ctx = event_ctx; + + /* Set up an event handler for a SIGINT */ + tes = tevent_add_signal(event_ctx, event_ctx, SIGINT, 0, + default_quit, ctx); + if (tes == NULL) { + return EIO; + } + + /* Set up an event handler for a SIGTERM */ + tes = tevent_add_signal(event_ctx, event_ctx, SIGTERM, 0, + default_quit, ctx); + if (tes == NULL) { + return EIO; + } + + conf_db = talloc_asprintf(ctx, "%s/%s", + get_db_path(), CONFDB_FILE); + if (conf_db == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory, aborting!\n"); + return ENOMEM; + } + + ret = confdb_init(ctx, &ctx->confdb_ctx, conf_db); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "The confdb initialization failed\n"); + return ret; + } + + if (debug_level == SSSDBG_UNRESOLVED) { + /* set debug level if any in conf_entry */ + ret = confdb_get_int(ctx->confdb_ctx, conf_entry, + CONFDB_SERVICE_DEBUG_LEVEL, + SSSDBG_INVALID, + &cfg_debug_level); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) " + "[%s]\n", ret, strerror(ret)); + return ret; + } + + if (cfg_debug_level == SSSDBG_INVALID) { + /* Check for the `debug` alias */ + ret = confdb_get_int(ctx->confdb_ctx, conf_entry, + CONFDB_SERVICE_DEBUG_LEVEL_ALIAS, + SSSDBG_DEFAULT, + &cfg_debug_level); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) " + "[%s]\n", ret, strerror(ret)); + return ret; + } + } + + debug_level = debug_convert_old_level(cfg_debug_level); + } + + /* same for debug timestamps */ + if (debug_timestamps == SSSDBG_TIMESTAMP_UNRESOLVED) { + ret = confdb_get_bool(ctx->confdb_ctx, conf_entry, + CONFDB_SERVICE_DEBUG_TIMESTAMPS, + SSSDBG_TIMESTAMP_DEFAULT, + &dt); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) " + "[%s]\n", ret, strerror(ret)); + return ret; + } + if (dt) debug_timestamps = SSSDBG_TIMESTAMP_ENABLED; + else debug_timestamps = SSSDBG_TIMESTAMP_DISABLED; + } + + /* same for debug microseconds */ + if (debug_microseconds == SSSDBG_MICROSECONDS_UNRESOLVED) { + ret = confdb_get_bool(ctx->confdb_ctx, conf_entry, + CONFDB_SERVICE_DEBUG_MICROSECONDS, + SSSDBG_MICROSECONDS_DEFAULT, + &dm); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) " + "[%s]\n", ret, strerror(ret)); + return ret; + } + if (dm) debug_microseconds = SSSDBG_MICROSECONDS_ENABLED; + else debug_microseconds = SSSDBG_MICROSECONDS_DISABLED; + } + + ret = confdb_get_bool(ctx->confdb_ctx, conf_entry, + CONFDB_SERVICE_DEBUG_BACKTRACE_ENABLED, + true, + &backtrace_enabled); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading %s from confdb (%d) [%s]\n", + CONFDB_SERVICE_DEBUG_BACKTRACE_ENABLED, ret, strerror(ret)); + return ret; + } + sss_debug_backtrace_enable(backtrace_enabled); + + /* before opening the log file set up log rotation */ + lctx = talloc_zero(ctx, struct logrotate_ctx); + if (!lctx) return ENOMEM; + + lctx->confdb = ctx->confdb_ctx; + lctx->confdb_path = conf_entry; + + tes = tevent_add_signal(ctx->event_ctx, ctx, SIGHUP, 0, + te_server_hup, lctx); + if (tes == NULL) { + return EIO; + } + + DEBUG(SSSDBG_IMPORTANT_INFO, + "Starting with debug level = %#.4x\n", debug_level); + + /* Setup the internal watchdog */ + ret = confdb_get_int(ctx->confdb_ctx, conf_entry, + CONFDB_DOMAIN_TIMEOUT, + 0, &watchdog_interval); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) [%s]\n", + ret, strerror(ret)); + return ret; + } + + if ((flags & FLAGS_NO_WATCHDOG) == 0) { + ret = setup_watchdog(ctx->event_ctx, watchdog_interval); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Watchdog setup failed.\n"); + return ret; + } + } + + ret = confdb_get_bool(ctx->confdb_ctx, + CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_DUMPABLE, + true, /* default value */ + &dumpable); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to determine "CONFDB_MONITOR_DUMPABLE"\n"); + return ret; + } + ret = prctl(PR_SET_DUMPABLE, dumpable ? 1 : 0); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set PR_SET_DUMPABLE\n"); + return ret; + } else if (!dumpable) { + DEBUG(SSSDBG_IMPORTANT_INFO, "Core dumps are disabled!\n"); + } + + sss_chain_id_setup(ctx->event_ctx); + + sss_log(SSS_LOG_INFO, "Starting up"); + + DEBUG(SSSDBG_TRACE_FUNC, "CONFDB: %s\n", conf_db); + + if (flags & FLAGS_INTERACTIVE) { + /* terminate when stdin goes away */ + stdin_event_flags = TEVENT_FD_READ; + } else { + /* stay alive forever */ + stdin_event_flags = 0; + } + + /* catch EOF on stdin */ +#ifdef SIGTTIN + signal(SIGTTIN, SIG_IGN); +#endif + tevent_add_fd(event_ctx, event_ctx, STDIN_FILENO, stdin_event_flags, + server_stdin_handler, discard_const(name)); + + *main_ctx = ctx; + return EOK; +} + +void server_loop(struct main_context *main_ctx) +{ + /* wait for events - this is where the server sits for most of its + life */ + tevent_loop_wait(main_ctx->event_ctx); + + /* as everything hangs off this event context, freeing it + should initiate a clean shutdown of all services */ + talloc_free(main_ctx->event_ctx); +} diff --git a/src/util/session_recording.c b/src/util/session_recording.c new file mode 100644 index 0000000..743f42f --- /dev/null +++ b/src/util/session_recording.c @@ -0,0 +1,127 @@ +/* + SSSD + + Session recording utilities + + Authors: + Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> + + Copyright (C) 2017 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/session_recording.h" +#include "util/debug.h" +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +errno_t session_recording_conf_load(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + struct session_recording_conf *pconf) +{ + int ret; + char *str; + struct stat s; + + if (cdb == NULL || pconf == NULL) { + ret = EINVAL; + goto done; + } + + /* Read session_recording/scope option */ + ret = confdb_get_string(cdb, mem_ctx, CONFDB_SESSION_RECORDING_CONF_ENTRY, + CONFDB_SESSION_RECORDING_SCOPE, "none", &str); + if (ret != EOK) goto done; + if (strcasecmp(str, "none") == 0) { + pconf->scope = SESSION_RECORDING_SCOPE_NONE; + } else if (strcasecmp(str, "some") == 0) { + pconf->scope = SESSION_RECORDING_SCOPE_SOME; + } else if (strcasecmp(str, "all") == 0) { + pconf->scope = SESSION_RECORDING_SCOPE_ALL; + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Unknown value for session recording scope: %s\n", + str); + ret = EINVAL; + goto done; + } + + /* If session recording is enabled at all */ + if (pconf->scope != SESSION_RECORDING_SCOPE_NONE) { + /* Check that the shell exists and is executable */ + ret = stat(SESSION_RECORDING_SHELL, &s); + if (ret != 0) { + switch (errno) { + case ENOENT: + DEBUG(SSSDBG_OP_FAILURE, + "Session recording shell \"%s\" not found\n", + SESSION_RECORDING_SHELL); + ret = EINVAL; + goto done; + case EOK: + if ((s.st_mode & 0111) != 0111) { + DEBUG(SSSDBG_OP_FAILURE, + "Session recording shell \"%s\" is not executable\n", + SESSION_RECORDING_SHELL); + ret = EINVAL; + goto done; + } + break; + default: + DEBUG(SSSDBG_OP_FAILURE, + "Failed checking for session recording shell " + "\"%s\": %s\n", + SESSION_RECORDING_SHELL, strerror(errno)); + ret = EINVAL; + goto done; + } + } + } + + /* Read session_recording/users option */ + ret = confdb_get_string_as_list(cdb, mem_ctx, + CONFDB_SESSION_RECORDING_CONF_ENTRY, + CONFDB_SESSION_RECORDING_USERS, + &pconf->users); + if (ret != EOK && ret != ENOENT) goto done; + + /* Read session_recording/groups option */ + ret = confdb_get_string_as_list(cdb, mem_ctx, + CONFDB_SESSION_RECORDING_CONF_ENTRY, + CONFDB_SESSION_RECORDING_GROUPS, + &pconf->groups); + if (ret != EOK && ret != ENOENT) goto done; + + /* Read session_recording/exclude users option */ + ret = confdb_get_string_as_list(cdb, mem_ctx, + CONFDB_SESSION_RECORDING_CONF_ENTRY, + CONFDB_SESSION_RECORDING_EXCLUDE_USERS, + &pconf->exclude_users); + if (ret != EOK && ret != ENOENT) goto done; + + /* Read session_recording/exclude groups option */ + ret = confdb_get_string_as_list(cdb, mem_ctx, + CONFDB_SESSION_RECORDING_CONF_ENTRY, + CONFDB_SESSION_RECORDING_EXCLUDE_GROUPS, + &pconf->exclude_groups); + if (ret != EOK && ret != ENOENT) goto done; + + ret = EOK; +done: + return ret; +} diff --git a/src/util/session_recording.h b/src/util/session_recording.h new file mode 100644 index 0000000..2b77b44 --- /dev/null +++ b/src/util/session_recording.h @@ -0,0 +1,87 @@ +/* + SSSD + + Session recording utilities + + Authors: + Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> + + Copyright (C) 2017 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __SESSION_RECORDING_H__ +#define __SESSION_RECORDING_H__ + +#include "confdb/confdb.h" +#include "util/util_errors.h" + +/** Scope of users/groups whose session should be recorded */ +enum session_recording_scope { + SESSION_RECORDING_SCOPE_NONE, /**< None, no users/groups */ + SESSION_RECORDING_SCOPE_SOME, /**< Some users/groups specified elsewhere */ + SESSION_RECORDING_SCOPE_ALL /**< All users/groups */ +}; + +/** Session recording configuration (from "session_recording" section) */ +struct session_recording_conf { + /** + * Session recording scope: + * whether to record nobody, everyone, or some users/groups + */ + enum session_recording_scope scope; + /** + * NULL-terminated list of users whose session should be recorded. + * Can be NULL, meaning empty list. Only applicable if scope is "some". + */ + char **users; + /** + * NULL-terminated list of groups, members of which should have their + * sessions recorded. Can be NULL, meaning empty list. Only applicable if + * scope is "some" + */ + char **groups; + /** + * NULL-terminated list of users to be excluded from recording. + * Can be NULL, meaning empty list. Only applicable if scope is "all". + */ + char **exclude_users; + /** + * NULL-terminated list of groups, members of which should be excluded + * from recording. Can be NULL, meaning empty list. Only applicable if + * scope is "all" + */ + char **exclude_groups; +}; + +/** + * Load session recording configuration from configuration database. + * + * @param mem_ctx Memory context to allocate data with. + * @param cdb The configuration database connection object to retrieve + * data from. + * @param pconf Location for the loaded session recording configuration. + * + * @return Status code: + * ENOMEM - memory allocation failed, + * EINVAL - configuration was invalid, + * EIO - an I/O error occurred while communicating with the ConfDB. + */ +extern errno_t session_recording_conf_load( + TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + struct session_recording_conf *pconf); + +#endif /* __SESSION_RECORDING_H__ */ diff --git a/src/util/signal.c b/src/util/signal.c new file mode 100644 index 0000000..d77b79d --- /dev/null +++ b/src/util/signal.c @@ -0,0 +1,89 @@ +/* + Unix SMB/CIFS implementation. + signal handling functions + + Copyright (C) Andrew Tridgell 1998 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> + +/** + * @file + * @brief Signal handling + */ + +/** + Block sigs. +**/ + +void BlockSignals(bool block, int signum) +{ +#ifdef HAVE_SIGPROCMASK + sigset_t set; + sigemptyset(&set); + sigaddset(&set,signum); + sigprocmask(block?SIG_BLOCK:SIG_UNBLOCK,&set,NULL); +#elif defined(HAVE_SIGBLOCK) + if (block) { + sigblock(sigmask(signum)); + } else { + sigsetmask(siggetmask() & ~sigmask(signum)); + } +#else + /* yikes! This platform can't block signals? */ + static int done; + if (!done) { + DEBUG(SSSDBG_FATAL_FAILURE,"WARNING: No signal blocking available\n"); + done=1; + } +#endif +} + +/** + Catch a signal. This should implement the following semantics: + + 1) The handler remains installed after being called. + 2) The signal should be blocked during handler execution. +**/ + +void (*CatchSignal(int signum,void (*handler)(int )))(int) +{ +#ifdef HAVE_SIGACTION + struct sigaction act; + struct sigaction oldact; + + memset(&act, 0, sizeof(act)); + + act.sa_handler = handler; +#ifdef SA_RESTART + /* + * We *want* SIGALRM to interrupt a system call. + */ + if(signum != SIGALRM) + act.sa_flags = SA_RESTART; +#endif + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask,signum); + sigaction(signum,&act,&oldact); + return oldact.sa_handler; +#else /* !HAVE_SIGACTION */ + /* FIXME: need to handle sigvec and systems with broken signal() */ + return signal(signum, handler); +#endif +} diff --git a/src/util/sss_chain_id.c b/src/util/sss_chain_id.c new file mode 100644 index 0000000..9623b52 --- /dev/null +++ b/src/util/sss_chain_id.c @@ -0,0 +1,60 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2021 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/sss_chain_id.h" +#include "config.h" + +extern const char *debug_chain_id_fmt; + +#ifdef BUILD_CHAIN_ID +void sss_chain_id_set_format(const char *fmt) +{ + debug_chain_id_fmt = fmt; +} + +uint64_t sss_chain_id_set(uint64_t id) +{ + uint64_t old_id = debug_chain_id; + debug_chain_id = id; + return old_id; +} + +uint64_t sss_chain_id_get(void) +{ + return debug_chain_id; +} +#else /* BUILD_CHAIN_ID not defined */ + +void sss_chain_id_set_format(const char *fmt) +{ + return; +} + +uint64_t sss_chain_id_set(uint64_t id) +{ + return 0; +} + +uint64_t sss_chain_id_get(void) +{ + return 0; +} + +#endif /* BUILD_CHAIN_ID */ diff --git a/src/util/sss_chain_id.h b/src/util/sss_chain_id.h new file mode 100644 index 0000000..ec5e9fa --- /dev/null +++ b/src/util/sss_chain_id.h @@ -0,0 +1,37 @@ +/* + Authors: + Justin Stephenson <jstephen@redhat.com> + + Copyright (C) 2021 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SSS_CHAIN_ID_ +#define _SSS_CHAIN_ID_ + +#include <stdint.h> + +extern uint64_t debug_chain_id; + +/* Explicitly set new chain id. The old id is returned. */ +uint64_t sss_chain_id_set(uint64_t id); + +/* Get the current chain id. */ +uint64_t sss_chain_id_get(void); + +/* Set new debug chain id logging format. */ +void sss_chain_id_set_format(const char *fmt); + +#endif /* _SSS_CHAIN_ID_ */ diff --git a/src/util/sss_chain_id_tevent.c b/src/util/sss_chain_id_tevent.c new file mode 100644 index 0000000..a68607d --- /dev/null +++ b/src/util/sss_chain_id_tevent.c @@ -0,0 +1,138 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2021 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" +#include "util/sss_chain_id.h" + +#include <tevent.h> + +#ifdef BUILD_CHAIN_ID + +static void sss_chain_id_trace_fde(struct tevent_fd *fde, + enum tevent_event_trace_point point, + void *private_data) +{ + switch (point) { + case TEVENT_EVENT_TRACE_ATTACH: + /* Assign the current chain id when the event is created. */ + tevent_fd_set_tag(fde, debug_chain_id); + break; + case TEVENT_EVENT_TRACE_BEFORE_HANDLER: + /* Set the chain id when a handler is being called. */ + debug_chain_id = tevent_fd_get_tag(fde); + break; + default: + /* Do nothing. */ + break; + } +} + +static void sss_chain_id_trace_signal(struct tevent_signal *se, + enum tevent_event_trace_point point, + void *private_data) +{ + switch (point) { + case TEVENT_EVENT_TRACE_ATTACH: + /* Assign the current chain id when the event is created. */ + tevent_signal_set_tag(se, debug_chain_id); + break; + case TEVENT_EVENT_TRACE_BEFORE_HANDLER: + /* Set the chain id when a handler is being called. */ + debug_chain_id = tevent_signal_get_tag(se); + break; + default: + /* Do nothing. */ + break; + } +} + +static void sss_chain_id_trace_timer(struct tevent_timer *timer, + enum tevent_event_trace_point point, + void *private_data) +{ + switch (point) { + case TEVENT_EVENT_TRACE_ATTACH: + /* Assign the current chain id when the event is created. */ + tevent_timer_set_tag(timer, debug_chain_id); + break; + case TEVENT_EVENT_TRACE_BEFORE_HANDLER: + /* Set the chain id when a handler is being called. */ + debug_chain_id = tevent_timer_get_tag(timer); + break; + default: + /* Do nothing. */ + break; + } +} + +static void sss_chain_id_trace_immediate(struct tevent_immediate *im, + enum tevent_event_trace_point point, + void *private_data) +{ + switch (point) { + case TEVENT_EVENT_TRACE_ATTACH: + /* Assign the current chain id when the event is created. */ + tevent_immediate_set_tag(im, debug_chain_id); + break; + case TEVENT_EVENT_TRACE_BEFORE_HANDLER: + /* Set the chain id when a handler is being called. */ + debug_chain_id = tevent_immediate_get_tag(im); + break; + default: + /* Do nothing. */ + break; + } +} + +static void sss_chain_id_trace_loop(enum tevent_trace_point point, + void *private_data) +{ + switch (point) { + case TEVENT_TRACE_AFTER_LOOP_ONCE: + /* Reset chain id when we got back to the loop. An event handler + * that set chain id was fired. This tracepoint represents a place + * after the event handler was finished, we need to restore chain + * id to 0 (out of request). + */ + debug_chain_id = 0; + break; + default: + /* Do nothing. */ + break; + } +} + +void sss_chain_id_setup(struct tevent_context *ev) +{ + tevent_set_trace_callback(ev, sss_chain_id_trace_loop, NULL); + tevent_set_trace_fd_callback(ev, sss_chain_id_trace_fde, NULL); + tevent_set_trace_signal_callback(ev, sss_chain_id_trace_signal, NULL); + tevent_set_trace_timer_callback(ev, sss_chain_id_trace_timer, NULL); + tevent_set_trace_immediate_callback(ev, sss_chain_id_trace_immediate, NULL); +} + +#else /* BUILD_CHAIN_ID not defined */ + +void sss_chain_id_setup(struct tevent_context *ev) +{ + return; +} + +#endif /* BUILD_CHAIN_ID */ diff --git a/src/util/sss_chain_id_tevent.h b/src/util/sss_chain_id_tevent.h new file mode 100644 index 0000000..547d271 --- /dev/null +++ b/src/util/sss_chain_id_tevent.h @@ -0,0 +1,29 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2021 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SSS_CHAIN_ID_TEVENT_ +#define _SSS_CHAIN_ID_TEVENT_ + +#include <tevent.h> + +/* Setup chain id tracking on tevent context. */ +void sss_chain_id_setup(struct tevent_context *ev); + +#endif /* _SSS_CHAIN_ID_TEVENT_ */ diff --git a/src/util/sss_cli_cmd.c b/src/util/sss_cli_cmd.c new file mode 100644 index 0000000..0b743df --- /dev/null +++ b/src/util/sss_cli_cmd.c @@ -0,0 +1,244 @@ +/* + SSSD - cmd2str util + + Copyright (C) Petr Cech <pcech@redhat.com> 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "sss_client/sss_cli.h" +#include "util/sss_cli_cmd.h" +#include "util/util.h" + +const char *sss_cmd2str(enum sss_cli_command cmd) +{ + switch (cmd) { + /* null */ + case SSS_CLI_NULL: + return "SSS_CLI_NULL"; + + /* version */ + case SSS_GET_VERSION: + return "SSS_GET_VERSION"; + + /* passwd */ + case SSS_NSS_GETPWNAM: + return "SSS_NSS_GETPWNAM"; + case SSS_NSS_GETPWUID: + return "SSS_NSS_GETPWUID"; + case SSS_NSS_SETPWENT: + return "SSS_NSS_SETPWENT"; + case SSS_NSS_GETPWENT: + return "SSS_NSS_GETPWENT"; + case SSS_NSS_ENDPWENT: + return "SSS_NSS_ENDPWENT"; + + /* group */ + case SSS_NSS_GETGRNAM: + return "SSS_NSS_GETGRNAM"; + case SSS_NSS_GETGRGID: + return "SSS_NSS_GETGRGID"; + case SSS_NSS_SETGRENT: + return "SSS_NSS_SETGRENT"; + case SSS_NSS_GETGRENT: + return "SSS_NSS_GETGRENT"; + case SSS_NSS_ENDGRENT: + return "SSS_NSS_ENDGRENT"; + case SSS_NSS_INITGR: + return "SSS_NSS_INITGR"; + +#if 0 + /* aliases */ + case SSS_NSS_GETALIASBYNAME: + return "SSS_NSS_GETALIASBYNAME"; + case SSS_NSS_GETALIASBYPORT: + return "SSS_NSS_GETALIASBYPORT"; + case SSS_NSS_SETALIASENT: + return "SSS_NSS_SETALIASENT"; + case SSS_NSS_GETALIASENT: + return "SSS_NSS_GETALIASENT"; + case SSS_NSS_ENDALIASENT: + return "SSS_NSS_ENDALIASENT"; + + /* ethers */ + case SSS_NSS_GETHOSTTON: + return "SSS_NSS_GETHOSTTON"; + case SSS_NSS_GETNTOHOST: + return "SSS_NSS_GETNTOHOST"; + case SSS_NSS_SETETHERENT: + return "SSS_NSS_SETETHERENT"; + case SSS_NSS_GETETHERENT: + return "SSS_NSS_GETETHERENT"; + case SSS_NSS_ENDETHERENT: + return "SSS_NSS_ENDETHERENT"; + +#endif + /* hosts */ + case SSS_NSS_GETHOSTBYNAME: + return "SSS_NSS_GETHOSTBYNAME"; + case SSS_NSS_GETHOSTBYNAME2: + return "SSS_NSS_GETHOSTBYNAME2"; + case SSS_NSS_GETHOSTBYADDR: + return "SSS_NSS_GETHOSTBYADDR"; + case SSS_NSS_SETHOSTENT: + return "SSS_NSS_SETHOSTENT"; + case SSS_NSS_GETHOSTENT: + return "SSS_NSS_GETHOSTENT"; + case SSS_NSS_ENDHOSTENT: + return "SSS_NSS_ENDHOSTENT"; + + /* netgroup */ + case SSS_NSS_SETNETGRENT: + return "SSS_NSS_SETNETGRENT"; + case SSS_NSS_GETNETGRENT: + return "SSS_NSS_GETNETGRENT"; + case SSS_NSS_ENDNETGRENT: + return "SSS_NSS_ENDNETGRENT"; + + /* networks */ + case SSS_NSS_GETNETBYNAME: + return "SSS_NSS_GETNETBYNAME"; + case SSS_NSS_GETNETBYADDR: + return "SSS_NSS_GETNETBYADDR"; + case SSS_NSS_SETNETENT: + return "SSS_NSS_SETNETENT"; + case SSS_NSS_GETNETENT: + return "SSS_NSS_GETNETENT"; + case SSS_NSS_ENDNETENT: + return "SSS_NSS_ENDNETENT"; + +#if 0 + /* protocols */ + case SSS_NSS_GETPROTOBYNAME: + return "SSS_NSS_GETPROTOBYNAME"; + case SSS_NSS_GETPROTOBYNUM: + return "SSS_NSS_GETPROTOBYNUM"; + case SSS_NSS_SETPROTOENT: + return "SSS_NSS_SETPROTOENT"; + case SSS_NSS_GETPROTOENT: + return "SSS_NSS_GETPROTOENT"; + case SSS_NSS_ENDPROTOENT: + return "SSS_NSS_ENDPROTOENT"; + + /* rpc */ + case SSS_NSS_GETRPCBYNAME: + return "SSS_NSS_GETRPCBYNAME"; + case SSS_NSS_GETRPCBYNUM: + return "SSS_NSS_GETRPCBYNUM"; + case SSS_NSS_SETRPCENT: + return "SSS_NSS_SETRPCENT"; + case SSS_NSS_GETRPCENT: + return "SSS_NSS_GETRPCENT"; + case SSS_NSS_ENDRPCENT: + return "SSS_NSS_ENDRPCENT"; +#endif + + /* services */ + case SSS_NSS_GETSERVBYNAME: + return "SSS_NSS_GETSERVBYNAME"; + case SSS_NSS_GETSERVBYPORT: + return "SSS_NSS_GETSERVBYPORT"; + case SSS_NSS_SETSERVENT: + return "SSS_NSS_SETSERVENT"; + case SSS_NSS_GETSERVENT: + return "SSS_NSS_GETSERVENT"; + case SSS_NSS_ENDSERVENT: + return "SSS_NSS_ENDSERVENT"; + +#if 0 + /* shadow */ + case SSS_NSS_GETSPNAM: + return "SSS_NSS_GETSPNAM"; + case SSS_NSS_GETSPUID: + return "SSS_NSS_GETSPUID"; + case SSS_NSS_SETSPENT: + return "SSS_NSS_SETSPENT"; + case SSS_NSS_GETSPENT: + return "SSS_NSS_GETSPENT"; + case SSS_NSS_ENDSPENT: + return "SSS_NSS_ENDSPENT"; +#endif + + /* SUDO */ + case SSS_SUDO_GET_SUDORULES: + return "SSS_SUDO_GET_SUDORULES"; + case SSS_SUDO_GET_DEFAULTS: + return "SSS_SUDO_GET_DEFAULTS"; + + /* autofs */ + case SSS_AUTOFS_SETAUTOMNTENT: + return "SSS_AUTOFS_SETAUTOMNTENT"; + case SSS_AUTOFS_GETAUTOMNTENT: + return "SSS_AUTOFS_GETAUTOMNTENT"; + case SSS_AUTOFS_GETAUTOMNTBYNAME: + return "SSS_AUTOFS_GETAUTOMNTBYNAME"; + case SSS_AUTOFS_ENDAUTOMNTENT: + return "SSS_AUTOFS_ENDAUTOMNTENT"; + + /* SSH */ + case SSS_SSH_GET_USER_PUBKEYS: + return "SSS_SSH_GET_USER_PUBKEYS"; + case SSS_SSH_GET_HOST_PUBKEYS: + return "SSS_SSH_GET_HOST_PUBKEYS"; + + /* PAM related calls */ + case SSS_PAM_AUTHENTICATE: + return "SSS_PAM_AUTHENTICATE"; + case SSS_PAM_SETCRED: + return "SSS_PAM_SETCRED"; + case SSS_PAM_ACCT_MGMT: + return "SSS_PAM_ACCT_MGMT"; + case SSS_PAM_OPEN_SESSION: + return "SSS_PAM_OPEN_SESSION"; + case SSS_PAM_CLOSE_SESSION: + return "SSS_PAM_CLOSE_SESSION"; + case SSS_PAM_CHAUTHTOK: + return "SSS_PAM_CHAUTHTOK"; + case SSS_PAM_CHAUTHTOK_PRELIM: + return "SSS_PAM_CHAUTHTOK_PRELIM"; + case SSS_CMD_RENEW: + return "SSS_CMD_RENEW"; + case SSS_PAM_PREAUTH: + return "SSS_PAM_PREAUTH"; + + /* PAC responder calls */ + case SSS_PAC_ADD_PAC_USER: + return "SSS_PAC_ADD_PAC_USER"; + + /* ID-SID mapping calls */ + case SSS_NSS_GETSIDBYNAME: + return "SSS_NSS_GETSIDBYNAME"; + case SSS_NSS_GETSIDBYID: + return "SSS_NSS_GETSIDBYID"; + case SSS_NSS_GETNAMEBYSID: + return "SSS_NSS_GETNAMEBYSID"; + case SSS_NSS_GETIDBYSID: + return "SSS_NSS_GETIDBYSID"; + case SSS_NSS_GETORIGBYNAME: + return "SSS_NSS_GETORIGBYNAME"; + case SSS_NSS_GETORIGBYUSERNAME: + return "SSS_NSS_GETORIGBYUSERNAME"; + case SSS_NSS_GETORIGBYGROUPNAME: + return "SSS_NSS_GETORIGBYGROUPNAME"; + + /* SUBID ranges */ + case SSS_NSS_GET_SUBID_RANGES: + return "SSS_NSS_GET_SUBID_RANGES"; + + default: + DEBUG(SSSDBG_MINOR_FAILURE, + "Translation's string is missing for command [%#x].\n", cmd); + return "UNKNOWN COMMAND"; + } +} diff --git a/src/util/sss_cli_cmd.h b/src/util/sss_cli_cmd.h new file mode 100644 index 0000000..66ad076 --- /dev/null +++ b/src/util/sss_cli_cmd.h @@ -0,0 +1,28 @@ +/* + SSSD - cmd2str util + + Copyright (C) Petr Cech <pcech@redhat.com> 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __SSS_CLI_CMD_H__ +#define __SSS_CLI_CMD_H__ + +#include "sss_client/sss_cli.h" + +/* Translate sss_cli_command to human readable form. */ +const char *sss_cmd2str(enum sss_cli_command cmd); + +#endif /* __SSS_CLI_CMD_H__ */ diff --git a/src/util/sss_endian.h b/src/util/sss_endian.h new file mode 100644 index 0000000..834c359 --- /dev/null +++ b/src/util/sss_endian.h @@ -0,0 +1,57 @@ +/* + SSSD + + Authors: + Lukas Slebodnik <lslebodn@redhat.com> + + Copyright (C) 2013 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef SSS_ENDIAN_H_ +#define SSS_ENDIAN_H_ + +#ifdef HAVE_ENDIAN_H +# include <endian.h> +#elif defined(HAVE_SYS_ENDIAN_H) +# include <sys/endian.h> +#endif /* !HAVE_ENDIAN_H && !HAVE_SYS_ENDIAN_H */ + +/* Endianness-compatibility for systems running older versions of glibc */ + +#ifndef le32toh +#ifndef HAVE_BYTESWAP_H +#error missing le32toh and byteswap.h +#else /* defined HAVE_BYTESWAP_H */ +#include <byteswap.h> + +/* support RHEL5 lack of definitions */ +/* Copied from endian.h on glibc 2.15 */ +#ifdef __USE_BSD +/* Conversion interfaces. */ +# if __BYTE_ORDER == __LITTLE_ENDIAN +# define le32toh(x) (x) +# define htole32(x) (x) +# else +# define le32toh(x) __bswap_32 (x) +# define htole32(x) __bswap_32 (x) +# endif +#endif /* __USE_BSD */ + +#endif /* HAVE_BYTESWAP_H */ + +#endif /* le32toh */ + +#endif /* SSS_ENDIAN_H_ */ diff --git a/src/util/sss_format.h b/src/util/sss_format.h new file mode 100644 index 0000000..6f2735b --- /dev/null +++ b/src/util/sss_format.h @@ -0,0 +1,76 @@ +/* + SSSD + + sss_format.h + + Authors: + Lukas Slebodnik <lslebodn@redhat.com> + + Copyright (C) 2013 Red Hat + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#ifndef __SSS_FORMAT_H__ +#define __SSS_FORMAT_H__ + +#include "config.h" + +#include <inttypes.h> + +/* key_serial_t is defined in keyutils.h as typedef int32_t */ +#define SPRIkey_ser PRId32 + +/* rlim_t is defined with conditional build as unsigned type. + * It seems that sizeof(rlim_t) is 8. It may be platform dependent, therefore + * the same format will be used like with uint64_t. + */ +#define SPRIrlim PRIu64 + +#if SIZEOF_ID_T == 8 +# define SPRIid PRIu64 +#elif SIZEOF_ID_T == 4 +# define SPRIid PRIu32 +#else +# error Unexpected sizeof id_t +#endif /* SIZEOF_ID_T */ + +#if SIZEOF_UID_T == 8 +# define SPRIuid PRIu64 +#elif SIZEOF_UID_T == 4 +# define SPRIuid PRIu32 +#else +# error Unexpected sizeof uid_t +#endif /* SIZEOF_UID_T */ + +#if SIZEOF_GID_T == 8 +# define SPRIgid PRIu64 +#elif SIZEOF_GID_T == 4 +# define SPRIgid PRIu32 +#else +# error Unexpected sizeof gid_t +#endif /* SIZEOF_GID_T */ + +#if SIZEOF_TIME_T == 8 +# define SPRItime PRId64 +#elif SIZEOF_TIME_T == 4 +# define SPRItime PRId32 +#else +# error Unexpected sizeof time_t +#endif /* SIZEOF_TIME_T */ + + +#endif /* __SSS_FORMAT_H__ */ diff --git a/src/util/sss_ini.c b/src/util/sss_ini.c new file mode 100644 index 0000000..3f71b18 --- /dev/null +++ b/src/util/sss_ini.c @@ -0,0 +1,969 @@ +/* + SSSD + + sss_ini.c + + Authors: + Ondrej Kos <okos@redhat.com> + + Copyright (C) 2013 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdio.h> +#include <errno.h> +#include <talloc.h> + +#include "config.h" +#include "util/util.h" +#include "util/sss_ini.h" +#include "confdb/confdb_setup.h" +#include "confdb/confdb_private.h" + +#include "ini_configobj.h" +#include "ini_config.h" + +struct sss_ini { + char **error_list; + struct ref_array *ra_success_list; + struct ref_array *ra_error_list; + struct ini_cfgobj *sssd_config; + struct value_obj *obj; + const struct stat *cstat; + struct ini_cfgfile *file; + bool main_config_exists; +}; + +#define sss_ini_get_sec_list ini_get_section_list +#define sss_ini_get_attr_list ini_get_attribute_list +#define sss_ini_get_const_string_config_value ini_get_const_string_config_value +#define sss_ini_get_config_obj ini_get_config_valueobj + + +static void sss_ini_free_error_messages(struct sss_ini *self) +{ + if (self != NULL) { + ini_config_free_errors(self->error_list); + self->error_list = NULL; + } +} + +static void sss_ini_free_ra_messages(struct sss_ini *self) +{ + if (self != NULL) { + ref_array_destroy(self->ra_success_list); + self->ra_success_list = NULL; + ref_array_destroy(self->ra_error_list); + self->ra_error_list = NULL; + } +} + +static void sss_ini_free_config(struct sss_ini *self) +{ + if (self != NULL && self->sssd_config != NULL) { + ini_config_destroy(self->sssd_config); + self->sssd_config = NULL; + } +} + +/* Close file descriptor */ + +static void sss_ini_close_file(struct sss_ini *self) +{ + if (self != NULL && self->file != NULL) { + ini_config_file_destroy(self->file); + self->file = NULL; + } +} + +/* sss_ini destructor */ + +static int sss_ini_destroy(struct sss_ini *self) +{ + sss_ini_free_error_messages(self); + sss_ini_free_ra_messages(self); + sss_ini_free_config(self); + sss_ini_close_file(self); + return 0; +} + +/* Initialize data structure */ + +struct sss_ini* sss_ini_new(TALLOC_CTX *mem_ctx) +{ + struct sss_ini *self; + + + self = talloc_zero(mem_ctx, struct sss_ini); + if (!self) { + DEBUG(SSSDBG_CRIT_FAILURE, "Not enough memory for sss_ini_data.\n"); + return NULL; + } + talloc_set_destructor(self, sss_ini_destroy); + return self; +} + +/* Open configuration file */ + +static int sss_ini_config_file_open(struct sss_ini *self, + const char *config_file) +{ + int ret; + + if (self == NULL) { + return EINVAL; + } + + ret = ini_config_file_open(config_file, + INI_META_STATS, + &self->file); + self->main_config_exists = (ret != ENOENT); + return ret; +} + +static int sss_ini_config_file_from_mem(struct sss_ini *self, + void *data_buf, + uint32_t data_len) +{ + if (self == NULL) { + return EINVAL; + } + + return ini_config_file_from_mem(data_buf, strlen(data_buf), + &self->file); +} + +/* Check configuration file permissions */ + +static int sss_ini_access_check(struct sss_ini *self) +{ + if (!self->main_config_exists) { + return EOK; + } + + return ini_config_access_check(self->file, + INI_ACCESS_CHECK_MODE | + INI_ACCESS_CHECK_UID | + INI_ACCESS_CHECK_GID, + 0, /* owned by root */ + 0, /* owned by root */ + S_IRUSR, /* r**------ */ + ALLPERMS & ~(S_IWUSR|S_IXUSR)); +} + + + +/* Get cstat */ + +int sss_ini_get_stat(struct sss_ini *self) +{ + self->cstat = ini_config_get_stat(self->file); + + if (!self->cstat) return EIO; + + return EOK; +} + + + +/* Get mtime */ + +int sss_ini_get_mtime(struct sss_ini *self, + size_t timestr_len, + char *timestr) +{ + return snprintf(timestr, timestr_len, "%llu", + (long long unsigned)self->cstat->st_mtime); +} + +/* Get file_exists */ + +bool sss_ini_exists(struct sss_ini *self) +{ + return self->main_config_exists; +} + +/* Print ini_config errors */ + +static void sss_ini_config_print_errors(char **error_list) +{ + unsigned count = 0; + + if (!error_list) { + return; + } + + while (error_list[count]) { + DEBUG(SSSDBG_FATAL_FAILURE, "%s\n", error_list[count]); + count++; + } +} + +static int sss_ini_parse(struct sss_ini *self) +{ + int ret; + + if (!self) { + return EINVAL; + } + + sss_ini_free_error_messages(self); + sss_ini_free_config(self); + + /* Create config object */ + ret = ini_config_create(&(self->sssd_config)); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to create config object. Error %d.\n", ret); + return ret; + } + + /* Parse file */ + ret = ini_config_parse(self->file, + INI_STOP_ON_ANY, + INI_MV1S_OVERWRITE, + INI_PARSE_NOWRAP, + self->sssd_config); + + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to parse configuration. Error %d.\n", ret); + + if (ini_config_error_count(self->sssd_config)) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Errors detected while parsing: %s\n", + ini_config_get_filename(self->file)); + + ini_config_get_errors(self->sssd_config, + &(self->error_list)); + } + } + return ret; +} + +static int sss_ini_add_snippets(struct sss_ini *self, + const char *config_dir) +{ +#ifdef HAVE_LIBINI_CONFIG_V1_3 + int ret; + const char *patterns[] = { "^[^\\.].*\\.conf$", NULL }; + const char *sections[] = { ".*", NULL }; + uint32_t i = 0; + char *msg = NULL; + struct ini_cfgobj *modified_sssd_config = NULL; + struct access_check snip_check; + + if (self == NULL || self->sssd_config == NULL || config_dir == NULL) { + return EINVAL; + } + + sss_ini_free_ra_messages(self); + + snip_check.flags = INI_ACCESS_CHECK_MODE | INI_ACCESS_CHECK_UID + | INI_ACCESS_CHECK_GID; + snip_check.uid = 0; /* owned by root */ + snip_check.gid = 0; /* owned by root */ + snip_check.mode = S_IRUSR; /* r**------ */ + snip_check.mask = ALLPERMS & ~(S_IWUSR | S_IXUSR); + + ret = ini_config_augment(self->sssd_config, + config_dir, + patterns, + sections, + &snip_check, + INI_STOP_ON_ANY, + INI_MV1S_OVERWRITE, + INI_PARSE_NOWRAP, + INI_MV2S_OVERWRITE, + &modified_sssd_config, + &self->ra_error_list, + &self->ra_success_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to augment configuration: Error %d", + ret); + } + + while (ref_array_get(self->ra_success_list, i, &msg) != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "Config merge success: %s\n", msg); + i++; + } + + i = 0; + while (ref_array_get(self->ra_error_list, i, &msg) != NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Config merge error: %s\n", msg); + i++; + } + + /* switch config objects if there are no errors */ + if (modified_sssd_config != NULL) { + ini_config_destroy(self->sssd_config); + self->sssd_config = modified_sssd_config; + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Using only main configuration file due to errors in merging\n"); + } + return ret; + +#else /* HAVE_LIBINI_CONFIG_V1_3 */ + return EOK; +#endif /* ! HAVE_LIBINI_CONFIG_V1_3 */ +} + +struct ref_array * +sss_ini_get_ra_success_list(struct sss_ini *self) +{ +#ifdef HAVE_LIBINI_CONFIG_V1_3 + return self->ra_success_list; +#else + return NULL; +#endif /* HAVE_LIBINI_CONFIG_V1_3 */ +} + +struct ref_array * +sss_ini_get_ra_error_list(struct sss_ini *self) +{ +#ifdef HAVE_LIBINI_CONFIG_V1_3 + return self->ra_error_list; +#else + return NULL; +#endif /* HAVE_LIBINI_CONFIG_V1_3 */ +} + +/* Get configuration object */ + +int sss_ini_get_cfgobj(struct sss_ini *self, + const char *section, const char *name) +{ + return sss_ini_get_config_obj(section,name, self->sssd_config, + INI_GET_FIRST_VALUE, &self->obj); +} + +/* Check configuration object */ + +int sss_ini_check_config_obj(struct sss_ini *self) +{ + if (self->obj == NULL) { + return ENOENT; + } + + return EOK; +} + + + +/* Get integer value */ + +int sss_ini_get_int_config_value(struct sss_ini *self, + int strict, int def, int *error) +{ + return ini_get_int_config_value(self->obj, strict, def, error); +} + +/* Get string value */ + +const char *sss_ini_get_string_config_value(struct sss_ini *self, + int *error) +{ + return ini_get_string_config_value(self->obj, error); +} + +/* Create LDIF */ + +int sss_confdb_create_ldif(TALLOC_CTX *mem_ctx, + struct sss_ini *self, + const char *only_section, + const char **config_ldif) +{ + int ret, i, j; + char *ldif; + char *tmp_ldif; + char **sections; + int section_count; + char *dn; + char *tmp_dn; + char *sec_dn; + char **attrs; + int attr_count; + char *ldif_attr; + TALLOC_CTX *tmp_ctx; + size_t dn_size; + size_t ldif_len; + size_t attr_len; + struct value_obj *obj = NULL; + bool section_handled = true; + + if (only_section != NULL) { + /* If the section is specified, we must handle it, either by adding + * its contents or by deleting the section if it doesn't exist + */ + section_handled = false; + } + + ldif_len = strlen(CONFDB_INTERNAL_LDIF); + ldif = talloc_array(mem_ctx, char, ldif_len+1); + if (!ldif) return ENOMEM; + + tmp_ctx = talloc_new(ldif); + if (!tmp_ctx) { + ret = ENOMEM; + goto error; + } + + memcpy(ldif, CONFDB_INTERNAL_LDIF, ldif_len); + + /* Read in the collection and convert it to an LDIF */ + /* Get the list of sections */ + sections = sss_ini_get_sec_list(self->sssd_config, + §ion_count, &ret); + if (ret != EOK) { + goto error; + } + + for (i = 0; i < section_count; i++) { + const char *rdn = NULL; + DEBUG(SSSDBG_TRACE_FUNC, + "Processing config section [%s]\n", sections[i]); + ret = parse_section(tmp_ctx, sections[i], &sec_dn, &rdn); + if (ret != EOK) { + goto error; + } + + if (only_section != NULL) { + if (strcasecmp(only_section, sections[i])) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping section %s\n", sections[i]); + continue; + } else { + /* Mark the requested section as handled so that we don't + * try to re-add it later + */ + section_handled = true; + } + } + + dn = talloc_asprintf(tmp_ctx, + "dn: %s,cn=config\n" + "cn: %s\n", + sec_dn, rdn); + if (!dn) { + ret = ENOMEM; + free_section_list(sections); + goto error; + } + dn_size = strlen(dn); + + /* Get all of the attributes and their values as LDIF */ + attrs = sss_ini_get_attr_list(self->sssd_config, sections[i], + &attr_count, &ret); + if (ret != EOK) { + free_section_list(sections); + goto error; + } + + for (j = 0; j < attr_count; j++) { + DEBUG(SSSDBG_TRACE_FUNC, + "Processing attribute [%s]\n", attrs[j]); + ret = sss_ini_get_config_obj(sections[i], attrs[j], + self->sssd_config, + INI_GET_FIRST_VALUE, &obj); + if (ret != EOK) goto error; + + const char *value = sss_ini_get_const_string_config_value(obj, &ret); + if (ret != EOK) goto error; + if (value && value[0] == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Attribute '%s' has empty value, ignoring\n", + attrs[j]); + continue; + } + + ldif_attr = talloc_asprintf(tmp_ctx, + "%s: %s\n", attrs[j], value); + DEBUG(SSSDBG_TRACE_ALL, "%s\n", ldif_attr); + + attr_len = strlen(ldif_attr); + + tmp_dn = talloc_realloc(tmp_ctx, dn, char, + dn_size+attr_len+1); + if (!tmp_dn) { + ret = ENOMEM; + free_attribute_list(attrs); + free_section_list(sections); + goto error; + } + dn = tmp_dn; + memcpy(dn+dn_size, ldif_attr, attr_len+1); + dn_size += attr_len; + } + + dn_size ++; + tmp_dn = talloc_realloc(tmp_ctx, dn, char, + dn_size+1); + if (!tmp_dn) { + ret = ENOMEM; + free_attribute_list(attrs); + free_section_list(sections); + goto error; + } + dn = tmp_dn; + dn[dn_size-1] = '\n'; + dn[dn_size] = '\0'; + + DEBUG(SSSDBG_TRACE_ALL, "Section dn\n%s\n", dn); + + tmp_ldif = talloc_realloc(mem_ctx, ldif, char, + ldif_len+dn_size+1); + if (!tmp_ldif) { + ret = ENOMEM; + free_attribute_list(attrs); + free_section_list(sections); + goto error; + } + ldif = tmp_ldif; + memcpy(ldif+ldif_len, dn, dn_size); + ldif_len += dn_size; + + free_attribute_list(attrs); + talloc_free(dn); + } + + + if (only_section != NULL && section_handled == false) { + /* If only a single section was supposed to be + * handled, but it wasn't found in the INI file, + * create an LDIF that would remove the section + */ + ret = parse_section(tmp_ctx, only_section, &sec_dn, NULL); + if (ret != EOK) { + goto error; + } + + dn = talloc_asprintf(tmp_ctx, + "dn: %s,cn=config\n" + "changetype: delete\n\n", + sec_dn); + if (dn == NULL) { + ret = ENOMEM; + goto error; + } + dn_size = strlen(dn); + + tmp_ldif = talloc_realloc(mem_ctx, ldif, char, + ldif_len+dn_size+1); + if (!tmp_ldif) { + ret = ENOMEM; + goto error; + } + + ldif = tmp_ldif; + memcpy(ldif+ldif_len, dn, dn_size); + ldif_len += dn_size; + } + + ldif[ldif_len] = '\0'; + + free_section_list(sections); + + *config_ldif = (const char *)ldif; + talloc_free(tmp_ctx); + return EOK; + +error: + talloc_free(ldif); + return ret; +} + +#ifdef HAVE_LIBINI_CONFIG_V1_3 +static errno_t check_domain_inherit_from(char *cfg_section, + struct ini_cfgobj *config_obj, + struct ini_errobj *errobj) +{ + struct value_obj *vo = NULL; + int ret; + + ret = ini_get_config_valueobj(cfg_section, + "inherit_from", + config_obj, + INI_GET_NEXT_VALUE, + &vo); + if (ret != EOK) { + goto done; + } + + if (vo != NULL) { + ret = ini_errobj_add_msg(errobj, + "Attribute 'inherit_from' is not " + "allowed in section '%s'.", + cfg_section); + if (ret != EOK) { + goto done; + } + } + + ret = EOK; + +done: + return ret; +} + +static errno_t check_domain_id_provider(char *cfg_section, + struct ini_cfgobj *config_obj, + struct ini_errobj *errobj) +{ + struct value_obj *vo = NULL; + const char *valid_values[] = { "ad", "ipa", "ldap", "proxy", +#ifdef BUILD_FILES_PROVIDER + "files", +#endif + NULL }; + const char **valid_value; + const char *value; + int ret; + + ret = ini_get_config_valueobj(cfg_section, + "id_provider", + config_obj, + INI_GET_NEXT_VALUE, + &vo); + if (ret != EOK) { + goto done; + } + + if (vo == NULL) { + ret = ini_errobj_add_msg(errobj, + "Attribute 'id_provider' is " + "missing in section '%s'.", + cfg_section); + } else { + value = sss_ini_get_const_string_config_value(vo, &ret); + if (ret != EOK) { + goto done; + } + + valid_value = valid_values; + while (*valid_value != NULL) { + if (strcmp(value, *valid_value) == 0) { + break; + } + valid_value++; + } + if (*valid_value == NULL) { + ret = ini_errobj_add_msg(errobj, + "Attribute 'id_provider' in section '%s' " + "has an invalid value: %s", + cfg_section, value); + if (ret != EOK) { + goto done; + } + } + } + + ret = EOK; + +done: + return ret; +} + +#define SECTION_IS_DOMAIN(s) \ + (strncmp("domain/", s, strlen("domain/")) == 0) +#define SECTION_DOMAIN_IS_SUBDOMAIN(s) \ + (strchr(s + strlen("domain/"), '/') != NULL) + +/* Here we can put custom SSSD specific checks that can not be implemented + * using libini validators */ +static int custom_sssd_checks(const char *rule_name, + struct ini_cfgobj *rules_obj, + struct ini_cfgobj *config_obj, + struct ini_errobj *errobj, + void **data) +{ + char **cfg_sections = NULL; + int num_cfg_sections; + int ret; + + /* Get all sections in configuration */ + cfg_sections = ini_get_section_list(config_obj, &num_cfg_sections, &ret); + if (ret != EOK) { + goto done; + } + + /* Check a normal domain section (not application domains) */ + for (int i = 0; i < num_cfg_sections; i++) { + if (SECTION_IS_DOMAIN(cfg_sections[i])) { + ret = check_domain_inherit_from(cfg_sections[i], config_obj, errobj); + if (ret != EOK) { + goto done; + } + + if (!SECTION_DOMAIN_IS_SUBDOMAIN(cfg_sections[i])) { + ret = check_domain_id_provider(cfg_sections[i], config_obj, errobj); + if (ret != EOK) { + goto done; + } + } + } + } + + ret = EOK; +done: + ini_free_section_list(cfg_sections); + return ret; +} + +static int sss_ini_call_validators_errobj(struct sss_ini *data, + const char *rules_path, + struct ini_errobj *errobj) +{ + int ret; + struct ini_cfgobj *rules_cfgobj = NULL; + struct ini_validator custom_sssd = { "sssd_checks", custom_sssd_checks, + NULL }; + struct ini_validator *sss_validators[] = { &custom_sssd, NULL }; + + ret = ini_rules_read_from_file(rules_path, &rules_cfgobj); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to read sssd.conf schema %d [%s]\n", ret, strerror(ret)); + goto done; + } + + ret = ini_rules_check(rules_cfgobj, data->sssd_config, sss_validators, errobj); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "ini_rules_check failed %d [%s]\n", ret, strerror(ret)); + goto done; + } + +done: + if (rules_cfgobj) ini_config_destroy(rules_cfgobj); + + return ret; +} +#endif /* HAVE_LIBINI_CONFIG_V1_3 */ + +int sss_ini_call_validators(struct sss_ini *data, + const char *rules_path) +{ +#ifdef HAVE_LIBINI_CONFIG_V1_3 + int ret; + struct ini_errobj *errobj = NULL; + + ret = ini_errobj_create(&errobj); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to create error list\n"); + goto done; + } + + ret = sss_ini_call_validators_errobj(data, + rules_path, + errobj); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get errors from validators.\n"); + goto done; + } + + /* Do not error out when validators find some issue */ + while (!ini_errobj_no_more_msgs(errobj)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "%s\n", ini_errobj_get_msg(errobj)); + ini_errobj_next(errobj); + } + + ret = EOK; + +done: + ini_errobj_destroy(&errobj); + return ret; +#else + DEBUG(SSSDBG_TRACE_FUNC, + "libini_config does not support configuration file validataion\n"); + return EOK; +#endif /* HAVE_LIBINI_CONFIG_V1_3 */ +} + +int sss_ini_call_validators_strs(TALLOC_CTX *mem_ctx, + struct sss_ini *data, + const char *rules_path, + char ***_errors, + size_t *_num_errors) +{ +#ifdef HAVE_LIBINI_CONFIG_V1_3 + TALLOC_CTX *tmp_ctx = NULL; + struct ini_errobj *errobj = NULL; + int ret; + size_t num_errors; + char **errors = NULL; + + if (_num_errors == NULL || _errors == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = ini_errobj_create(&errobj); + if (ret != EOK) { + goto done; + } + + ret = sss_ini_call_validators_errobj(data, + rules_path, + errobj); + if (ret != EOK) { + goto done; + } + num_errors = ini_errobj_count(errobj); + if (num_errors == 0) { + *_num_errors = num_errors; + goto done; + } + + errors = talloc_array(tmp_ctx, char *, num_errors); + if (errors == NULL) { + ret = ENOMEM; + goto done; + } + + for (int i = 0; i < num_errors; i++) { + errors[i] = talloc_strdup(errors, ini_errobj_get_msg(errobj)); + if (errors[i] == NULL) { + ret = ENOMEM; + goto done; + } + + ini_errobj_next(errobj); + } + + *_num_errors = num_errors; + *_errors = talloc_steal(mem_ctx, errors); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + ini_errobj_destroy(&errobj); + + return ret; + +#else + DEBUG(SSSDBG_TRACE_FUNC, + "libini_config does not support configuration file validation\n"); + + if (_num_errors == NULL || _errors == NULL) { + return EINVAL; + } + + _num_errors = 0; + return EOK; +#endif /* HAVE_LIBINI_CONFIG_V1_3 */ +} + +int sss_ini_open(struct sss_ini *self, + const char *config_file, + const char *fallback_cfg) +{ + int ret; + + if (self == NULL) { + return EINVAL; + } + + if (config_file != NULL) { + ret = sss_ini_config_file_open(self, config_file); + } else { + ret = ENOENT; + } + + switch (ret) { + case EOK: + break; + case ENOENT: + DEBUG(SSSDBG_TRACE_FUNC, "No %s.\n", config_file); + if (fallback_cfg == NULL) { + return ret; + } + + ret = sss_ini_config_file_from_mem(self, + discard_const(fallback_cfg), + strlen(fallback_cfg)); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "sss_ini_config_file_from_mem failed. Error %d\n", + ret); + } + break; + default: + DEBUG(SSSDBG_CONF_SETTINGS, + "sss_ini_config_file_open failed: Error %d\n", + ret); + sss_ini_config_print_errors(self->error_list); + break; + } + return ret; +} + +int sss_ini_read_sssd_conf(struct sss_ini *self, + const char *config_file, + const char *config_dir) +{ + errno_t ret; + + if (self == NULL) { + return EINVAL; + } + + ret = sss_ini_open(self, config_file, CONFDB_FALLBACK_CONFIG); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "The sss_ini_open failed %s: %d\n", + config_file, + ret); + return ERR_INI_OPEN_FAILED; + } + + if (sss_ini_exists(self)) { + ret = sss_ini_access_check(self); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Permission check on config file failed.\n"); + return ERR_INI_INVALID_PERMISSION; + } + } else { + DEBUG(SSSDBG_CONF_SETTINGS, + "File %1$s does not exist.\n", + (config_file ? config_file : "NULL")); + } + + ret = sss_ini_parse(self); + if (ret != EOK) { + sss_ini_config_print_errors(self->error_list); + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to parse configuration.\n"); + return ERR_INI_PARSE_FAILED; + } + + ret = sss_ini_add_snippets(self, config_dir); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Error while reading configuration directory.\n"); + return ERR_INI_ADD_SNIPPETS_FAILED; + } + + return ret; +} diff --git a/src/util/sss_ini.h b/src/util/sss_ini.h new file mode 100644 index 0000000..4e3f67f --- /dev/null +++ b/src/util/sss_ini.h @@ -0,0 +1,164 @@ +/* + SSSD + + sss_ini.c + + Authors: + Ondrej Kos <okos@redhat.com> + + Copyright (C) 2013 Red Hat + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#ifndef __SSS_INI_H__ +#define __SSS_INI_H__ + +#include <stdbool.h> + +/** + * @brief INI data structure + */ +struct sss_ini; + +/** + * @brief create new ini data object + * + * @param[in] tmp_ctx talloc context + * + * @return + * - pointer to newly allocated and initialized structure + * - NULL in case of error + */ +struct sss_ini* sss_ini_new(TALLOC_CTX *tmp_ctx); + + +/** + * @brief Open ini file or use fallback_cfg if file is not present. Include + * configuration snippets and perform access check. + * + * @param[in] self pointer to sss_ini structure + * @param[in] config_file ini file + * @param[in] config_dir directory containing ini files to be included + * + * @return + * - EOK - success + * - ERR_INI_OPEN_FAILED - sss_ini_open failed + * - ERR_INI_INVALID_PERMISSION - access check failed + * - ERR_INI_PARSE_FAILED - failed to parse configuration file + * - ERR_INI_ADD_SNIPPETS_FAILED - failed to add configuration snippets + */ +int sss_ini_read_sssd_conf(struct sss_ini *self, + const char *config_file, + const char *config_dir); + +/** + * @brief Open ini file or use fallback_cfg if file is not present + * + * @param[in] self pointer to sss_ini structure + * @param[in] config_file ini file + * @param[in] fallback_cfg string with ini content. This parameter is used + * when config_file doesn't exist or it is set to NULL + * + * @return error code + */ +int sss_ini_open(struct sss_ini *self, + const char *config_file, + const char *fallback_cfg); + +/** + * @brief Check whether sss_ini_open() reported that ini file is + * not present + * + * @param[in] self pointer to sss_ini structure + * + * @return + * - true we are using ini file + * - false file was not found + */ +bool sss_ini_exists(struct sss_ini *self); + +/** + * @brief get Cstat structure of the ini file + */ +int sss_ini_get_stat(struct sss_ini *self); + +/** + * @brief Get mtime of the ini file + */ +int sss_ini_get_mtime(struct sss_ini *self, + size_t timestr_len, + char *timestr); + +/** + * @brief Get pointer to list of snippet parsing errors + */ +struct ref_array * +sss_ini_get_ra_error_list(struct sss_ini *self); + +/** + * @brief Get pointer to list of successfully merged snippet files + */ +struct ref_array * +sss_ini_get_ra_success_list(struct sss_ini *self); + +/** + * @brief Get configuration object + */ +int sss_ini_get_cfgobj(struct sss_ini *self, + const char *section, const char *name); + +/** + * @brief Check configuration object + */ +int sss_ini_check_config_obj(struct sss_ini *self); + +/** + * @brief Get int value + */ +int sss_ini_get_int_config_value(struct sss_ini *self, + int strict, int def, int *error); + +/** + * @brief Get string value + */ +const char *sss_ini_get_string_config_value(struct sss_ini *self, + int *error); + +/** + * @brief Create LDIF + */ +int sss_confdb_create_ldif(TALLOC_CTX *mem_ctx, + struct sss_ini *self, + const char *only_section, + const char **config_ldif); + +/** + * @brief Validate sssd.conf if libini_config support it + */ +int sss_ini_call_validators(struct sss_ini *data, + const char *rules_path); + +/** + * @brief Get errors from validators in array of strings + */ +int sss_ini_call_validators_strs(TALLOC_CTX *mem_ctx, + struct sss_ini *data, + const char *rules_path, + char ***_strs, + size_t *_num_errors); + +#endif /* __SSS_INI_H__ */ diff --git a/src/util/sss_iobuf.c b/src/util/sss_iobuf.c new file mode 100644 index 0000000..bd8d2f3 --- /dev/null +++ b/src/util/sss_iobuf.c @@ -0,0 +1,452 @@ +#include <talloc.h> + +#include "util/util.h" +#include "util/sss_iobuf.h" + +/** + * @brief The iobuf structure that holds the data, its capacity and + * a pointer to the data. + * + * @see sss_iobuf_init_empty() + * @see sss_iobuf_init_readonly() + */ +struct sss_iobuf { + uint8_t *data; /* Start of the data buffer */ + + size_t dp; /* Data pointer */ + size_t size; /* Current data buffer size */ + size_t capacity; /* Maximum capacity */ +}; + +struct sss_iobuf *sss_iobuf_init_empty(TALLOC_CTX *mem_ctx, + size_t size, + size_t capacity) +{ + struct sss_iobuf *iobuf; + uint8_t *buf; + + iobuf = talloc_zero(mem_ctx, struct sss_iobuf); + if (iobuf == NULL) { + return NULL; + } + + buf = talloc_array(iobuf, uint8_t, size); + if (buf == NULL) { + talloc_free(iobuf); + return NULL; + } + + if (capacity == 0) { + capacity = SIZE_MAX / 2; + } + + iobuf->data = buf; + iobuf->size = size; + iobuf->capacity = capacity; + + return iobuf; +} + +struct sss_iobuf *sss_iobuf_init_readonly(TALLOC_CTX *mem_ctx, + const uint8_t *data, + size_t size) +{ + struct sss_iobuf *iobuf; + + iobuf = sss_iobuf_init_empty(mem_ctx, size, size); + if (iobuf == NULL) { + return NULL; + } + + if (data != NULL) { + memcpy(iobuf->data, data, size); + } + + return iobuf; +} + +struct sss_iobuf *sss_iobuf_init_steal(TALLOC_CTX *mem_ctx, + uint8_t *data, + size_t size) +{ + struct sss_iobuf *iobuf; + + iobuf = talloc_zero(mem_ctx, struct sss_iobuf); + if (iobuf == NULL) { + return NULL; + } + + iobuf->data = talloc_steal(iobuf, data); + iobuf->size = size; + iobuf->capacity = size; + + return iobuf; +} + +void sss_iobuf_cursor_reset(struct sss_iobuf *iobuf) +{ + iobuf->dp = 0; +} + +size_t sss_iobuf_get_len(struct sss_iobuf *iobuf) +{ + if (iobuf == NULL) { + return 0; + } + + return iobuf->dp; +} + +size_t sss_iobuf_get_capacity(struct sss_iobuf *iobuf) +{ + if (iobuf == NULL) { + return 0; + } + + return iobuf->capacity; +} + +size_t sss_iobuf_get_size(struct sss_iobuf *iobuf) +{ + if (iobuf == NULL) { + return 0; + } + + return iobuf->size; +} + +uint8_t *sss_iobuf_get_data(struct sss_iobuf *iobuf) +{ + if (iobuf == NULL) { + return NULL; + } + + return iobuf->data; +} + +static size_t iobuf_get_len(struct sss_iobuf *iobuf) +{ + if (iobuf == NULL) { + return 0; + } + + return (iobuf->size - iobuf->dp); +} + +static errno_t ensure_bytes(struct sss_iobuf *iobuf, + size_t nbytes) +{ + size_t wantsize; + size_t newsize; + uint8_t *newdata; + + if (iobuf == NULL) { + return EINVAL; + } + + wantsize = iobuf->dp + nbytes; + if (wantsize <= iobuf->size) { + /* Enough space already */ + return EOK; + } + + /* Else, try to extend the iobuf */ + if (wantsize > iobuf->capacity) { + /* We will never grow past capacity */ + return ENOBUFS; + } + + /* Double the size until we add at least nbytes, but stop if we double past capacity */ + for (newsize = iobuf->size; + (newsize < wantsize) && (newsize < iobuf->capacity); + newsize *= 2) + ; + + if (newsize > iobuf->capacity) { + newsize = iobuf->capacity; + } + + newdata = talloc_realloc(iobuf, iobuf->data, uint8_t, newsize); + if (newdata == NULL) { + return ENOMEM; + } + + iobuf->data = newdata; + iobuf->size = newsize; + + return EOK; +} + +static inline uint8_t *iobuf_ptr(struct sss_iobuf *iobuf) +{ + return iobuf->data + iobuf->dp; +} + +errno_t sss_iobuf_read(struct sss_iobuf *iobuf, + size_t len, + uint8_t *_buf, + size_t *_read) +{ + size_t remaining; + + if (iobuf == NULL || _buf == NULL) { + return EINVAL; + } + + remaining = iobuf_get_len(iobuf); + if (len > remaining) { + len = remaining; + } + + safealign_memcpy(_buf, iobuf_ptr(iobuf), len, &iobuf->dp); + if (_read != NULL) { + *_read = len; + } + + return EOK; +} + +errno_t sss_iobuf_read_len(struct sss_iobuf *iobuf, + size_t len, + uint8_t *_buf) +{ + size_t read_bytes; + errno_t ret; + + ret = sss_iobuf_read(iobuf, len, _buf, &read_bytes); + if (ret != EOK) { + return ret; + } + + if (read_bytes != len) { + return ENOBUFS; + } + + return EOK; +} + +errno_t sss_iobuf_write_len(struct sss_iobuf *iobuf, + uint8_t *buf, + size_t len) +{ + errno_t ret; + + if (iobuf == NULL || buf == NULL) { + return EINVAL; + } + + ret = ensure_bytes(iobuf, len); + if (ret != EOK) { + return ret; + } + + safealign_memcpy(iobuf_ptr(iobuf), buf, len, &iobuf->dp); + + return EOK; +} + +errno_t sss_iobuf_read_varlen(TALLOC_CTX *mem_ctx, + struct sss_iobuf *iobuf, + uint8_t **_out, + size_t *_len) +{ + uint8_t *out; + uint32_t len; + size_t slen; + errno_t ret; + + if (iobuf == NULL || _out == NULL || _len == NULL) { + return EINVAL; + } + + ret = sss_iobuf_read_uint32(iobuf, &len); + if (ret != EOK) { + return ret; + } + + if (len == 0) { + *_out = NULL; + *_len = 0; + return EOK; + } + + out = talloc_array(mem_ctx, uint8_t, len); + if (out == NULL) { + return ENOMEM; + } + + slen = len; + ret = sss_iobuf_read_len(iobuf, slen, out); + if (ret != EOK) { + talloc_free(out); + return ret; + } + + *_out = out; + *_len = slen; + + return EOK; +} + +errno_t sss_iobuf_write_varlen(struct sss_iobuf *iobuf, + uint8_t *data, + size_t len) +{ + errno_t ret; + + if (iobuf == NULL || (data == NULL && len != 0)) { + return EINVAL; + } + + ret = sss_iobuf_write_uint32(iobuf, len); + if (ret != EOK) { + return ret; + } + + if (len == 0) { + return EOK; + } + + return sss_iobuf_write_len(iobuf, data, len); +} + +errno_t sss_iobuf_read_iobuf(TALLOC_CTX *mem_ctx, + struct sss_iobuf *iobuf, + struct sss_iobuf **_out) +{ + struct sss_iobuf *out; + uint8_t *data; + size_t len; + errno_t ret; + + ret = sss_iobuf_read_varlen(NULL, iobuf, &data, &len); + if (ret != EOK) { + return ret; + } + + out = sss_iobuf_init_steal(mem_ctx, data, len); + if (out == NULL) { + return ENOMEM; + } + + *_out = out; + + return EOK; +} + +errno_t sss_iobuf_write_iobuf(struct sss_iobuf *iobuf, + struct sss_iobuf *data) +{ + return sss_iobuf_write_varlen(iobuf, data->data, data->size); +} + +errno_t sss_iobuf_read_uint8(struct sss_iobuf *iobuf, + uint8_t *_val) +{ + SAFEALIGN_COPY_UINT8_CHECK(_val, iobuf_ptr(iobuf), + iobuf->capacity, &iobuf->dp); + return EOK; +} + +errno_t sss_iobuf_read_uint32(struct sss_iobuf *iobuf, + uint32_t *_val) +{ + SAFEALIGN_COPY_UINT32_CHECK(_val, iobuf_ptr(iobuf), + iobuf->capacity, &iobuf->dp); + return EOK; +} + +errno_t sss_iobuf_read_int32(struct sss_iobuf *iobuf, + int32_t *_val) +{ + SAFEALIGN_COPY_INT32_CHECK(_val, iobuf_ptr(iobuf), + iobuf->capacity, &iobuf->dp); + return EOK; +} + +errno_t sss_iobuf_write_uint8(struct sss_iobuf *iobuf, + uint8_t val) +{ + errno_t ret; + + ret = ensure_bytes(iobuf, sizeof(uint8_t)); + if (ret != EOK) { + return ret; + } + + SAFEALIGN_SETMEM_UINT8(iobuf_ptr(iobuf), val, &iobuf->dp); + return EOK; +} + +errno_t sss_iobuf_write_uint32(struct sss_iobuf *iobuf, + uint32_t val) +{ + errno_t ret; + + ret = ensure_bytes(iobuf, sizeof(uint32_t)); + if (ret != EOK) { + return ret; + } + + SAFEALIGN_SETMEM_UINT32(iobuf_ptr(iobuf), val, &iobuf->dp); + return EOK; +} + +errno_t sss_iobuf_write_int32(struct sss_iobuf *iobuf, + int32_t val) +{ + errno_t ret; + + ret = ensure_bytes(iobuf, sizeof(int32_t)); + if (ret != EOK) { + return ret; + } + + SAFEALIGN_SETMEM_INT32(iobuf_ptr(iobuf), val, &iobuf->dp); + return EOK; +} + +errno_t sss_iobuf_read_stringz(struct sss_iobuf *iobuf, + const char **_out) +{ + uint8_t *end; + size_t len; + + if (iobuf == NULL) { + return EINVAL; + } + + if (_out == NULL) { + return EINVAL; + } + + *_out = NULL; + + end = memchr(iobuf_ptr(iobuf), '\0', sss_iobuf_get_size(iobuf)); + if (end == NULL) { + return EINVAL; + } + + len = end + 1 - iobuf_ptr(iobuf); + if (sss_iobuf_get_size(iobuf) < len) { + return EINVAL; + } + + *_out = (const char *) iobuf_ptr(iobuf); + iobuf->dp += len; + return EOK; +} + +errno_t sss_iobuf_write_stringz(struct sss_iobuf *iobuf, + const char *str) +{ + if (iobuf == NULL || str == NULL) { + return EINVAL; + } + + SAFEALIGN_MEMCPY_CHECK(iobuf_ptr(iobuf), + str, strlen(str)+1, + sss_iobuf_get_size(iobuf), + &iobuf->dp); + return EOK; +} diff --git a/src/util/sss_iobuf.h b/src/util/sss_iobuf.h new file mode 100644 index 0000000..01f0f45 --- /dev/null +++ b/src/util/sss_iobuf.h @@ -0,0 +1,201 @@ +#ifndef __SSS_IOBUF_H_ +#define __SSS_IOBUF_H_ + +#include <talloc.h> +#include <stdint.h> +#include <errno.h> + +#include "util/util_errors.h" + +struct sss_iobuf; + +/* + * @brief Allocate an empty IO buffer + * + * @param[in] mem_ctx The talloc context that owns the iobuf + * + * When this buffer is written into, but the capacity is exceeded, the write + * function will return an error. + * + * @param[in] mem_ctx The talloc context that owns the iobuf + * @param[in] size The size of the data buffer + * @param[in] capacity The maximum capacity the buffer can grow into. + * Use 0 for an 'unlimited' buffer that will grow + * until SIZE_MAX/2. + * + * @warning The allocated data storage will not be zeroed. + * + * @return The newly created buffer on success or NULL on an error. + * + */ +struct sss_iobuf *sss_iobuf_init_empty(TALLOC_CTX *mem_ctx, + size_t size, + size_t capacity); + +/* + * @brief Allocate an IO buffer with a fixed size + * + * This function is useful for parsing an input buffer from an existing + * buffer pointed to by data. + * + * The iobuf does not assume ownership of the data buffer in talloc terms, + * but copies the data instead. + * + * @param[in] mem_ctx The talloc context that owns the iobuf + * @param[in] data The data to initialize the IO buffer with. This + * data is copied into the iobuf-owned buffer. + * @param[in] size The size of the data buffer + * + * @warning The allocated data storage will not be zeroed. + * + * @return The newly created buffer on success or NULL on an error. + */ +struct sss_iobuf *sss_iobuf_init_readonly(TALLOC_CTX *mem_ctx, + const uint8_t *data, + size_t size); + +/* + * @brief Allocate an IO buffer with a fixed size, stealing input data. + * + * This function is useful for parsing an input buffer from an existing + * buffer pointed to by data. + * + * The iobuf assumes ownership of the data buffer. + * + * @param[in] mem_ctx The talloc context that owns the iobuf + * @param[in] data The data to initialize the IO buffer with. + * @param[in] size The size of the data buffer + * + * @return The newly created buffer on success or NULL on an error. + */ +struct sss_iobuf *sss_iobuf_init_steal(TALLOC_CTX *mem_ctx, + uint8_t *data, + size_t size); + +/* + * @brief Reset internal cursor of the IO buffer (seek to the start) + */ +void sss_iobuf_cursor_reset(struct sss_iobuf *iobuf); + +/* + * @brief Returns the number of bytes currently stored in the iobuf + * + * @return The number of bytes (the data pointer offset) + */ +size_t sss_iobuf_get_len(struct sss_iobuf *iobuf); + +/* + * @brief Returns the capacity of the IO buffer + * + * @return The capacity of the IO buffer. Returns zero + * for an unlimited buffer. + */ +size_t sss_iobuf_get_capacity(struct sss_iobuf *iobuf); + +/* + * @brief Returns the current size of the IO buffer + */ +size_t sss_iobuf_get_size(struct sss_iobuf *iobuf); + +/* + * @brief Returns the data pointer of the IO buffer + */ +uint8_t *sss_iobuf_get_data(struct sss_iobuf *iobuf); + +/* + * @brief Read from an IO buffer + * + * Read up to len bytes from an IO buffer. It is not an error to request + * more bytes than the buffer actually has - the function will succeed, but + * return the actual number of bytes read. Reading from an empty buffer just + * returns zero bytes read. + * + * @param[in] iobuf The IO buffer to read from + * @param[in] len The maximum number of bytes to read + * @param[out] _buf The buffer to read data into from iobuf + * @param[out] _read The actual number of bytes read from IO buffer. + * + * @return EOK on success, errno otherwise + */ +errno_t sss_iobuf_read(struct sss_iobuf *iobuf, + size_t len, + uint8_t *_buf, + size_t *_read); + +/* + * @brief Read an exact number of bytes from an IO buffer + * + * Read exactly len bytes from an IO buffer. If the buffer contains fewer + * bytes than len, ENOBUFS is returned. + * + * @param[in] iobuf The IO buffer to read from + * @param[in] len The maximum number of bytes to read + * @param[out] _buf The buffer to read data into from iobuf + * + * @return EOK on success, errno otherwise + */ +errno_t sss_iobuf_read_len(struct sss_iobuf *iobuf, + size_t len, + uint8_t *_buf); + +/* + * @brief Write into an IO buffer + * + * Attempts to write len bytes into the iobuf. If the capacity is exceeded, + * the iobuf module tries to extend the buffer up to the maximum capacity. + * + * If reallocating the internal buffer fails, the data pointers are not + * touched. + * + * @param[in] iobuf The IO buffer to write to + * @param[in] buf The data to write into the buffer + * @param[in] len The number of bytes to write + * + * @return EOK on success, errno otherwise. Notably returns ENOBUFS if + * the buffer capacity is exceeded. + */ +errno_t sss_iobuf_write_len(struct sss_iobuf *iobuf, + uint8_t *buf, + size_t len); + +errno_t sss_iobuf_read_varlen(TALLOC_CTX *mem_ctx, + struct sss_iobuf *iobuf, + uint8_t **_out, + size_t *_len); + +errno_t sss_iobuf_write_varlen(struct sss_iobuf *iobuf, + uint8_t *data, + size_t len); + +errno_t sss_iobuf_read_iobuf(TALLOC_CTX *mem_ctx, + struct sss_iobuf *iobuf, + struct sss_iobuf **_out); + +errno_t sss_iobuf_write_iobuf(struct sss_iobuf *iobuf, + struct sss_iobuf *data); + +errno_t sss_iobuf_read_uint8(struct sss_iobuf *iobuf, + uint8_t *_val); + +errno_t sss_iobuf_write_uint8(struct sss_iobuf *iobuf, + uint8_t val); + +errno_t sss_iobuf_read_uint32(struct sss_iobuf *iobuf, + uint32_t *_val); + +errno_t sss_iobuf_write_uint32(struct sss_iobuf *iobuf, + uint32_t val); + +errno_t sss_iobuf_read_int32(struct sss_iobuf *iobuf, + int32_t *_val); + +errno_t sss_iobuf_write_int32(struct sss_iobuf *iobuf, + int32_t val); + +errno_t sss_iobuf_read_stringz(struct sss_iobuf *iobuf, + const char **_out); + +errno_t sss_iobuf_write_stringz(struct sss_iobuf *iobuf, + const char *str); + +#endif /* __SSS_IOBUF_H_ */ diff --git a/src/util/sss_krb5.c b/src/util/sss_krb5.c new file mode 100644 index 0000000..3f57e5b --- /dev/null +++ b/src/util/sss_krb5.c @@ -0,0 +1,1384 @@ +/* + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009-2010 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#include <ctype.h> +#include <stdio.h> +#include <errno.h> +#include <talloc.h> +#include <profile.h> + +#include "config.h" + +#include "util/sss_iobuf.h" +#include "util/util.h" +#include "util/util_errors.h" +#include "util/sss_krb5.h" + +static char * +sss_krb5_get_primary(TALLOC_CTX *mem_ctx, + const char *pattern, + const char *hostname) +{ + char *primary; + char *dot; + char *c; + char *shortname; + + if (strcmp(pattern, "%S$") == 0) { + shortname = talloc_strdup(mem_ctx, hostname); + if (!shortname) return NULL; + + dot = strchr(shortname, '.'); + if (dot) { + *dot = '\0'; + } + + for (c=shortname; *c != '\0'; ++c) { + *c = toupper(*c); + } + + /* The samAccountName is recommended to be less than 20 characters. + * This is only for users and groups. For machine accounts, + * the real limit is caused by NetBIOS protocol. + * NetBIOS names are limited to 16 (15 + $) + * https://support.microsoft.com/en-us/help/163409/netbios-suffixes-16th-character-of-the-netbios-name + */ + primary = talloc_asprintf(mem_ctx, "%.15s$", shortname); + talloc_free(shortname); + return primary; + } + + return talloc_asprintf(mem_ctx, pattern, hostname); +} + +const char *sss_printable_keytab_name(krb5_context ctx, const char *keytab_name) +{ + /* sss_printable_keytab_name() output is expected to be used + for logging purposes only. Thus it is non-critical to provide + krb5_kt_default_name() with a buffer which is potentially less then + actual file path. 1024 is chosen to be 'large enough' to fit default + keytab name for any sensible configuration. + (And while it is tempting to use PATH_MAX here it would be misuse + of this posix limit.) + */ + static char buff[1024]; + + if (keytab_name) { + return keytab_name; + } + + if (krb5_kt_default_name(ctx, buff, sizeof(buff)) != 0) { + return "-default keytab-"; + } + + return buff; +} + +errno_t select_principal_from_keytab(TALLOC_CTX *mem_ctx, + const char *hostname, + const char *desired_realm, + const char *keytab_name, + char **_principal, + char **_primary, + char **_realm) +{ + krb5_error_code kerr = 0; + krb5_context krb_ctx = NULL; + krb5_keytab keytab = NULL; + krb5_principal client_princ = NULL; + TALLOC_CTX *tmp_ctx; + char *primary = NULL; + char *realm = NULL; + int i = 0; + errno_t ret; + char *principal_string; + const char *realm_name; + int realm_len; + const char *error_message = NULL; + + /** + * The %s conversion is passed as-is, the %S conversion is translated to + * "short host name" + * + * Priority of lookup: + * - our.hostname@REALM or host/our.hostname@REALM depending on the input + * - SHORT.HOSTNAME$@REALM (AD domain) + * - host/our.hostname@REALM + * - foobar$@REALM (AD domain) + * - host/foobar@REALM + * - host/foo@BAR + * - pick the first principal in the keytab + */ + const char *primary_patterns[] = {"%s", "%S$", "host/%s", "*$", "host/*", + "host/*", NULL}; + const char *realm_patterns[] = {"%s", "%s", "%s", "%s", "%s", + NULL, NULL}; + + DEBUG(SSSDBG_FUNC_DATA, + "trying to select the most appropriate principal from keytab\n"); + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed\n"); + return ENOMEM; + } + + kerr = sss_krb5_init_context(&krb_ctx); + if (kerr) { + error_message = "Failed to init Kerberos context"; + ret = EFAULT; + goto done; + } + + if (keytab_name != NULL) { + kerr = krb5_kt_resolve(krb_ctx, keytab_name, &keytab); + } else { + kerr = krb5_kt_default(krb_ctx, &keytab); + } + if (kerr) { + const char *krb5_err_msg = sss_krb5_get_error_message(krb_ctx, kerr); + error_message = talloc_strdup(tmp_ctx, krb5_err_msg); + sss_krb5_free_error_message(krb_ctx, krb5_err_msg); + ret = EFAULT; + goto done; + } + + if (!desired_realm) { + desired_realm = "*"; + } + if (!hostname) { + hostname = "*"; + } + + do { + if (primary_patterns[i]) { + primary = sss_krb5_get_primary(tmp_ctx, + primary_patterns[i], + hostname); + if (primary == NULL) { + ret = ENOMEM; + goto done; + } + } else { + primary = NULL; + } + if (realm_patterns[i]) { + realm = talloc_asprintf(tmp_ctx, realm_patterns[i], desired_realm); + if (realm == NULL) { + ret = ENOMEM; + goto done; + } + } else { + realm = NULL; + } + + kerr = find_principal_in_keytab(krb_ctx, keytab, primary, realm, + &client_princ); + talloc_zfree(primary); + talloc_zfree(realm); + if (kerr == 0) { + break; + } + if (client_princ != NULL) { + krb5_free_principal(krb_ctx, client_princ); + client_princ = NULL; + } + i++; + } while(primary_patterns[i-1] != NULL || realm_patterns[i-1] != NULL); + + if (kerr == 0) { + if (_principal) { + kerr = krb5_unparse_name(krb_ctx, client_princ, &principal_string); + if (kerr) { + error_message = "krb5_unparse_name failed (_principal)"; + ret = EINVAL; + goto done; + } + + *_principal = talloc_strdup(mem_ctx, principal_string); + sss_krb5_free_unparsed_name(krb_ctx, principal_string); + if (!*_principal) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_FUNC_DATA, "Selected principal: %s\n", *_principal); + } + + if (_primary) { + kerr = sss_krb5_unparse_name_flags(krb_ctx, client_princ, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, + &principal_string); + if (kerr) { + if (_principal) talloc_zfree(*_principal); + error_message = "krb5_unparse_name failed (_primary)"; + ret = EINVAL; + goto done; + } + + *_primary = talloc_strdup(mem_ctx, principal_string); + sss_krb5_free_unparsed_name(krb_ctx, principal_string); + if (!*_primary) { + if (_principal) talloc_zfree(*_principal); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_FUNC_DATA, "Selected primary: %s\n", *_primary); + } + + if (_realm) { + sss_krb5_princ_realm(krb_ctx, client_princ, + &realm_name, + &realm_len); + if (realm_len == 0) { + error_message = "sss_krb5_princ_realm failed"; + if (_principal) talloc_zfree(*_principal); + if (_primary) talloc_zfree(*_primary); + ret = EINVAL; + goto done; + } + + *_realm = talloc_asprintf(mem_ctx, "%.*s", + realm_len, realm_name); + if (!*_realm) { + if (_principal) talloc_zfree(*_principal); + if (_primary) talloc_zfree(*_primary); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_FUNC_DATA, "Selected realm: %s\n", *_realm); + } + + ret = EOK; + } else { + ret = ERR_KRB5_PRINCIPAL_NOT_FOUND; + } + +done: + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to read keytab [%s]: %s\n", + sss_printable_keytab_name(krb_ctx, keytab_name), + (error_message ? error_message : sss_strerror(ret))); + + sss_log(SSS_LOG_ERR, "Failed to read keytab [%s]: %s\n", + sss_printable_keytab_name(krb_ctx, keytab_name), + (error_message ? error_message : sss_strerror(ret))); + } + if (keytab) krb5_kt_close(krb_ctx, keytab); + if (client_princ) krb5_free_principal(krb_ctx, client_princ); + if (krb_ctx) krb5_free_context(krb_ctx); + talloc_free(tmp_ctx); + return ret; +} + +enum matching_mode {MODE_NORMAL, MODE_PREFIX, MODE_POSTFIX}; +/** + * We only have primary and instances stored separately, we need to + * join them to one string and compare that string. + * + * @param ctx Kerberos context + * @param principal principal we want to match + * @param pattern_primary primary part of the principal we want to + * perform matching against. It is possible to use * wildcard + * at the beginning or at the end of the string. If NULL, it + * will act as "*" + * @param pattern_realm realm part of the principal we want to perform + * the matching against. If NULL, it will act as "*" + */ +static bool match_principal(krb5_context ctx, + krb5_principal principal, + const char *pattern_primary, + const char *pattern_realm) +{ + char *primary = NULL; + char *primary_str = NULL; + int primary_str_len = 0; + int tmp_len; + int len_diff; + const char *realm_name; + int realm_len; + + enum matching_mode mode = MODE_NORMAL; + TALLOC_CTX *tmp_ctx; + bool ret = false; + + sss_krb5_princ_realm(ctx, principal, &realm_name, &realm_len); + if (realm_len == 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "sss_krb5_princ_realm failed.\n"); + return false; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed\n"); + return false; + } + + if (pattern_primary) { + tmp_len = strlen(pattern_primary); + if (pattern_primary[tmp_len-1] == '*') { + mode = MODE_PREFIX; + primary_str = talloc_strdup(tmp_ctx, pattern_primary); + primary_str[tmp_len-1] = '\0'; + primary_str_len = tmp_len-1; + } else if (pattern_primary[0] == '*') { + mode = MODE_POSTFIX; + primary_str = talloc_strdup(tmp_ctx, pattern_primary+1); + primary_str_len = tmp_len-1; + } + + sss_krb5_unparse_name_flags(ctx, principal, KRB5_PRINCIPAL_UNPARSE_NO_REALM, + &primary); + + len_diff = strlen(primary)-primary_str_len; + + if ((mode == MODE_NORMAL && + strcmp(primary, pattern_primary) != 0) || + (mode == MODE_PREFIX && + strncmp(primary, primary_str, primary_str_len) != 0) || + (mode == MODE_POSTFIX && + strcmp(primary+len_diff, primary_str) != 0)) { + goto done; + } + } + + if (!pattern_realm || (realm_len == strlen(pattern_realm) && + strncmp(realm_name, pattern_realm, realm_len) == 0)) { + DEBUG(SSSDBG_TRACE_LIBS, + "Principal matched to the sample (%s@%s).\n", pattern_primary, + pattern_realm); + ret = true; + } + +done: + free(primary); + talloc_free(tmp_ctx); + return ret; +} + +krb5_error_code find_principal_in_keytab(krb5_context ctx, + krb5_keytab keytab, + const char *pattern_primary, + const char *pattern_realm, + krb5_principal *princ) +{ + krb5_error_code kerr; + krb5_error_code kerr_dbg; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + bool principal_found = false; + + memset(&cursor, 0, sizeof(cursor)); + memset(&entry, 0, sizeof(entry)); + + kerr = krb5_kt_start_seq_get(ctx, keytab, &cursor); + if (kerr != 0) { + const char *krb5_err_msg = sss_krb5_get_error_message(ctx, kerr); + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_kt_start_seq_get failed: %s\n", + krb5_err_msg); + sss_log(SSS_LOG_ERR, "krb5_kt_start_seq_get failed: %s\n", + krb5_err_msg); + sss_krb5_free_error_message(ctx, krb5_err_msg); + return kerr; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Trying to find principal %s@%s in keytab.\n", pattern_primary, pattern_realm); + while ((kerr = krb5_kt_next_entry(ctx, keytab, &entry, &cursor)) == 0) { + principal_found = match_principal(ctx, entry.principal, pattern_primary, pattern_realm); + if (principal_found) { + break; + } + + kerr_dbg = sss_krb5_free_keytab_entry_contents(ctx, &entry); + if (kerr_dbg != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to free keytab entry.\n"); + } + memset(&entry, 0, sizeof(entry)); + } + + /* Close the keytab here. Even though we're using cursors, the file + * handle is stored in the krb5_keytab structure, and it gets + * overwritten by other keytab calls, creating a leak. */ + kerr_dbg = krb5_kt_end_seq_get(ctx, keytab, &cursor); + if (kerr_dbg != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_kt_end_seq_get failed.\n"); + } + + if (principal_found) { + kerr = krb5_copy_principal(ctx, entry.principal, princ); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "krb5_copy_principal failed.\n"); + sss_log(SSS_LOG_ERR, "krb5_copy_principal failed.\n"); + } + kerr_dbg = sss_krb5_free_keytab_entry_contents(ctx, &entry); + if (kerr_dbg != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to free keytab entry.\n"); + } + } else { + /* If principal was not found then 'kerr' was set */ + if (kerr != KRB5_KT_END) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error while reading keytab using krb5_kt_next_entry()\n"); + sss_log(SSS_LOG_ERR, + "Error while reading keytab using krb5_kt_next_entry()\n"); + } else { + kerr = KRB5_KT_NOTFOUND; + DEBUG(SSSDBG_TRACE_FUNC, + "No principal matching %s@%s found in keytab.\n", + pattern_primary, pattern_realm); + } + } + + return kerr; +} + +static const char *__SSS_KRB5_NO_ERR_MSG_AVAILABLE = "- no krb5 error message available -"; + +const char *KRB5_CALLCONV sss_krb5_get_error_message(krb5_context ctx, + krb5_error_code ec) +{ +#ifdef HAVE_KRB5_GET_ERROR_MESSAGE + return krb5_get_error_message(ctx, ec); +#else + int size = sizeof("Kerberos error [XXXXXXXXXXXX]"); + char *s = malloc(sizeof(char) * size); + if (s != NULL) { + int ret = snprintf(s, size, "Kerberos error [%12d]", ec); + if (ret < 0 || ret >= size) { + free(s); + s = NULL; + } + } + return (s ? s : __SSS_KRB5_NO_ERR_MSG_AVAILABLE); +#endif +} + +void KRB5_CALLCONV sss_krb5_free_error_message(krb5_context ctx, const char *s) +{ + if (s == __SSS_KRB5_NO_ERR_MSG_AVAILABLE) { + return; + } + +#ifdef HAVE_KRB5_GET_ERROR_MESSAGE + if (s != NULL) { + krb5_free_error_message(ctx, s); + } +#else + free(s); +#endif + + return; +} + +krb5_error_code KRB5_CALLCONV sss_krb5_get_init_creds_opt_alloc( + krb5_context context, + krb5_get_init_creds_opt **opt) +{ +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC + return krb5_get_init_creds_opt_alloc(context, opt); +#else + *opt = calloc(1, sizeof(krb5_get_init_creds_opt)); + if (*opt == NULL) { + return ENOMEM; + } + krb5_get_init_creds_opt_init(*opt); + + return 0; +#endif +} + +void KRB5_CALLCONV sss_krb5_get_init_creds_opt_free (krb5_context context, + krb5_get_init_creds_opt *opt) +{ +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC + krb5_get_init_creds_opt_free(context, opt); +#else + free(opt); +#endif + + return; +} + +void KRB5_CALLCONV sss_krb5_free_unparsed_name(krb5_context context, char *name) +{ +#ifdef HAVE_KRB5_FREE_UNPARSED_NAME + if (name != NULL) { + krb5_free_unparsed_name(context, name); + } +#else + if (name != NULL) { + memset(name, 0, strlen(name)); + free(name); + } +#endif +} + + +krb5_error_code KRB5_CALLCONV sss_krb5_get_init_creds_opt_set_expire_callback( + krb5_context context, + krb5_get_init_creds_opt *opt, + krb5_expire_callback_func cb, + void *data) +{ +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_EXPIRE_CALLBACK + return krb5_get_init_creds_opt_set_expire_callback(context, opt, cb, data); +#else + DEBUG(SSSDBG_FUNC_DATA, + "krb5_get_init_creds_opt_set_expire_callback not available.\n"); + return 0; +#endif +} + +errno_t check_fast(const char *str, bool *use_fast) +{ +#if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_FLAGS + if (strcasecmp(str, "never") == 0 ) { + *use_fast = false; + } else if (strcasecmp(str, "try") == 0 || strcasecmp(str, "demand") == 0) { + *use_fast = true; + } else { + sss_log(SSS_LOG_ALERT, "Unsupported value [%s] for option krb5_use_fast," + "please use never, try, or demand.\n", str); + return EINVAL; + } + + return EOK; +#else + sss_log(SSS_LOG_ALERT, "This build of sssd does not support FAST. " + "Please remove option krb5_use_fast.\n"); + return EINVAL; +#endif +} + +krb5_error_code KRB5_CALLCONV sss_krb5_get_init_creds_opt_set_fast_ccache_name( + krb5_context context, + krb5_get_init_creds_opt *opt, + const char *fast_ccache_name) +{ +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_CCACHE_NAME + return krb5_get_init_creds_opt_set_fast_ccache_name(context, opt, + fast_ccache_name); +#else + DEBUG(SSSDBG_FUNC_DATA, + "krb5_get_init_creds_opt_set_fast_ccache_name not available.\n"); + return 0; +#endif +} + +krb5_error_code KRB5_CALLCONV sss_krb5_get_init_creds_opt_set_fast_flags( + krb5_context context, + krb5_get_init_creds_opt *opt, + krb5_flags flags) +{ +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_FLAGS + return krb5_get_init_creds_opt_set_fast_flags(context, opt, flags); +#else + DEBUG(SSSDBG_FUNC_DATA, + "krb5_get_init_creds_opt_set_fast_flags not available.\n"); + return 0; +#endif +} + + +#ifndef HAVE_KRB5_UNPARSE_NAME_FLAGS +#ifndef REALM_SEP +#define REALM_SEP '@' +#endif +#ifndef COMPONENT_SEP +#define COMPONENT_SEP '/' +#endif + +static int +sss_krb5_copy_component_quoting(char *dest, const krb5_data *src, int flags) +{ + int j; + const char *cp = src->data; + char *q = dest; + int length = src->length; + + if (flags & KRB5_PRINCIPAL_UNPARSE_DISPLAY) { + memcpy(dest, src->data, src->length); + return src->length; + } + + for (j=0; j < length; j++,cp++) { + int no_realm = (flags & KRB5_PRINCIPAL_UNPARSE_NO_REALM) && + !(flags & KRB5_PRINCIPAL_UNPARSE_SHORT); + + switch (*cp) { + case REALM_SEP: + if (no_realm) { + *q++ = *cp; + break; + } + case COMPONENT_SEP: + case '\\': + *q++ = '\\'; + *q++ = *cp; + break; + case '\t': + *q++ = '\\'; + *q++ = 't'; + break; + case '\n': + *q++ = '\\'; + *q++ = 'n'; + break; + case '\b': + *q++ = '\\'; + *q++ = 'b'; + break; + case '\0': + *q++ = '\\'; + *q++ = '0'; + break; + default: + *q++ = *cp; + } + } + return q - dest; +} + +static int +sss_krb5_component_length_quoted(const krb5_data *src, int flags) +{ + const char *cp = src->data; + int length = src->length; + int j; + int size = length; + + if ((flags & KRB5_PRINCIPAL_UNPARSE_DISPLAY) == 0) { + int no_realm = (flags & KRB5_PRINCIPAL_UNPARSE_NO_REALM) && + !(flags & KRB5_PRINCIPAL_UNPARSE_SHORT); + + for (j = 0; j < length; j++,cp++) + if ((!no_realm && *cp == REALM_SEP) || + *cp == COMPONENT_SEP || + *cp == '\0' || *cp == '\\' || *cp == '\t' || + *cp == '\n' || *cp == '\b') + size++; + } + + return size; +} + +#endif /* HAVE_KRB5_UNPARSE_NAME_FLAGS */ + + +krb5_error_code +sss_krb5_parse_name_flags(krb5_context context, const char *name, int flags, + krb5_principal *principal) +{ +#ifdef HAVE_KRB5_PARSE_NAME_FLAGS + return krb5_parse_name_flags(context, name, flags, principal); +#else + if (flags != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "krb5_parse_name_flags not available on " \ + "this plattform, names are parsed " \ + "without flags. Some features like " \ + "enterprise principals might not work " \ + "as expected.\n"); + } + + return krb5_parse_name(context, name, principal); +#endif +} + +krb5_error_code +sss_krb5_unparse_name_flags(krb5_context context, krb5_const_principal principal, + int flags, char **name) +{ +#ifdef HAVE_KRB5_UNPARSE_NAME_FLAGS + return krb5_unparse_name_flags(context, principal, flags, name); +#else + char *cp, *q; + int i; + int length; + krb5_int32 nelem; + unsigned int totalsize = 0; + char *default_realm = NULL; + krb5_error_code ret = 0; + + if (name != NULL) + *name = NULL; + + if (!principal || !name) + return KRB5_PARSE_MALFORMED; + + if (flags & KRB5_PRINCIPAL_UNPARSE_SHORT) { + /* omit realm if local realm */ + krb5_principal_data p; + + ret = krb5_get_default_realm(context, &default_realm); + if (ret != 0) + goto cleanup; + + krb5_princ_realm(context, &p)->length = strlen(default_realm); + krb5_princ_realm(context, &p)->data = default_realm; + + if (krb5_realm_compare(context, &p, principal)) + flags |= KRB5_PRINCIPAL_UNPARSE_NO_REALM; + } + + if ((flags & KRB5_PRINCIPAL_UNPARSE_NO_REALM) == 0) { + totalsize += sss_krb5_component_length_quoted(krb5_princ_realm(context, + principal), + flags); + totalsize++; + } + + nelem = krb5_princ_size(context, principal); + for (i = 0; i < (int) nelem; i++) { + cp = krb5_princ_component(context, principal, i)->data; + totalsize += sss_krb5_component_length_quoted(krb5_princ_component(context, principal, i), flags); + totalsize++; + } + if (nelem == 0) + totalsize++; + + *name = malloc(totalsize); + + if (!*name) { + ret = ENOMEM; + goto cleanup; + } + + q = *name; + + for (i = 0; i < (int) nelem; i++) { + cp = krb5_princ_component(context, principal, i)->data; + length = krb5_princ_component(context, principal, i)->length; + q += sss_krb5_copy_component_quoting(q, + krb5_princ_component(context, + principal, + i), + flags); + *q++ = COMPONENT_SEP; + } + + if (i > 0) + q--; + if ((flags & KRB5_PRINCIPAL_UNPARSE_NO_REALM) == 0) { + *q++ = REALM_SEP; + q += sss_krb5_copy_component_quoting(q, krb5_princ_realm(context, principal), flags); + } + *q++ = '\0'; + +cleanup: + free(default_realm); + + return ret; +#endif /* HAVE_KRB5_UNPARSE_NAME_FLAGS */ +} + +void sss_krb5_get_init_creds_opt_set_canonicalize(krb5_get_init_creds_opt *opts, + int canonicalize) +{ + /* FIXME: The extra check for HAVE_KRB5_TICKET_TIMES is a workaround due to Heimdal + * defining krb5_get_init_creds_opt_set_canonicalize() with a different set of + * arguments. We should use a better configure check in the future. + */ +#if defined(HAVE_KRB5_GET_INIT_CREDS_OPT_SET_CANONICALIZE) && defined(HAVE_KRB5_TICKET_TIMES) + krb5_get_init_creds_opt_set_canonicalize(opts, canonicalize); +#else + DEBUG(SSSDBG_OP_FAILURE, "Kerberos principal canonicalization is not available!\n"); +#endif +} + +#ifdef HAVE_KRB5_PRINCIPAL_GET_REALM +void sss_krb5_princ_realm(krb5_context context, krb5_const_principal princ, + const char **realm, int *len) +{ + const char *realm_str = krb5_principal_get_realm(context, princ); + + if (realm_str != NULL) { + *realm = realm_str; + *len = strlen(realm_str); + } else { + *realm = NULL; + *len = 0; + } +} +#else +void sss_krb5_princ_realm(krb5_context context, krb5_const_principal princ, + const char **realm, int *len) +{ + const krb5_data *data; + + data = krb5_princ_realm(context, princ); + if (data) { + *realm = data->data; + *len = data->length; + } else { + *realm = NULL; + *len = 0; + } +} +#endif + +krb5_error_code +sss_krb5_free_keytab_entry_contents(krb5_context context, + krb5_keytab_entry *entry) +{ +#ifdef HAVE_KRB5_FREE_KEYTAB_ENTRY_CONTENTS + return krb5_free_keytab_entry_contents(context, entry); +#else + return krb5_kt_free_entry(context, entry); +#endif +} + + +#ifdef HAVE_KRB5_SET_TRACE_CALLBACK + +#ifndef HAVE_KRB5_TRACE_INFO +/* krb5-1.10 had struct krb5_trace_info, 1.11 has type named krb5_trace_info */ +typedef struct krb5_trace_info krb5_trace_info; +#endif /* HAVE_KRB5_TRACE_INFO */ + +static void +sss_child_krb5_trace_cb(krb5_context context, + const krb5_trace_info *info, void *data) +{ + if (info == NULL) { + /* Null info means destroy the callback data. */ + return; + } + + DEBUG(SSSDBG_TRACE_ALL, "%s\n", info->message); +} + +errno_t +sss_child_set_krb5_tracing(krb5_context ctx) +{ + return krb5_set_trace_callback(ctx, sss_child_krb5_trace_cb, NULL); +} +#else /* HAVE_KRB5_SET_TRACE_CALLBACK */ +errno_t +sss_child_set_krb5_tracing(krb5_context ctx) +{ + DEBUG(SSSDBG_CONF_SETTINGS, "krb5 tracing is not available\n"); + return 0; +} +#endif /* HAVE_KRB5_SET_TRACE_CALLBACK */ + +krb5_error_code sss_krb5_find_authdata(krb5_context context, + krb5_authdata *const *ticket_authdata, + krb5_authdata *const *ap_req_authdata, + krb5_authdatatype ad_type, + krb5_authdata ***results) +{ +#ifdef HAVE_KRB5_FIND_AUTHDATA + return krb5_find_authdata(context, ticket_authdata, ap_req_authdata, + ad_type, results); +#else + return ENOTSUP; +#endif +} + +krb5_error_code sss_extract_pac(krb5_context ctx, + krb5_ccache ccache, + krb5_principal server_principal, + krb5_principal client_principal, + krb5_keytab keytab, + uint32_t check_pac_flags, + krb5_authdata ***_pac_authdata) +{ + krb5_error_code kerr; + krb5_creds mcred; + krb5_creds cred; + krb5_authdata **pac_authdata = NULL; + krb5_pac pac = NULL; + krb5_ticket *ticket = NULL; + krb5_keytab_entry entry; + + memset(&entry, 0, sizeof(entry)); + memset(&mcred, 0, sizeof(mcred)); + memset(&cred, 0, sizeof(mcred)); + + mcred.server = server_principal; + mcred.client = client_principal; + + kerr = krb5_cc_retrieve_cred(ctx, ccache, 0, &mcred, &cred); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_cc_retrieve_cred failed.\n"); + goto done; + } + + kerr = krb5_decode_ticket(&cred.ticket, &ticket); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_decode_ticket failed.\n"); + goto done; + } + + kerr = krb5_server_decrypt_ticket_keytab(ctx, keytab, ticket); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_server_decrypt_ticket_keytab failed.\n"); + goto done; + } + + kerr = sss_krb5_find_authdata(ctx, + ticket->enc_part2->authorization_data, NULL, + KRB5_AUTHDATA_WIN2K_PAC, &pac_authdata); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_find_authdata failed.\n"); + goto done; + } + + if (pac_authdata == NULL || pac_authdata[0] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "No PAC authdata available.\n"); + if (check_pac_flags & CHECK_PAC_PRESENT) { + kerr = ERR_CHECK_PAC_FAILED; + } else { + kerr = ENOENT; + } + goto done; + } + + if (pac_authdata[1] != NULL) { + DEBUG(SSSDBG_OP_FAILURE, "More than one PAC autdata found.\n"); + kerr = EINVAL; + goto done; + } + + kerr = krb5_pac_parse(ctx, pac_authdata[0]->contents, + pac_authdata[0]->length, &pac); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_pac_parse failed.\n"); + goto done; + } + + kerr = krb5_kt_get_entry(ctx, keytab, ticket->server, + ticket->enc_part.kvno, ticket->enc_part.enctype, + &entry); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_kt_get_entry failed.\n"); + goto done; + } + + kerr = krb5_pac_verify(ctx, pac, 0, NULL, &entry.key, NULL); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_pac_verify failed.\n"); + goto done; + } + + *_pac_authdata = pac_authdata; + kerr = 0; + +done: + if (kerr != 0) { + krb5_free_authdata(ctx, pac_authdata); + } + if (entry.magic != 0) { + krb5_free_keytab_entry_contents(ctx, &entry); + } + krb5_pac_free(ctx, pac); + if (ticket != NULL) { + krb5_free_ticket(ctx, ticket); + } + + krb5_free_cred_contents(ctx, &cred); + return kerr; +} + +char * sss_get_ccache_name_for_principal(TALLOC_CTX *mem_ctx, + krb5_context ctx, + krb5_principal principal, + const char *location) +{ +#ifdef HAVE_KRB5_CC_COLLECTION + krb5_error_code kerr; + krb5_ccache tmp_cc = NULL; + char *tmp_ccname = NULL; + char *ret_ccname = NULL; + + DEBUG(SSSDBG_TRACE_ALL, + "Location: [%s]\n", location); + + kerr = krb5_cc_set_default_name(ctx, location); + if (kerr != 0) { + KRB5_DEBUG(SSSDBG_MINOR_FAILURE, ctx, kerr); + return NULL; + } + + kerr = krb5_cc_cache_match(ctx, principal, &tmp_cc); + if (kerr != 0) { + const char *err_msg = sss_krb5_get_error_message(ctx, kerr); + DEBUG(SSSDBG_TRACE_INTERNAL, + "krb5_cc_cache_match failed: [%d][%s]\n", kerr, err_msg); + sss_krb5_free_error_message(ctx, err_msg); + return NULL; + } + + kerr = krb5_cc_get_full_name(ctx, tmp_cc, &tmp_ccname); + if (kerr != 0) { + KRB5_DEBUG(SSSDBG_MINOR_FAILURE, ctx, kerr); + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, + "tmp_ccname: [%s]\n", tmp_ccname); + + ret_ccname = talloc_strdup(mem_ctx, tmp_ccname); + if (ret_ccname == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed (ENOMEM).\n"); + } + +done: + if (tmp_cc != NULL) { + kerr = krb5_cc_close(ctx, tmp_cc); + if (kerr != 0) { + KRB5_DEBUG(SSSDBG_MINOR_FAILURE, ctx, kerr); + } + } + krb5_free_string(ctx, tmp_ccname); + + return ret_ccname; +#else + return NULL; +#endif /* HAVE_KRB5_CC_COLLECTION */ +} + +krb5_error_code sss_krb5_kt_have_content(krb5_context context, + krb5_keytab keytab) +{ +#ifdef HAVE_KRB5_KT_HAVE_CONTENT + return krb5_kt_have_content(context, keytab); +#else + krb5_keytab_entry entry; + krb5_kt_cursor cursor; + krb5_error_code kerr; + krb5_error_code kerr_end; + + kerr = krb5_kt_start_seq_get(context, keytab, &cursor); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "krb5_kt_start_seq_get failed, assuming no entries.\n"); + return KRB5_KT_NOTFOUND; + } + + kerr = krb5_kt_next_entry(context, keytab, &entry, &cursor); + kerr_end = krb5_kt_end_seq_get(context, keytab, &cursor); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "krb5_kt_next_entry failed, assuming no entries.\n"); + return KRB5_KT_NOTFOUND; + } + kerr = krb5_free_keytab_entry_contents(context, &entry); + + if (kerr_end != 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "krb5_kt_end_seq_get failed, ignored.\n"); + } + if (kerr != 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "krb5_free_keytab_entry_contents failed, ignored.\n"); + } + + return 0; +#endif +} + +#define KDC_PROXY_INDICATOR "https://" +#define KDC_PROXY_INDICATOR_LEN (sizeof(KDC_PROXY_INDICATOR) - 1) + +bool sss_krb5_realm_has_proxy(const char *realm) +{ + krb5_context context = NULL; + krb5_error_code kerr; + struct _profile_t *profile = NULL; + const char *profile_path[4] = {"realms", NULL, "kdc", NULL}; + char **list = NULL; + bool res = false; + size_t c; + + if (realm == NULL) { + return false; + } + + kerr = sss_krb5_init_context(&context); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "sss_krb5_init_context failed.\n"); + return false; + } + + kerr = krb5_get_profile(context, &profile); + if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_get_profile failed.\n"); + goto done; + } + + profile_path[1] = realm; + + kerr = profile_get_values(profile, profile_path, &list); + if (kerr == PROF_NO_RELATION || kerr == PROF_NO_SECTION) { + goto done; + } else if (kerr != 0) { + DEBUG(SSSDBG_OP_FAILURE, "profile_get_values failed.\n"); + goto done; + } + + for (c = 0; list[c] != NULL; c++) { + if (strncasecmp(KDC_PROXY_INDICATOR, list[c], + KDC_PROXY_INDICATOR_LEN) == 0) { + DEBUG(SSSDBG_TRACE_ALL, + "Found KDC Proxy indicator [%s] in [%s].\n", + KDC_PROXY_INDICATOR, list[c]); + res = true; + break; + } + } + +done: + profile_free_list(list); + profile_release(profile); + krb5_free_context(context); + + return res; +} + +static errno_t iobuf_read_uint32be(struct sss_iobuf *iobuf, + uint32_t *_val) +{ + uint32_t beval; + errno_t ret; + + ret = sss_iobuf_read_uint32(iobuf, &beval); + if (ret != EOK) { + return ret; + } + + *_val = be32toh(beval); + return EOK; +} + +static errno_t iobuf_write_uint32be(struct sss_iobuf *iobuf, + uint32_t val) +{ + uint32_t beval; + + beval = htobe32(val); + return sss_iobuf_write_uint32(iobuf, beval); +} + +static errno_t iobuf_get_len_bytes(TALLOC_CTX *mem_ctx, + struct sss_iobuf *iobuf, + uint32_t *_nbytes, + uint8_t **_bytes) +{ + errno_t ret; + uint32_t nbytes; + uint8_t *bytes = NULL; + + ret = iobuf_read_uint32be(iobuf, &nbytes); + if (ret != EOK) { + return ret; + } + + bytes = talloc_zero_size(mem_ctx, nbytes); + if (bytes == NULL) { + return ENOMEM; + } + + ret = sss_iobuf_read_len(iobuf, nbytes, bytes); + if (ret != EOK) { + talloc_free(bytes); + return ret; + } + + *_bytes = bytes; + *_nbytes = nbytes; + return EOK; +} + +void get_krb5_data_from_cred(struct sss_iobuf *iobuf, krb5_data *k5data) +{ + k5data->data = (char *) sss_iobuf_get_data(iobuf); + k5data->length = sss_iobuf_get_size(iobuf); +} + +static errno_t get_krb5_data(TALLOC_CTX *mem_ctx, + struct sss_iobuf *iobuf, + krb5_data *k5data) +{ + errno_t ret; + uint32_t nbytes; + uint8_t *bytes = NULL; + + ret = iobuf_get_len_bytes(mem_ctx, iobuf, &nbytes, &bytes); + if (ret != EOK) { + talloc_free(bytes); + return ret; + } + + k5data->data = (char *) bytes; /* FIXME - the cast is ugly */ + k5data->length = nbytes; + return EOK; +} + +static errno_t set_krb5_data(struct sss_iobuf *iobuf, + krb5_data *k5data) +{ + errno_t ret; + + ret = iobuf_write_uint32be(iobuf, k5data->length); + if (ret != EOK) { + return ret; + } + + if (k5data->length > 0) { + ret = sss_iobuf_write_len(iobuf, + (uint8_t *) k5data->data, + k5data->length); + if (ret != EOK) { + return ret; + } + } + + return EOK; +} + +/* FIXME - it would be nice if Kerberos exported these APIs.. */ +krb5_error_code sss_krb5_unmarshal_princ(TALLOC_CTX *mem_ctx, + struct sss_iobuf *iobuf, + krb5_principal *_princ) +{ + krb5_principal princ = NULL; + krb5_error_code ret; + uint32_t ncomps; + + if (iobuf == NULL || _princ == NULL) { + return EINVAL; + } + + princ = talloc_zero(mem_ctx, struct krb5_principal_data); + if (princ == NULL) { + return ENOMEM; + } + + princ->magic = KV5M_PRINCIPAL; + + ret = iobuf_read_uint32be(iobuf, (uint32_t *) &princ->type); + if (ret != EOK) { + goto fail; + } + + ret = iobuf_read_uint32be(iobuf, &ncomps); + if (ret != EOK) { + goto fail; + } + + if (ncomps > sss_iobuf_get_capacity(iobuf)) { + /* Sanity check to avoid large allocations */ + ret = EINVAL; + goto fail; + } + + if (ncomps != 0) { + princ->data = talloc_zero_array(princ, krb5_data, ncomps); + if (princ->data == NULL) { + ret = ENOMEM; + goto fail; + } + + princ->length = ncomps; + } + + ret = get_krb5_data(princ, iobuf, &princ->realm); + if (ret != EOK) { + goto fail; + } + + for (size_t i = 0; i < ncomps; i++) { + ret = get_krb5_data(princ->data, iobuf, &princ->data[i]); + if (ret != EOK) { + goto fail; + } + } + + *_princ = princ; + return 0; + +fail: + talloc_free(princ); + return ret; +} + +krb5_error_code sss_krb5_marshal_princ(krb5_principal princ, + struct sss_iobuf *iobuf) +{ + krb5_error_code ret; + + if (iobuf == NULL || princ == NULL) { + return EINVAL; + } + + ret = iobuf_write_uint32be(iobuf, princ->type); + if (ret != EOK) { + return ret; + } + + ret = iobuf_write_uint32be(iobuf, princ->length); + if (ret != EOK) { + return ret; + } + + ret = set_krb5_data(iobuf, &princ->realm); + if (ret != EOK) { + return ret; + } + + for (int i = 0; i < princ->length; i++) { + ret = set_krb5_data(iobuf, &princ->data[i]); + if (ret != EOK) { + return ret; + } + } + return EOK; +} + +krb5_error_code sss_krb5_init_context(krb5_context *context) +{ + krb5_error_code kerr; + const char *msg; + + kerr = krb5_init_context(context); + if (kerr != 0) { + /* It is safe to call (sss_)krb5_get_error_message() with NULL as first + * argument. */ + msg = sss_krb5_get_error_message(NULL, kerr); + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to init Kerberos context [%s]\n", msg); + sss_log(SSS_LOG_CRIT, "Failed to init Kerberos context [%s]\n", msg); + sss_krb5_free_error_message(NULL, msg); + } + + return kerr; +} + +bool sss_krb5_creds_compare(krb5_context kctx, krb5_creds *a, krb5_creds *b) +{ + if (!krb5_principal_compare(kctx, a->client, b->client)) { + return false; + } + + if (!krb5_principal_compare(kctx, a->server, b->server)) { + return false; + } + + return true; +} diff --git a/src/util/sss_krb5.h b/src/util/sss_krb5.h new file mode 100644 index 0000000..a9254e2 --- /dev/null +++ b/src/util/sss_krb5.h @@ -0,0 +1,207 @@ +/* + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009-2010 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __SSS_KRB5_H__ +#define __SSS_KRB5_H__ + +#include "config.h" + +#include <stdbool.h> +#include <talloc.h> + +#ifdef HAVE_KRB5_KRB5_H +#include <krb5/krb5.h> +#else +#include <krb5.h> +#endif + +#include "util/sss_iobuf.h" +#include "util/util.h" +#include <uuid/uuid.h> + +#define KRB5_CHILD_LOG_FILE "krb5_child" +#define LDAP_CHILD_LOG_FILE "ldap_child" + +/* MIT Kerberos has the same hardcoded warning interval of 7 days. Due to the + * fact that using the expiration time of a Kerberos password with LDAP + * authentication is presumably a rare case a separate config option is not + * necessary. */ +#define KERBEROS_PWEXPIRE_WARNING_TIME (7 * 24 * 60 * 60) + +const char *sss_printable_keytab_name(krb5_context ctx, const char *keytab_name); + +#if defined HAVE_KRB5_CC_CACHE_MATCH && defined HAVE_KRB5_CC_GET_FULL_NAME +#define HAVE_KRB5_CC_COLLECTION 1 +#endif + +const char * KRB5_CALLCONV sss_krb5_get_error_message (krb5_context, + krb5_error_code); + +void KRB5_CALLCONV sss_krb5_free_error_message(krb5_context, const char *); + +#define KRB5_DEBUG(level, errctx, krb5_error) do { \ + const char *__krb5_error_msg; \ + __krb5_error_msg = sss_krb5_get_error_message(errctx, krb5_error); \ + DEBUG(level, "%d: [%d][%s]\n", __LINE__, krb5_error, __krb5_error_msg); \ + sss_log(SSS_LOG_ERR, "%s", __krb5_error_msg); \ + sss_krb5_free_error_message(errctx, __krb5_error_msg); \ +} while(0) + +krb5_error_code KRB5_CALLCONV sss_krb5_get_init_creds_opt_alloc( + krb5_context context, + krb5_get_init_creds_opt **opt); + +void KRB5_CALLCONV sss_krb5_get_init_creds_opt_free (krb5_context context, + krb5_get_init_creds_opt *opt); + +void KRB5_CALLCONV sss_krb5_free_unparsed_name(krb5_context context, char *name); + +krb5_error_code find_principal_in_keytab(krb5_context ctx, + krb5_keytab keytab, + const char *pattern_primary, + const char *pattern_realm, + krb5_principal *princ); + +errno_t select_principal_from_keytab(TALLOC_CTX *mem_ctx, + const char *hostname, + const char *desired_realm, + const char *keytab_name, + char **_principal, + char **_primary, + char **_realm); + +#ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_EXPIRE_CALLBACK +typedef void +(KRB5_CALLCONV *krb5_expire_callback_func)(krb5_context context, void *data, + krb5_timestamp password_expiration, + krb5_timestamp account_expiration, + krb5_boolean is_last_req); +#endif +krb5_error_code KRB5_CALLCONV sss_krb5_get_init_creds_opt_set_expire_callback( + krb5_context context, + krb5_get_init_creds_opt *opt, + krb5_expire_callback_func cb, + void *data); + +errno_t check_fast(const char *str, bool *use_fast); + +krb5_error_code KRB5_CALLCONV sss_krb5_get_init_creds_opt_set_fast_ccache_name( + krb5_context context, + krb5_get_init_creds_opt *opt, + const char *fast_ccache_name); + +krb5_error_code KRB5_CALLCONV sss_krb5_get_init_creds_opt_set_fast_flags( + krb5_context context, + krb5_get_init_creds_opt *opt, + krb5_flags flags); + +#if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_FLAGS +#define SSS_KRB5_FAST_REQUIRED KRB5_FAST_REQUIRED +#else +#define SSS_KRB5_FAST_REQUIRED 0 +#endif + + +#ifndef HAVE_KRB5_PARSE_NAME_FLAGS +#define KRB5_PRINCIPAL_PARSE_NO_REALM 0x1 +#define KRB5_PRINCIPAL_PARSE_REQUIRE_REALM 0x2 +#define KRB5_PRINCIPAL_PARSE_ENTERPRISE 0x4 +#endif +krb5_error_code +sss_krb5_parse_name_flags(krb5_context context, const char *name, int flags, + krb5_principal *principal); + +#ifndef HAVE_KRB5_UNPARSE_NAME_FLAGS +#define KRB5_PRINCIPAL_UNPARSE_SHORT 0x1 +#define KRB5_PRINCIPAL_UNPARSE_NO_REALM 0x2 +#define KRB5_PRINCIPAL_UNPARSE_DISPLAY 0x4 +#endif +krb5_error_code +sss_krb5_unparse_name_flags(krb5_context context, krb5_const_principal principal, + int flags, char **name); + +void sss_krb5_get_init_creds_opt_set_canonicalize(krb5_get_init_creds_opt *opts, + int canonicalize); + +enum sss_krb5_cc_type { + SSS_KRB5_TYPE_FILE, +#ifdef HAVE_KRB5_CC_COLLECTION + SSS_KRB5_TYPE_DIR, + SSS_KRB5_TYPE_KEYRING, +#endif /* HAVE_KRB5_CC_COLLECTION */ + + SSS_KRB5_TYPE_UNKNOWN +}; + +/* === Compatibility routines for the Heimdal Kerberos implementation === */ + +void sss_krb5_princ_realm(krb5_context context, krb5_const_principal princ, + const char **realm, int *len); + +krb5_error_code +sss_krb5_free_keytab_entry_contents(krb5_context context, + krb5_keytab_entry *entry); + +#ifdef HAVE_KRB5_TICKET_TIMES +typedef krb5_ticket_times sss_krb5_ticket_times; +#elif defined(HAVE_KRB5_TIMES) +typedef krb5_times sss_krb5_ticket_times; +#endif + +/* Redirect libkrb5 tracing towards our DEBUG statements */ +errno_t sss_child_set_krb5_tracing(krb5_context ctx); + +krb5_error_code sss_krb5_find_authdata(krb5_context context, + krb5_authdata *const *ticket_authdata, + krb5_authdata *const *ap_req_authdata, + krb5_authdatatype ad_type, + krb5_authdata ***results); + +krb5_error_code sss_extract_pac(krb5_context ctx, + krb5_ccache ccache, + krb5_principal server_principal, + krb5_principal client_principal, + krb5_keytab keytab, + uint32_t check_pac_flags, + krb5_authdata ***_pac_authdata); + +char * sss_get_ccache_name_for_principal(TALLOC_CTX *mem_ctx, + krb5_context ctx, + krb5_principal principal, + const char *location); + +krb5_error_code sss_krb5_kt_have_content(krb5_context context, + krb5_keytab keytab); + +bool sss_krb5_realm_has_proxy(const char *realm); + +krb5_error_code sss_krb5_marshal_princ(krb5_principal princ, + struct sss_iobuf *iobuf); + +krb5_error_code sss_krb5_unmarshal_princ(TALLOC_CTX *mem_ctx, + struct sss_iobuf *iobuf, + krb5_principal *_princ); + +krb5_error_code sss_krb5_init_context(krb5_context *context); + +void get_krb5_data_from_cred(struct sss_iobuf *iobuf, krb5_data *k5data); + +bool sss_krb5_creds_compare(krb5_context kctx, krb5_creds *a, krb5_creds *b); +#endif /* __SSS_KRB5_H__ */ diff --git a/src/util/sss_ldap.c b/src/util/sss_ldap.c new file mode 100644 index 0000000..756574c --- /dev/null +++ b/src/util/sss_ldap.c @@ -0,0 +1,499 @@ +/* + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <fcntl.h> + +#include "util/util.h" +#include "util/sss_sockets.h" +#include "util/sss_ldap.h" + +#include "providers/ldap/sdap.h" + +const char* sss_ldap_err2string(int err) +{ + if (IS_SSSD_ERROR(err)) { + return sss_strerror(err); + } else { + return ldap_err2string(err); + } +} + +int sss_ldap_get_diagnostic_msg(TALLOC_CTX *mem_ctx, LDAP *ld, char **_errmsg) +{ + char *errmsg = NULL; + int optret; + + optret = ldap_get_option(ld, SDAP_DIAGNOSTIC_MESSAGE, (void*)&errmsg); + if (optret != LDAP_SUCCESS) { + return EINVAL; + } + + *_errmsg = talloc_strdup(mem_ctx, errmsg ? errmsg : "unknown error"); + ldap_memfree(errmsg); + if (*_errmsg == NULL) { + return ENOMEM; + } + return EOK; +} + +int sss_ldap_control_create(const char *oid, int iscritical, + struct berval *value, int dupval, + LDAPControl **ctrlp) +{ +#ifdef HAVE_LDAP_CONTROL_CREATE + return ldap_control_create(oid, iscritical, value, dupval, ctrlp); +#else + LDAPControl *lc = NULL; + + if (oid == NULL || ctrlp == NULL) { + return LDAP_PARAM_ERROR; + } + + lc = calloc(sizeof(LDAPControl), 1); + if (lc == NULL) { + return LDAP_NO_MEMORY; + } + + lc->ldctl_oid = strdup(oid); + if (lc->ldctl_oid == NULL) { + free(lc); + return LDAP_NO_MEMORY; + } + + if (value != NULL && value->bv_val != NULL) { + if (dupval == 0) { + lc->ldctl_value = *value; + } else { + ber_dupbv(&lc->ldctl_value, value); + if (lc->ldctl_value.bv_val == NULL) { + free(lc->ldctl_oid); + free(lc); + return LDAP_NO_MEMORY; + } + } + } + + lc->ldctl_iscritical = iscritical; + + *ctrlp = lc; + + return LDAP_SUCCESS; +#endif +} + +#ifdef HAVE_LDAP_INIT_FD + +#define LDAP_PROTO_TCP 1 /* ldap:// */ +#define LDAP_PROTO_UDP 2 /* reserved */ +#define LDAP_PROTO_IPC 3 /* ldapi:// */ +#define LDAP_PROTO_EXT 4 /* user-defined socket/sockbuf */ + +extern int ldap_init_fd(ber_socket_t fd, int proto, const char *url, LDAP **ld); + +static void sss_ldap_init_sys_connect_done(struct tevent_req *subreq); +#endif + +struct sss_ldap_init_state { + LDAP *ldap; + int sd; + const char *uri; + bool use_udp; +}; + +static int sss_ldap_init_state_destructor(void *data) +{ + struct sss_ldap_init_state *state = (struct sss_ldap_init_state *)data; + + if (state->ldap) { + DEBUG(SSSDBG_TRACE_FUNC, + "calling ldap_unbind_ext for ldap:[%p] sd:[%d]\n", + state->ldap, state->sd); + ldap_unbind_ext(state->ldap, NULL, NULL); + } + if (state->sd != -1) { + DEBUG(SSSDBG_TRACE_FUNC, "closing socket [%d]\n", state->sd); + close(state->sd); + state->sd = -1; + } + + return 0; +} + + +struct tevent_req *sss_ldap_init_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *uri, + struct sockaddr *addr, + int addr_len, int timeout) +{ + int ret = EOK; + struct tevent_req *req; + struct sss_ldap_init_state *state; + + req = tevent_req_create(mem_ctx, &state, struct sss_ldap_init_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + talloc_set_destructor((TALLOC_CTX *)state, sss_ldap_init_state_destructor); + + state->ldap = NULL; + state->sd = -1; + state->uri = uri; + state->use_udp = strncmp(uri, "cldap", 5) == 0 ? true : false; + +#ifdef HAVE_LDAP_INIT_FD + struct tevent_req *subreq; + + subreq = sssd_async_socket_init_send(state, ev, state->use_udp, addr, + addr_len, timeout); + if (subreq == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "sssd_async_socket_init_send failed.\n"); + goto fail; + } + + tevent_req_set_callback(subreq, sss_ldap_init_sys_connect_done, req); + return req; + +fail: + tevent_req_error(req, ret); +#else + DEBUG(SSSDBG_MINOR_FAILURE, "ldap_init_fd not available, " + "will use ldap_initialize with uri [%s].\n", uri); + ret = ldap_initialize(&state->ldap, uri); + if (ret == LDAP_SUCCESS) { + tevent_req_done(req); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_initialize failed [%s].\n", sss_ldap_err2string(ret)); + if (ret == LDAP_SERVER_DOWN) { + tevent_req_error(req, ETIMEDOUT); + } else { + tevent_req_error(req, EIO); + } + } +#endif + + tevent_req_post(req, ev); + return req; +} + +#ifdef HAVE_LDAP_INIT_FD +static errno_t unset_fcntl_flags(int fd, int fl_flags) +{ + errno_t ret; + int flags; + + flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fcntl F_GETFL failed [%s].\n", strerror(ret)); + return ret; + } + + /* unset flags */ + flags &= ~fl_flags; + + ret = fcntl(fd, F_SETFL, flags); + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fcntl F_SETFL failed [%s].\n", strerror(ret)); + return ret; + } + + return EOK; +} + +static void sss_ldap_init_sys_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sss_ldap_init_state *state = tevent_req_data(req, + struct sss_ldap_init_state); + char *tlserr; + int ret; + int lret; + int optret; + int ticks_before_install; + int ticks_after_install; + + ret = sssd_async_socket_init_recv(subreq, &state->sd); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sssd_async_socket_init request failed: [%d]: %s.\n", + ret, sss_strerror(ret)); + goto fail; + } + + /* openldap < 2.5 does not correctly handle O_NONBLOCK during starttls for + * ldaps, so we need to remove the flag here. This is fine since I/O events + * are handled via tevent so we only read when there is data available. + * + * We need to keep O_NONBLOCK due to a bug in openldap to correctly perform + * a parallel CLDAP pings without timeout. See: + * https://bugs.openldap.org/show_bug.cgi?id=9328 + * + * @todo remove this when the bug is fixed and we can put a hard requirement + * on newer openldap. + */ + if (!state->use_udp) { + ret = unset_fcntl_flags(state->sd, O_NONBLOCK); + if (ret != EOK) { + goto fail; + } + } + + /* Initialize LDAP handler */ + + lret = ldap_init_fd(state->sd, + state->use_udp ? LDAP_PROTO_UDP : LDAP_PROTO_TCP, + state->uri, &state->ldap); + if (lret != LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_init_fd failed: %s. [%d][%s]\n", + sss_ldap_err2string(lret), state->sd, state->uri); + ret = lret == LDAP_SERVER_DOWN ? ETIMEDOUT : EIO; + goto fail; + } + + if (ldap_is_ldaps_url(state->uri)) { + ticks_before_install = get_watchdog_ticks(); + lret = ldap_install_tls(state->ldap); + ticks_after_install = get_watchdog_ticks(); + if (lret != LDAP_SUCCESS) { + if (lret == LDAP_LOCAL_ERROR) { + DEBUG(SSSDBG_FUNC_DATA, "TLS/SSL already in place.\n"); + } else { + + optret = sss_ldap_get_diagnostic_msg(state, state->ldap, + &tlserr); + if (optret == LDAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_install_tls failed: [%s] [%s]\n", + sss_ldap_err2string(lret), tlserr); + sss_log(SSS_LOG_ERR, + "Could not start TLS encryption. %s", tlserr); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "ldap_install_tls failed: [%s]\n", + sss_ldap_err2string(lret)); + sss_log(SSS_LOG_ERR, "Could not start TLS encryption. " + "Check for certificate issues."); + } + + if (ticks_after_install > ticks_before_install) { + ret = ERR_TLS_HANDSHAKE_INTERRUPTED; + DEBUG(SSSDBG_CRIT_FAILURE, + "Assuming %s\n", + sss_ldap_err2string(ret)); + goto fail; + } + + ret = EIO; + goto fail; + } + } + } + + tevent_req_done(req); + return; + +fail: + tevent_req_error(req, ret); +} +#endif + +int sss_ldap_init_recv(struct tevent_req *req, LDAP **ldap, int *sd) +{ + struct sss_ldap_init_state *state = tevent_req_data(req, + struct sss_ldap_init_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + + /* Everything went well therefore we do not want to release resources */ + talloc_set_destructor(state, NULL); + + *ldap = state->ldap; + *sd = state->sd; + + return EOK; +} + +/* + * _filter will contain combined filters from all possible search bases + * or NULL if it should be empty + */ + + +bool sss_ldap_dn_in_search_bases_len(TALLOC_CTX *mem_ctx, + const char *dn, + struct sdap_search_base **search_bases, + char **_filter, + int *_match_len) +{ + struct sdap_search_base *base; + int basedn_len, dn_len; + int len_diff; + int i, j; + bool base_confirmed = false; + bool comma_found = false; + bool backslash_found = false; + char *filter = NULL; + bool ret = false; + int match_len; + + if (dn == NULL) { + DEBUG(SSSDBG_FUNC_DATA, "dn is NULL\n"); + ret = false; + goto done; + } + + if (search_bases == NULL) { + DEBUG(SSSDBG_FUNC_DATA, "search_bases is NULL\n"); + ret = false; + goto done; + } + + dn_len = strlen(dn); + for (i = 0; search_bases[i] != NULL; i++) { + base = search_bases[i]; + basedn_len = strlen(base->basedn); + + if (basedn_len > dn_len) { + continue; + } + + len_diff = dn_len - basedn_len; + base_confirmed = (strncasecmp(&dn[len_diff], base->basedn, basedn_len) == 0); + if (!base_confirmed) { + continue; + } + match_len = basedn_len; + + switch (base->scope) { + case LDAP_SCOPE_BASE: + /* dn > base? */ + if (len_diff != 0) { + continue; + } + break; + case LDAP_SCOPE_ONELEVEL: + if (len_diff == 0) { + /* Base object doesn't belong to scope=one + * search */ + continue; + } + + comma_found = false; + for (j = 0; j < len_diff - 1; j++) { /* ignore comma before base */ + if (dn[j] == '\\') { + backslash_found = true; + } else if (dn[j] == ',' && !backslash_found) { + comma_found = true; + break; + } else { + backslash_found = false; + } + } + + /* it has at least one more level */ + if (comma_found) { + continue; + } + + break; + case LDAP_SCOPE_SUBTREE: + /* dn length >= base dn length && base_confirmed == true */ + break; + default: + DEBUG(SSSDBG_FUNC_DATA, "Unsupported scope: %d\n", base->scope); + continue; + } + + /* + * If we get here, the dn is valid. + * If no filter is set, than return true immediately. + * Append filter otherwise. + */ + ret = true; + if (_match_len) { + *_match_len = match_len; + } + + if (base->filter == NULL || _filter == NULL) { + goto done; + } else { + filter = talloc_strdup_append(filter, base->filter); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup_append() failed\n"); + ret = false; + goto done; + } + } + } + + if (_filter != NULL) { + if (filter != NULL) { + *_filter = talloc_asprintf(mem_ctx, "(|%s)", filter); + if (*_filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_asprintf_append() failed\n"); + ret = false; + goto done; + } + } else { + *_filter = NULL; + } + } + +done: + talloc_free(filter); + return ret; +} + +bool sss_ldap_dn_in_search_bases(TALLOC_CTX *mem_ctx, + const char *dn, + struct sdap_search_base **search_bases, + char **_filter) +{ + return sss_ldap_dn_in_search_bases_len(mem_ctx, dn, search_bases, _filter, + NULL); +} + +char *sss_ldap_encode_ndr_uint32(TALLOC_CTX *mem_ctx, uint32_t flags) +{ + char hex[9]; /* 4 bytes in hex + terminating zero */ + errno_t ret; + + ret = snprintf(hex, 9, "%08x", flags); + if (ret != 8) { + return NULL; + } + + return talloc_asprintf(mem_ctx, "\\%c%c\\%c%c\\%c%c\\%c%c", + hex[6], hex[7], hex[4], hex[5], + hex[2], hex[3], hex[0], hex[1]); +} diff --git a/src/util/sss_ldap.h b/src/util/sss_ldap.h new file mode 100644 index 0000000..b5fc61f --- /dev/null +++ b/src/util/sss_ldap.h @@ -0,0 +1,99 @@ +/* + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __SSS_LDAP_H__ +#define __SSS_LDAP_H__ + +#include <sys/types.h> +#include <sys/socket.h> +#include <ldap.h> +#include <talloc.h> +#include <tevent.h> + +#ifndef LDAP_CONTROL_PWEXPIRED +#define LDAP_CONTROL_PWEXPIRED "2.16.840.1.113730.3.4.4" +#endif + +#ifndef LDAP_CONTROL_PWEXPIRING +#define LDAP_CONTROL_PWEXPIRING "2.16.840.1.113730.3.4.5" +#endif + +#ifdef LDAP_OPT_DIAGNOSTIC_MESSAGE +#define SDAP_DIAGNOSTIC_MESSAGE LDAP_OPT_DIAGNOSTIC_MESSAGE +#else +#ifdef LDAP_OPT_ERROR_STRING +#define SDAP_DIAGNOSTIC_MESSAGE LDAP_OPT_ERROR_STRING +#else +#error No extended diagnostic message available +#endif +#endif + +const char* sss_ldap_err2string(int err); + +int sss_ldap_get_diagnostic_msg(TALLOC_CTX *mem_ctx, + LDAP *ld, + char **_errmsg); + +#ifndef LDAP_SERVER_ASQ_OID +#define LDAP_SERVER_ASQ_OID "1.2.840.113556.1.4.1504" +#endif /* LDAP_SERVER_ASQ_OID */ + +#ifndef LDAP_SERVER_SD_OID +#define LDAP_SERVER_SD_OID "1.2.840.113556.1.4.801" +#endif /* LDAP_SERVER_SD_OID */ + + +/* + * The following four flags specify which security descriptor parts to retrieve + * during sd_search (see http://msdn.microsoft.com/en-us/library/aa366987.aspx) + */ +#define SECINFO_OWNER ( 0x00000001 ) +#define SECINFO_GROUP ( 0x00000002 ) +#define SECINFO_DACL ( 0x00000004 ) +#define SECINFO_SACL ( 0x00000008 ) + +int sss_ldap_control_create(const char *oid, int iscritical, + struct berval *value, int dupval, + LDAPControl **ctrlp); + +struct tevent_req *sss_ldap_init_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *uri, + struct sockaddr *addr, + int addr_len, int timeout); + +int sss_ldap_init_recv(struct tevent_req *req, LDAP **ldap, int *sd); + +struct sdap_options; +struct sdap_search_base; +bool sss_ldap_dn_in_search_bases(TALLOC_CTX *mem_ctx, + const char *dn, + struct sdap_search_base **search_bases, + char **_filter); + +bool sss_ldap_dn_in_search_bases_len(TALLOC_CTX *mem_ctx, + const char *dn, + struct sdap_search_base **search_bases, + char **_filter, + int *_match_len); + +char *sss_ldap_encode_ndr_uint32(TALLOC_CTX *mem_ctx, uint32_t flags); + +#endif /* __SSS_LDAP_H__ */ diff --git a/src/util/sss_log.c b/src/util/sss_log.c new file mode 100644 index 0000000..3c415d4 --- /dev/null +++ b/src/util/sss_log.c @@ -0,0 +1,126 @@ +/* + SSSD + + sss_log.c + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2010 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" + +#ifdef WITH_JOURNALD +#include <systemd/sd-journal.h> +#else /* WITH_JOURNALD */ +#include <syslog.h> +#endif /* WITH_JOURNALD */ + +static int sss_to_syslog(int priority) +{ + switch(priority) { + case SSS_LOG_EMERG: + return LOG_EMERG; + case SSS_LOG_ALERT: + return LOG_ALERT; + case SSS_LOG_CRIT: + return LOG_CRIT; + case SSS_LOG_ERR: + return LOG_ERR; + case SSS_LOG_WARNING: + return LOG_WARNING; + case SSS_LOG_NOTICE: + return LOG_NOTICE; + case SSS_LOG_INFO: + return LOG_INFO; + case SSS_LOG_DEBUG: + return LOG_DEBUG; + default: + /* If we've been passed an invalid priority, it's + * best to assume it's an emergency. + */ + return LOG_EMERG; + } +} + +static void sss_log_internal(int priority, int facility, const char *format, + va_list ap); + +void sss_log(int priority, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + sss_log_internal(priority, LOG_DAEMON, format, ap); + va_end(ap); +} + +void sss_log_ext(int priority, int facility, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + sss_log_internal(priority, facility, format, ap); + va_end(ap); +} + + + +#ifdef WITH_JOURNALD + +static void sss_log_internal(int priority, int facility, const char *format, + va_list ap) +{ + int syslog_priority; + int ret; + char *message; + const char *domain; + + ret = vasprintf(&message, format, ap); + + if (ret == -1) { + /* ENOMEM */ + return; + } + + domain = getenv(SSS_DOM_ENV); + if (domain == NULL) { + domain = ""; + } + + syslog_priority = sss_to_syslog(priority); + sd_journal_send("MESSAGE=%s", message, + "SSSD_DOMAIN=%s", domain, + "SSSD_PRG_NAME=sssd[%s]", debug_prg_name, + "PRIORITY=%i", syslog_priority, + "SYSLOG_FACILITY=%i", LOG_FAC(facility), + NULL); + + free(message); +} + +#else /* WITH_JOURNALD */ + +static void sss_log_internal(int priority, int facility, const char *format, + va_list ap) +{ + int syslog_priority = sss_to_syslog(priority); + + vsyslog(facility|syslog_priority, format, ap); +} + +#endif /* WITH_JOURNALD */ diff --git a/src/util/sss_nss.c b/src/util/sss_nss.c new file mode 100644 index 0000000..63fd069 --- /dev/null +++ b/src/util/sss_nss.c @@ -0,0 +1,233 @@ +/* + SSSD + + Utility functions related to ID information + + Copyright (C) Jan Zeleny <jzeleny@redhat.com> 2012 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "util/sss_nss.h" + +char *expand_homedir_template(TALLOC_CTX *mem_ctx, + const char *template, + bool case_sensitive, + struct sss_nss_homedir_ctx *homedir_ctx) +{ + char *copy; + char *p; + char *n; + char *result = NULL; + char *res = NULL; + TALLOC_CTX *tmp_ctx = NULL; + const char *orig = NULL; + char *username = NULL; + + if (template == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing template.\n"); + return NULL; + } + + if (homedir_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing home directory data.\n"); + return NULL; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return NULL; + + copy = talloc_strdup(tmp_ctx, template); + if (copy == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + goto done; + } + + result = talloc_strdup(tmp_ctx, ""); + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + goto done; + } + + p = copy; + while ( (n = strchr(p, '%')) != NULL) { + *n = '\0'; + n++; + if ( *n == '\0' ) { + DEBUG(SSSDBG_CRIT_FAILURE, "format error, single %% at the end of " + "the template.\n"); + goto done; + } + switch( *n ) { + case 'u': + if (homedir_ctx->username == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot expand user name template because user name " + "is empty.\n"); + goto done; + } + username = sss_output_name(tmp_ctx, homedir_ctx->username, + case_sensitive, 0); + if (username == NULL) { + goto done; + } + + result = talloc_asprintf_append(result, "%s%s", p, username); + talloc_free(username); + break; + + case 'l': + if (homedir_ctx->username == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot expand first letter of user name template " + "because user name is empty.\n"); + goto done; + } + username = sss_output_name(tmp_ctx, homedir_ctx->username, + case_sensitive, 0); + if (username == NULL) { + goto done; + } + + result = talloc_asprintf_append(result, "%s%c", p, username[0]); + talloc_free(username); + break; + + case 'U': + if (homedir_ctx->uid == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot expand uid template " + "because uid is invalid.\n"); + goto done; + } + result = talloc_asprintf_append(result, "%s%d", p, + homedir_ctx->uid); + break; + + case 'd': + if (homedir_ctx->domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot expand domain name " + "template because domain name " + "is empty.\n"); + goto done; + } + result = talloc_asprintf_append(result, "%s%s", p, + homedir_ctx->domain); + break; + + case 'f': + if (homedir_ctx->domain == NULL + || homedir_ctx->username == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot expand fully qualified " + "name template because domain " + "or user name is empty.\n"); + goto done; + } + username = sss_output_name(tmp_ctx, homedir_ctx->username, + case_sensitive, 0); + if (username == NULL) { + goto done; + } + + result = talloc_asprintf_append(result, "%s%s@%s", p, + username, homedir_ctx->domain); + talloc_free(username); + break; + + case 'o': + case 'h': + if (homedir_ctx->original == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Original home directory for %s is not available, " + "using empty string\n", homedir_ctx->username); + orig = ""; + } else { + if (*n == 'o') { + orig = homedir_ctx->original; + } else { + orig = sss_tc_utf8_str_tolower(tmp_ctx, + homedir_ctx->original); + if (orig == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to lowercase the original home " + "directory.\n"); + goto done; + } + } + } + result = talloc_asprintf_append(result, "%s%s", p, orig); + break; + + case 'F': + if (homedir_ctx->flatname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot expand domain name " + "template because domain flat " + "name is empty.\n"); + goto done; + } + result = talloc_asprintf_append(result, "%s%s", p, + homedir_ctx->flatname); + break; + + case 'H': + if (homedir_ctx->config_homedir_substr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot expand home directory substring template " + "substring is empty.\n"); + goto done; + } + result = talloc_asprintf_append(result, "%s%s", p, + homedir_ctx->config_homedir_substr); + break; + + case 'P': + if (homedir_ctx->upn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot expand user principal name template " + "string is empty.\n"); + goto done; + } + result = talloc_asprintf_append(result, "%s%s", p, + homedir_ctx->upn); + break; + + case '%': + result = talloc_asprintf_append(result, "%s%%", p); + break; + + default: + DEBUG(SSSDBG_CRIT_FAILURE, "format error, unknown template " + "[%%%c].\n", *n); + goto done; + } + + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append failed.\n"); + goto done; + } + + p = n + 1; + } + + result = talloc_asprintf_append(result, "%s", p); + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append failed.\n"); + goto done; + } + + res = talloc_move(mem_ctx, &result); +done: + talloc_zfree(tmp_ctx); + return res; +} diff --git a/src/util/sss_nss.h b/src/util/sss_nss.h new file mode 100644 index 0000000..2b8a5ae --- /dev/null +++ b/src/util/sss_nss.h @@ -0,0 +1,42 @@ +/* + SSSD + + Utility functions related to ID information + + Copyright (C) Jan Zeleny <jzeleny@redhat.com> 2012 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __SSS_NSS_H__ +#define __SSS_NSS_H__ + +#include <stdbool.h> +#include <sys/types.h> +#include <talloc.h> + +struct sss_nss_homedir_ctx { + const char *username; + uint32_t uid; + const char *original; + const char *domain; + const char *flatname; + const char *config_homedir_substr; + const char *upn; +}; + +char *expand_homedir_template(TALLOC_CTX *mem_ctx, const char *template, + bool case_sensitive, + struct sss_nss_homedir_ctx *homedir_ctx); +#endif diff --git a/src/util/sss_pam_data.c b/src/util/sss_pam_data.c new file mode 100644 index 0000000..f09b9c5 --- /dev/null +++ b/src/util/sss_pam_data.c @@ -0,0 +1,205 @@ +/* + SSSD + + Utilities to for tha pam_data structure + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <security/pam_modules.h> + +#include "util/sss_pam_data.h" +#include "util/sss_cli_cmd.h" + +#define PAM_SAFE_ITEM(item) item ? item : "not set" + +int pam_data_destructor(void *ptr) +{ + struct pam_data *pd = talloc_get_type(ptr, struct pam_data); + + /* make sure to wipe any password from memory before freeing */ + sss_authtok_wipe_password(pd->authtok); + sss_authtok_wipe_password(pd->newauthtok); + + return 0; +} + +struct pam_data *create_pam_data(TALLOC_CTX *mem_ctx) +{ + struct pam_data *pd; + + pd = talloc_zero(mem_ctx, struct pam_data); + if (pd == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + goto failed; + } + + pd->pam_status = PAM_SYSTEM_ERR; + + pd->authtok = sss_authtok_new(pd); + if (pd->authtok == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + goto failed; + } + + pd->newauthtok = sss_authtok_new(pd); + if (pd->newauthtok == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + goto failed; + } + + talloc_set_destructor((TALLOC_CTX *) pd, pam_data_destructor); + + return pd; + +failed: + talloc_free(pd); + return NULL; +} + +errno_t copy_pam_data(TALLOC_CTX *mem_ctx, struct pam_data *src, + struct pam_data **dst) +{ + struct pam_data *pd = NULL; + errno_t ret; + + pd = create_pam_data(mem_ctx); + if (pd == NULL) { + ret = ENOMEM; + goto failed; + } + + pd->cmd = src->cmd; + pd->priv = src->priv; + + pd->domain = talloc_strdup(pd, src->domain); + if (pd->domain == NULL && src->domain != NULL) { + ret = ENOMEM; + goto failed; + } + pd->user = talloc_strdup(pd, src->user); + if (pd->user == NULL && src->user != NULL) { + ret = ENOMEM; + goto failed; + } + pd->service = talloc_strdup(pd, src->service); + if (pd->service == NULL && src->service != NULL) { + ret = ENOMEM; + goto failed; + } + pd->tty = talloc_strdup(pd, src->tty); + if (pd->tty == NULL && src->tty != NULL) { + ret = ENOMEM; + goto failed; + } + pd->ruser = talloc_strdup(pd, src->ruser); + if (pd->ruser == NULL && src->ruser != NULL) { + ret = ENOMEM; + goto failed; + } + pd->rhost = talloc_strdup(pd, src->rhost); + if (pd->rhost == NULL && src->rhost != NULL) { + ret = ENOMEM; + goto failed; + } + + pd->cli_pid = src->cli_pid; + pd->client_id_num = src->client_id_num; + + /* if structure pam_data was allocated on stack and zero initialized, + * than src->authtok and src->newauthtok are NULL, therefore + * instead of copying, new empty authtok will be created. + */ + if (src->authtok) { + ret = sss_authtok_copy(src->authtok, pd->authtok); + if (ret) { + goto failed; + } + } else { + pd->authtok = sss_authtok_new(pd); + if (pd->authtok == NULL) { + ret = ENOMEM; + goto failed; + } + } + + if (src->newauthtok) { + ret = sss_authtok_copy(src->newauthtok, pd->newauthtok); + if (ret) { + goto failed; + } + } else { + pd->newauthtok = sss_authtok_new(pd); + if (pd->newauthtok == NULL) { + ret = ENOMEM; + goto failed; + } + } + + *dst = pd; + + return EOK; + +failed: + talloc_free(pd); + DEBUG(SSSDBG_CRIT_FAILURE, + "copy_pam_data failed: (%d) %s.\n", ret, strerror(ret)); + return ret; +} + +void pam_print_data(int l, struct pam_data *pd) +{ + DEBUG(l, "command: %s\n", sss_cmd2str(pd->cmd)); + DEBUG(l, "domain: %s\n", PAM_SAFE_ITEM(pd->domain)); + DEBUG(l, "user: %s\n", PAM_SAFE_ITEM(pd->user)); + DEBUG(l, "service: %s\n", PAM_SAFE_ITEM(pd->service)); + DEBUG(l, "tty: %s\n", PAM_SAFE_ITEM(pd->tty)); + DEBUG(l, "ruser: %s\n", PAM_SAFE_ITEM(pd->ruser)); + DEBUG(l, "rhost: %s\n", PAM_SAFE_ITEM(pd->rhost)); + DEBUG(l, "authtok type: %d (%s)\n", + sss_authtok_get_type(pd->authtok), + sss_authtok_type_to_str(sss_authtok_get_type(pd->authtok))); + DEBUG(l, "newauthtok type: %d (%s)\n", + sss_authtok_get_type(pd->newauthtok), + sss_authtok_type_to_str(sss_authtok_get_type(pd->newauthtok))); + DEBUG(l, "priv: %d\n", pd->priv); + DEBUG(l, "cli_pid: %d\n", pd->cli_pid); + DEBUG(l, "child_pid: %d\n", pd->child_pid); + DEBUG(l, "logon name: %s\n", PAM_SAFE_ITEM(pd->logon_name)); + DEBUG(l, "flags: %d\n", pd->cli_flags); +} + +int pam_add_response(struct pam_data *pd, enum response_type type, + int len, const uint8_t *data) +{ + struct response_data *new; + + new = talloc(pd, struct response_data); + if (new == NULL) return ENOMEM; + + new->type = type; + new->len = len; + new->data = talloc_memdup(new, data, len); + if (new->data == NULL) return ENOMEM; + new->do_not_send_to_client = false; + new->next = pd->resp_list; + pd->resp_list = new; + + return EOK; +} diff --git a/src/util/sss_pam_data.h b/src/util/sss_pam_data.h new file mode 100644 index 0000000..e9b90a8 --- /dev/null +++ b/src/util/sss_pam_data.h @@ -0,0 +1,99 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SSS_PAM_DATA_H_ +#define _SSS_PAM_DATA_H_ + +#include "config.h" +#include <stdbool.h> +#include <stdint.h> +#ifdef USE_KEYRING +#include <sys/types.h> +#include <keyutils.h> +#endif + +#include "util/util_errors.h" +#include "util/debug.h" +#include "util/authtok.h" + +#define DEBUG_PAM_DATA(level, pd) do { \ + pam_print_data(level, pd); \ +} while(0) + +struct response_data { + int32_t type; + int32_t len; + uint8_t *data; + bool do_not_send_to_client; + struct response_data *next; +}; + +struct pam_data { + int cmd; + char *domain; + char *user; + char *service; + char *tty; + char *ruser; + char *rhost; + char **requested_domains; + struct sss_auth_token *authtok; + struct sss_auth_token *newauthtok; + uint32_t cli_pid; + uint32_t child_pid; + char *logon_name; + uint32_t cli_flags; + + int pam_status; + int response_delay; + struct response_data *resp_list; + + bool offline_auth; + bool last_auth_saved; + int priv; + int account_locked; + + uint32_t client_id_num; +#ifdef USE_KEYRING + key_serial_t key_serial; +#endif + bool passkey_local_done; +}; + +/** + * @brief Create new zero initialized struct pam_data. + * + * @param mem_ctx A memory context use to allocate the internal data + * @return A pointer to new struct pam_data + * NULL on error + * + * NOTE: This function should be the only way, how to create new empty + * struct pam_data, because this function automatically initialize sub + * structures and set destructor to created object. + */ +struct pam_data *create_pam_data(TALLOC_CTX *mem_ctx); +errno_t copy_pam_data(TALLOC_CTX *mem_ctx, struct pam_data *old_pd, + struct pam_data **new_pd); +void pam_print_data(int l, struct pam_data *pd); +int pam_add_response(struct pam_data *pd, + enum response_type type, + int len, const uint8_t *data); + +#endif /* _SSS_PAM_DATA_H_ */ diff --git a/src/util/sss_ptr_hash.c b/src/util/sss_ptr_hash.c new file mode 100644 index 0000000..115b0b9 --- /dev/null +++ b/src/util/sss_ptr_hash.c @@ -0,0 +1,395 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <talloc.h> +#include <dhash.h> + +#include "util/util.h" +#include "util/sss_ptr_hash.h" + +static bool sss_ptr_hash_check_type(void *ptr, const char *type) +{ + void *type_ptr; + + type_ptr = talloc_check_name(ptr, type); + if (type_ptr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid data type detected. Expected [%s], got [%s].\n", + type, talloc_get_name(ptr)); + return false; + } + + return true; +} + +static int sss_ptr_hash_table_destructor(hash_table_t *table) +{ + sss_ptr_hash_delete_all(table, false); + return 0; +} + +struct sss_ptr_hash_delete_data { + hash_delete_callback *callback; + void *pvt; +}; + +struct sss_ptr_hash_value { + hash_table_t *table; + const char *key; + void *payload; + bool delete_in_progress; +}; + +static int +sss_ptr_hash_value_destructor(struct sss_ptr_hash_value *value) +{ + hash_key_t table_key; + + /* Do not call hash_delete() if we got here from hash delete callback when + * the callback calls talloc_free(payload) which frees the value. This + * should not happen since talloc will avoid circular free but let's be + * over protective here. */ + if (value->delete_in_progress) { + return 0; + } + + value->delete_in_progress = true; + if (value->table && value->key) { + table_key.type = HASH_KEY_STRING; + table_key.str = discard_const_p(char, value->key); + if (hash_delete(value->table, &table_key) != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "failed to delete entry with key '%s'\n", value->key); + value->delete_in_progress = false; + } + } + + return 0; +} + +static struct sss_ptr_hash_value * +sss_ptr_hash_value_create(hash_table_t *table, + const char *key, + void *talloc_ptr) +{ + struct sss_ptr_hash_value *value; + + value = talloc_zero(talloc_ptr, struct sss_ptr_hash_value); + if (value == NULL) { + return NULL; + } + + value->key = talloc_strdup(value, key); + if (value->key == NULL) { + talloc_free(value); + return NULL; + } + + value->table = table; + value->payload = talloc_ptr; + talloc_set_destructor(value, sss_ptr_hash_value_destructor); + + return value; +} + +static void +sss_ptr_hash_delete_cb(hash_entry_t *item, + hash_destroy_enum deltype, + void *pvt) +{ + struct sss_ptr_hash_delete_data *data; + struct sss_ptr_hash_value *value; + struct hash_entry_t callback_entry; + + if (pvt == NULL) { + return; + } + + value = talloc_get_type(item->value.ptr, struct sss_ptr_hash_value); + if (value == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid value!\n"); + return; + } + + /* Switch to the input value and call custom callback. */ + data = talloc_get_type(pvt, struct sss_ptr_hash_delete_data); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid data!\n"); + return; + } + + callback_entry.key = item->key; + callback_entry.value.type = HASH_VALUE_PTR; + callback_entry.value.ptr = value->payload; + + /* Delete the value in case this callback has been called directly + * from dhash (overwriting existing entry) instead of hash_delete() + * in value's destructor. */ + if (!value->delete_in_progress) { + talloc_set_destructor(value, NULL); + talloc_free(value); + } + + /* Even if execution is already in the context of + * talloc_free(payload) -> talloc_free(value) -> ... + * there still might be legitimate reasons to execute callback. + */ + data->callback(&callback_entry, deltype, data->pvt); +} + +hash_table_t *sss_ptr_hash_create(TALLOC_CTX *mem_ctx, + hash_delete_callback *del_cb, + void *del_cb_pvt) +{ + struct sss_ptr_hash_delete_data *data = NULL; + hash_table_t *table; + errno_t ret; + + if (del_cb != NULL) { + data = talloc_zero(NULL, struct sss_ptr_hash_delete_data); + if (data == NULL) { + return NULL; + } + + data->callback = del_cb; + data->pvt = del_cb_pvt; + } + + ret = sss_hash_create_ex(mem_ctx, 0, &table, 0, 0, 0, 0, + sss_ptr_hash_delete_cb, data); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, sss_strerror(ret)); + talloc_free(data); + return NULL; + } + + if (data != NULL) { + talloc_steal(table, data); + } + + talloc_set_destructor(table, sss_ptr_hash_table_destructor); + + return table; +} + +errno_t _sss_ptr_hash_add(hash_table_t *table, + const char *key, + void *talloc_ptr, + const char *type, + bool override) +{ + struct sss_ptr_hash_value *value; + hash_value_t table_value; + hash_key_t table_key; + int hret; + + if (table == NULL || key == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid input!\n"); + return EINVAL; + } + + if (!sss_ptr_hash_check_type(talloc_ptr, type)) { + return ERR_INVALID_DATA_TYPE; + } + + table_key.type = HASH_KEY_STRING; + table_key.str = discard_const_p(char, key); + + if (override == false && hash_has_key(table, &table_key)) { + return EEXIST; + } + + value = sss_ptr_hash_value_create(table, key, talloc_ptr); + if (value == NULL) { + return ENOMEM; + } + + table_value.type = HASH_VALUE_PTR; + table_value.ptr = value; + + hret = hash_enter(table, &table_key, &table_value); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add key %s!\n", key); + talloc_free(value); + return EIO; + } + + return EOK; +} + +static struct sss_ptr_hash_value * +sss_ptr_hash_lookup_internal(hash_table_t *table, + const char *key) +{ + hash_value_t table_value; + hash_key_t table_key; + int hret; + + table_key.type = HASH_KEY_STRING; + table_key.str = discard_const_p(char, key); + + hret = hash_lookup(table, &table_key, &table_value); + if (hret == HASH_ERROR_KEY_NOT_FOUND) { + return NULL; + } else if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to search hash table [%d]\n", hret); + return NULL; + } + + /* Check value type. */ + if (table_value.type != HASH_VALUE_PTR) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid value type found: %d\n", + table_value.type); + return NULL; + } + + if (!sss_ptr_hash_check_type(table_value.ptr, "struct sss_ptr_hash_value")) { + return NULL; + } + + return table_value.ptr; +} + +void *_sss_ptr_hash_lookup(hash_table_t *table, + const char *key, + const char *type) +{ + struct sss_ptr_hash_value *value; + + value = sss_ptr_hash_lookup_internal(table, key); + if (value == NULL || value->payload == NULL) { + return NULL; + } + + if (!sss_ptr_hash_check_type(value->payload, type)) { + return NULL; + } + + return value->payload; +} + +void *_sss_ptr_get_value(hash_value_t *table_value, + const char *type) +{ + struct sss_ptr_hash_value *value; + + /* Check value type. */ + if (table_value->type != HASH_VALUE_PTR) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid value type found: %d\n", + table_value->type); + return NULL; + } + + if (!sss_ptr_hash_check_type(table_value->ptr, "struct sss_ptr_hash_value")) { + return NULL; + } + + value = table_value->ptr; + + if (!sss_ptr_hash_check_type(value->payload, type)) { + return NULL; + } + + return value->payload; +} + +void sss_ptr_hash_delete(hash_table_t *table, + const char *key, + bool free_value) +{ + struct sss_ptr_hash_value *value; + void *payload = NULL; + + if (table == NULL || key == NULL) { + return; + } + + value = sss_ptr_hash_lookup_internal(table, key); + if (value == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to remove key '%s' from table\n", key); + return; + } + + if (free_value) { + payload = value->payload; + } + + talloc_free(value); /* this will call hash_delete() in value d-tor */ + + talloc_free(payload); /* it is safe to call talloc_free(NULL) */ + + return; +} + +void sss_ptr_hash_delete_all(hash_table_t *table, + bool free_values) +{ + hash_value_t *content; + struct sss_ptr_hash_value *value; + void *payload = NULL; + unsigned long count; + unsigned long i; + int hret; + + if (table == NULL) { + return; + } + + hret = hash_values(table, &count, &content); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get values [%d]\n", hret); + return; + } + + for (i = 0; i < count; ++i) { + if ((content[i].type == HASH_VALUE_PTR) && + sss_ptr_hash_check_type(content[i].ptr, + "struct sss_ptr_hash_value")) { + value = content[i].ptr; + if (free_values) { + payload = value->payload; + } + talloc_free(value); + if (free_values) { + talloc_free(payload); /* it's safe to call talloc_free(NULL) */ + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected type of table content, skipping"); + } + } + + talloc_free(content); + + return; +} + +bool sss_ptr_hash_has_key(hash_table_t *table, + const char *key) +{ + hash_key_t table_key; + + table_key.type = HASH_KEY_STRING; + table_key.str = discard_const_p(char, key); + + return hash_has_key(table, &table_key); +} diff --git a/src/util/sss_ptr_hash.h b/src/util/sss_ptr_hash.h new file mode 100644 index 0000000..0889b17 --- /dev/null +++ b/src/util/sss_ptr_hash.h @@ -0,0 +1,145 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SSS_PTR_HASH_H_ +#define _SSS_PTR_HASH_H_ + +#include <talloc.h> +#include <dhash.h> + +#include "util/util.h" + +/** + * Create a new hash table with string key and talloc pointer value with + * possible custom delete callback @del_cb. + * Table will have destructor setup to wipe content. + * Never call hash_destroy(table) and hash_delete() explicitly but rather + * use talloc_free(table) and sss_ptr_hash_delete(). + * + * A notes about @del_cb: + * - this callback must never modify hash table (i.e. add/del entries); + * - this callback is triggered when value is either explicitly removed + * from the table or simply freed (latter leads to removal of an entry + * from the table); + * - this callback is also triggered for every entry when table is freed + * entirely. In this case (deltype == HASH_TABLE_DESTROY) any table + * lookups / iteration are forbidden as table might be already invalidated. + */ +hash_table_t *sss_ptr_hash_create(TALLOC_CTX *mem_ctx, + hash_delete_callback *del_cb, + void *del_cb_pvt); + +/** + * Add a new value @talloc_ptr of type @type into the table. + * + * If the @key already exist in the table and @override is true, + * the value is overridden. Otherwise EEXIST error is returned. + * + * If talloc_ptr is freed the key and value are automatically + * removed from the hash table (del_cb that was set up during + * table creation is executed as a first step of this removal). + * + * @return EOK If the <@key, @talloc_ptr> pair was inserted. + * @return EEXIST If @key already exists and @override is false. + * @return Other errno code in case of an error. + */ +errno_t _sss_ptr_hash_add(hash_table_t *table, + const char *key, + void *talloc_ptr, + const char *type, + bool override); + +/** + * Add a new value @talloc_ptr of type @type into the table. + * + * If talloc_ptr is freed the key and value are automatically + * removed from the hash table. + * + * @return EOK If the <@key, @talloc_ptr> pair was inserted. + * @return EEXIST If @key already exists. + * @return Other errno code in case of an error. + */ +#define sss_ptr_hash_add(table, key, talloc_ptr, type) \ + _sss_ptr_hash_add(table, key, talloc_ptr, #type, false) + +/** + * Add a new value @talloc_ptr of type @type into the table. + * + * If the @key already exists in the table, its value is + * overridden. If talloc_ptr is freed the key and value + * are automatically removed from the hash table. + * + * @return EOK If the <@key, @talloc_ptr> pair was inserted. + * @return Other errno code in case of an error. + */ +#define sss_ptr_hash_add_or_override(table, key, talloc_ptr, type) \ + _sss_ptr_hash_add(table, key, talloc_ptr, #type, true) + +void *_sss_ptr_hash_lookup(hash_table_t *table, + const char *key, + const char *type); + +/** + * Lookup @key in the table and return its value as typed to @type. + * The type of the value must match with @type, otherwise NULL is returned. + * + * @return talloc_ptr If the value is found as type matches. + * @return NULL If the value is not found or if the type is invalid. + */ +#define sss_ptr_hash_lookup(table, key, type) \ + (type *)_sss_ptr_hash_lookup(table, key, #type) + +void *_sss_ptr_get_value(hash_value_t *table_value, + const char *type); + +/** + * Obtain inserted talloc pointer from table value typed to @type. + * The type of the value must match with @type, otherwise NULL is returned. + * + * @return talloc_ptr If the value is found as type matches. + * @return NULL If the value is not found or if the type is invalid. + */ +#define sss_ptr_get_value(table_value, type) \ + (type *)_sss_ptr_get_value(table_value, #type) + +/** + * Delete @key from table. If @free_value is true then also the value + * associated with @key is freed, otherwise it is left intact. + */ +void sss_ptr_hash_delete(hash_table_t *table, + const char *key, + bool free_value); + +/** + * Delete all keys from the table. If @free_value sis true then also + * the values associated with those keys are reed, otherwise + * they are left intact. + */ +void sss_ptr_hash_delete_all(hash_table_t *table, + bool free_values); + +/** + * @return true If @key is present in the table. + * @return false Otherwise. + */ +bool sss_ptr_hash_has_key(hash_table_t *table, + const char *key); + +#endif /* _SSS_PTR_HASH_H_ */ diff --git a/src/util/sss_ptr_list.c b/src/util/sss_ptr_list.c new file mode 100644 index 0000000..5be9b50 --- /dev/null +++ b/src/util/sss_ptr_list.c @@ -0,0 +1,173 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2018 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <talloc.h> + +#include "util/util.h" +#include "util/dlinklist.h" +#include "util/sss_ptr_list.h" + +struct sss_ptr_list { + struct sss_ptr_list_item *head; + bool in_destructor; + bool free_data; +}; + +struct sss_ptr_list_spy { + struct sss_ptr_list_item *item; +}; + +static int +sss_ptr_list_spy_destructor(struct sss_ptr_list_spy *spy) +{ + spy->item->ptr = NULL; + spy->item->spy = NULL; + talloc_free(spy->item); + return 0; +} + +static int +sss_ptr_list_item_destructor(struct sss_ptr_list_item *item) +{ + if (item->spy != NULL) { + talloc_set_destructor(item->spy, NULL); + talloc_free(item->spy); + } + + if (item->list == NULL) { + return 0; + } + + if (item->list->free_data && item->ptr != NULL) { + talloc_free(item->ptr); + } + + if (item->list->in_destructor) { + return 0; + } + + DLIST_REMOVE(item->list->head, item); + return 0; +} + +static int +sss_ptr_list_destructor(struct sss_ptr_list *list) +{ + list->in_destructor = true; + + return 0; +} + +static struct sss_ptr_list_spy * +sss_ptr_list_spy_create(struct sss_ptr_list_item *item, void *ptr) +{ + struct sss_ptr_list_spy *spy; + + spy = talloc_zero(ptr, struct sss_ptr_list_spy); + if (spy == NULL) { + return NULL; + } + + spy->item = item; + + talloc_set_destructor(spy, sss_ptr_list_spy_destructor); + + return spy; +} + +struct sss_ptr_list * +sss_ptr_list_create(TALLOC_CTX *mem_ctx, bool free_data_on_removal) +{ + struct sss_ptr_list *list; + + list = talloc_zero(mem_ctx, struct sss_ptr_list); + if (list == NULL) { + return NULL; + } + + list->free_data = free_data_on_removal; + + talloc_set_destructor(list, sss_ptr_list_destructor); + return list; +} + +errno_t +sss_ptr_list_add(struct sss_ptr_list *list, void *ptr) +{ + struct sss_ptr_list_item *item; + + item = talloc_zero(list, struct sss_ptr_list_item); + if (item == NULL) { + return ENOMEM; + } + + item->ptr = ptr; + item->list = list; + item->spy = sss_ptr_list_spy_create(item, ptr); + if (item->spy == NULL) { + talloc_free(item); + return ENOMEM; + } + + DLIST_ADD(list->head, item); + + talloc_set_destructor(item, sss_ptr_list_item_destructor); + + return EOK; +} + +void +sss_ptr_list_remove(struct sss_ptr_list *list, void *ptr) +{ + struct sss_ptr_list_item *item; + + item = sss_ptr_list_find(list, ptr); + if (item == NULL) { + return; + } + + talloc_free(item); +} + +struct sss_ptr_list_item * +sss_ptr_list_find(struct sss_ptr_list *list, void *ptr) +{ + struct sss_ptr_list_item *item; + + DLIST_FOR_EACH(item, list->head) { + if (item->ptr == ptr) { + return item; + } + } + + return NULL; +} + +struct sss_ptr_list_item * +sss_ptr_list_head(struct sss_ptr_list *list) +{ + return list->head; +} + +bool +sss_ptr_list_is_empty(struct sss_ptr_list *list) +{ + return list == NULL || list->head == NULL; +} diff --git a/src/util/sss_ptr_list.h b/src/util/sss_ptr_list.h new file mode 100644 index 0000000..dc40ea3 --- /dev/null +++ b/src/util/sss_ptr_list.h @@ -0,0 +1,120 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2018 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SSS_PTR_LIST_H_ +#define _SSS_PTR_LIST_H_ + +#include <talloc.h> + +#include "util/util.h" +#include "util/dlinklist.h" + +struct sss_ptr_list; +struct sss_ptr_list_spy; + +struct sss_ptr_list_item { + void *ptr; + + struct sss_ptr_list *list; + struct sss_ptr_list_spy *spy; + struct sss_ptr_list_item *prev; + struct sss_ptr_list_item *next; +}; + +/** + * Create new linked list. + * + * @param mem_ctx Memory context. + * @param free_data_on_removal If true than the stored pointer is freed when + * it is being removed from the list or when + * the list is freed. + * @return New list or NULL on failure. + */ +struct sss_ptr_list * +sss_ptr_list_create(TALLOC_CTX *mem_ctx, bool free_data_on_removal); + +/** + * Obtain head of the list that can be used with DLIST_* macros. + * + * @return Head of the list or NULL if it is empty. + */ +struct sss_ptr_list_item * +sss_ptr_list_head(struct sss_ptr_list *list); + +/** + * @return True if the list is empty, false otherwise. + */ +bool +sss_ptr_list_is_empty(struct sss_ptr_list *list); + +/** + * Add new item (must be a talloc context) to the list. + * + * The list item will be automatically removed from the list if @ptr is freed. + * To remove the item from the list, call talloc_free(item). + * + * @param list Linked list. + * @param ptr Talloc pointer to add to the list. + * + * @return New EOK on success, other errno code on error. + */ +errno_t +sss_ptr_list_add(struct sss_ptr_list *list, void *ptr); + +/** + * Remove stored pointer from the list. + * + * If @free_data_on_removal was true when creating the list, the pointer will + * be automatically freed. + * + * @param list Linked list. + * @param ptr Talloc pointer to remove from the list. + */ +void +sss_ptr_list_remove(struct sss_ptr_list *list, void *ptr); + +/** + * Find pointer in the list and return list item containing it. + * + * @param list Linked list. + * @param ptr Talloc pointer to search for. + * + * @return List item if found, NULL otherwise.. + */ +struct sss_ptr_list_item * +sss_ptr_list_find(struct sss_ptr_list *list, void *ptr); + +/** + * Return value stored inside linked list item. + * + * @param item Linked list item. + * @param type Type to look for. + * + * @return The value. + */ +#define sss_ptr_list_value(item, type) \ + talloc_get_type(item->ptr, type) + +#define SSS_PTR_LIST_FOR_EACH(list, value, type) \ + for (struct sss_ptr_list_item *__item = sss_ptr_list_head(list); \ + __item != NULL && (((value) = sss_ptr_list_value(__item, type)), 1); \ + __item = __item->next) + +#endif /* _SSS_PTR_LIST_H_ */ diff --git a/src/util/sss_python.c b/src/util/sss_python.c new file mode 100644 index 0000000..2b01c6e --- /dev/null +++ b/src/util/sss_python.c @@ -0,0 +1,60 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2011 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "src/util/sss_python.h" + +PyObject * +sss_exception_with_doc(const char *name, const char *doc, PyObject *base, + PyObject *dict) +{ +#if PY_VERSION_HEX >= 0x03080000 + return PyErr_NewExceptionWithDoc(name, doc, base, dict); +#elif PY_VERSION_HEX >= 0x02070000 + return PyErr_NewExceptionWithDoc(discard_const_p(char, name), + discard_const_p(char, doc), base, dict); +#else + int result; + PyObject *ret = NULL; + PyObject *mydict = NULL; /* points to the dict only if we create it */ + PyObject *docobj; + + if (dict == NULL) { + dict = mydict = PyDict_New(); + if (dict == NULL) { + return NULL; + } + } + + if (doc != NULL) { + docobj = PyString_FromString(doc); + if (docobj == NULL) + goto failure; + result = PyDict_SetItemString(dict, "__doc__", docobj); + Py_DECREF(docobj); + if (result < 0) + goto failure; + } + + ret = PyErr_NewException(discard_const_p(char, name), base, dict); + failure: + Py_XDECREF(mydict); + return ret; +#endif +} diff --git a/src/util/sss_python.h b/src/util/sss_python.h new file mode 100644 index 0000000..ea38422 --- /dev/null +++ b/src/util/sss_python.h @@ -0,0 +1,68 @@ +#ifndef __SSS_PYTHON_H__ +#define __SSS_PYTHON_H__ + +#include "config.h" + +#include <Python.h> +#include <stdbool.h> + +#include "util/util.h" + +#if PY_VERSION_HEX < 0x02050000 +#define sss_py_const_p(type, value) discard_const_p(type, (value)) +#else +#define sss_py_const_p(type, value) (value) +#endif + +#if PY_MAJOR_VERSION >= 3 +#define IS_PY3K + +#define MODINITERROR(module) do { \ + Py_XDECREF(module); \ + return NULL; \ +} while(0) + +#define PYNUMBER_CHECK(what) PyLong_Check(what) +#define PYNUMBER_FROMLONG(what) PyLong_FromLong(what) +#define PYNUMBER_ASLONG(what) PyLong_AsLong(what) +#else /* PY_MAJOR_VERSION < 3 */ +#include <bytesobject.h> + +#define MODINITERROR(module) do { \ + Py_XDECREF(module); \ + return; \ +} while(0) + +#define PYNUMBER_CHECK(what) PyInt_Check(what) +#define PYNUMBER_FROMLONG(what) PyInt_FromLong(what) +#define PYNUMBER_ASLONG(what) PyInt_AsLong(what) +#endif /* PY_MAJOR_VERSION < 3 */ + +/* Exceptions compatibility */ +PyObject * +sss_exception_with_doc(const char *name, const char *doc, PyObject *base, + PyObject *dict); + +/* Convenience macros */ +#define TYPE_READY(module, type, name) do { \ + if (PyType_Ready(&type) < 0) { \ + MODINITERROR(module); \ + } \ + Py_INCREF(&type); \ + if (PyModule_AddObject(module, \ + discard_const_p(char, name), \ + (PyObject *) &type) == -1) { \ + Py_XDECREF(&type); \ + MODINITERROR(module); \ + } \ +} while(0) \ + +#define SAFE_SET(old, new) do { \ + PyObject *__simple_set_tmp = NULL; \ + __simple_set_tmp = old; \ + Py_INCREF(new); \ + old = new; \ + Py_XDECREF(__simple_set_tmp); \ +} while(0) + +#endif /* __SSS_PYTHON_H__ */ diff --git a/src/util/sss_regexp.c b/src/util/sss_regexp.c new file mode 100644 index 0000000..3fc1a7b --- /dev/null +++ b/src/util/sss_regexp.c @@ -0,0 +1,190 @@ +/* + SSSD + + Authors: + Tomas Halman <thalman@redhat.com> + + Copyright (C) 2019 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/sss_regexp.h" +#include <string.h> +#include "util/util_errors.h" +#include "util/debug.h" + +#define SSS_REGEXP_OVEC_SIZE 30 +#define SSS_REGEXP_ERR_MSG_SIZE 120 /* 120 is recomended by pcre2 doc */ + +#ifndef EOK +#define EOK 0 +#endif + +/* + * sss_regexp with pcre2 + */ +struct _sss_regexp_t { + pcre2_code *re; + pcre2_match_data *match_data; + char *matched_string; +}; + +static int sss_regexp_pcre2_destroy(sss_regexp_t *self) +{ + if (self->re) { + pcre2_code_free(self->re); + } + if (self->match_data) { + pcre2_match_data_free(self->match_data); + } + if (self->matched_string) { + pcre2_substring_free((PCRE2_UCHAR *)self->matched_string); + } + return 0; +} + +static int sss_regexp_pcre2_compile(sss_regexp_t *self, + const char *pattern, + int options) +{ + int errorcode; + unsigned char errormsg[SSS_REGEXP_ERR_MSG_SIZE]; + size_t erroroffset; + + self->re = pcre2_compile((PCRE2_SPTR)pattern, + strlen(pattern), + options, + &errorcode, + &erroroffset, + NULL); + if (self->re == NULL) { + pcre2_get_error_message(errorcode, + errormsg, + SSS_REGEXP_ERR_MSG_SIZE); + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid Regular Expression pattern " + "at position %zu. (Error: %d [%s])\n", erroroffset, errorcode, errormsg); + return errorcode; + } + return EOK; +} + +static int sss_regexp_pcre2_match(sss_regexp_t *self, + const char *subject, + int startoffset, + int options) +{ + if (!self->re) { + return SSS_REGEXP_ERROR_NOMATCH; + } + if (self->match_data) { + pcre2_match_data_free(self->match_data); + } + self->match_data = pcre2_match_data_create_from_pattern(self->re, NULL); + if (!self->match_data) { + return SSS_REGEXP_ERROR_NOMEMORY; + } + return pcre2_match(self->re, + (PCRE2_SPTR)subject, + strlen(subject), + startoffset, + options, + self->match_data, + NULL); +} + +static int sss_regexp_pcre2_get_named_substring(sss_regexp_t *self, + const char *name, + const char **value) +{ + PCRE2_SIZE length; + int rc; + + if (self->matched_string) { + pcre2_substring_free((PCRE2_UCHAR *)(self->matched_string)); + self->matched_string = NULL; + } + rc = pcre2_substring_get_byname(self->match_data, + (PCRE2_SPTR)name, + (PCRE2_UCHAR **) &self->matched_string, + &length); + *value = self->matched_string; + return rc; +} + +/* + * sss_regexp talloc destructor + */ +static int sss_regexp_destroy(sss_regexp_t *self) +{ + if (!self) return 0; + return sss_regexp_pcre2_destroy(self); +} + +/* + * sss_regexp constructor + */ +int sss_regexp_new(TALLOC_CTX *mem_ctx, + const char *pattern, + int options, + sss_regexp_t **self_p) +{ + int ret; + sss_regexp_t *self = talloc_zero(mem_ctx, sss_regexp_t); + if (!self) { + DEBUG(SSSDBG_CRIT_FAILURE, "Not enough memory for sss_regexp_t.\n"); + *self_p = NULL; + return SSS_REGEXP_ERROR_NOMEMORY; + } + talloc_set_destructor(self, sss_regexp_destroy); + + ret = sss_regexp_pcre2_compile(self, + pattern, + options); + if (ret != EOK) { + talloc_free(self); + self = NULL; + } + *self_p = self; + return ret; +} + +/* + * sss_regexp match function + */ +int sss_regexp_match(sss_regexp_t *self, + const char *subject, + int startoffset, + int options) +{ + if (!self || !self->re || !subject) return SSS_REGEXP_ERROR_NOMATCH; + + return sss_regexp_pcre2_match(self, subject, startoffset, options); +} + + +/* + * sss_regexp get named substring + */ +int sss_regexp_get_named_substring(sss_regexp_t *self, + const char *name, + const char **value) +{ + if (!self || !self->re || !name) { + *value = NULL; + return SSS_REGEXP_ERROR_NOMATCH; + } + + return sss_regexp_pcre2_get_named_substring(self, name, value); +} diff --git a/src/util/sss_regexp.h b/src/util/sss_regexp.h new file mode 100644 index 0000000..2b29361 --- /dev/null +++ b/src/util/sss_regexp.h @@ -0,0 +1,83 @@ +/* + SSSD + + Authors: + Tomas Halman <thalman@redhat.com> + + Copyright (C) 2018 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef SSS_REGEXP_H_ +#define SSS_REGEXP_H_ + +#include <stddef.h> +#include <talloc.h> +#include "config.h" + +/* regexp class */ +typedef struct _sss_regexp_t sss_regexp_t; + +#include <pcre2.h> +#define SSS_REGEXP_ERROR_NOMATCH PCRE2_ERROR_NOMATCH +#define SSS_REGEXP_ERROR_NOMEMORY PCRE2_ERROR_NOMEMORY +#define SSS_REGEXP_NOTEMPTY PCRE2_NOTEMPTY +#define SSS_REGEXP_EXTENDED PCRE2_EXTENDED +#define SSS_REGEXP_DUPNAMES PCRE2_DUPNAMES + +/* how to use sss_regexp: + * + * int err; + * const char *found; + * + * sss_regexp_t *re + * err = sss_regexp_new (NULL, "#(?P<myname>.+)#", 0, &re); + * if (err != EOK) { + * goto fail; + * } + * int rc = sss_regexp_match (re, + * "a#findthis#b", + * 0, + * 0); + * if (rc != 0) { ... } + * rc = sss_regexp_get_named_substring (re, "myname", &found); + * ... + * talloc_free (re); + */ + +/* + * Create new compiled regexp object. + */ +int sss_regexp_new(TALLOC_CTX *mem_ctx, + const char *pattern, + int options, + sss_regexp_t **self_p); + +/* + * Search subject with previously created regexp. + */ +int sss_regexp_match(sss_regexp_t *self, + const char *subject, + int startoffset, + int options); + +/* + * Get named substring from last sss_regexp_match. + */ +int sss_regexp_get_named_substring(sss_regexp_t *self, + const char *name, + const char **value); + +#endif /* SSS_REGEXP_H_ */ diff --git a/src/util/sss_selinux.c b/src/util/sss_selinux.c new file mode 100644 index 0000000..165aeca --- /dev/null +++ b/src/util/sss_selinux.c @@ -0,0 +1,255 @@ +/* + SSSD + + SELinux-related utility functions + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/sss_selinux.h" +#include "util/sss_utf8.h" +#include "db/sysdb_selinux.h" + +static bool match_entity(struct ldb_message_element *values, + struct ldb_message_element *sought_values) +{ + int i, j; + + for (i = 0; i < values->num_values; i++) { + for (j = 0; j < sought_values->num_values; j++) { + if (values->values[i].length != sought_values->values[j].length) { + continue; + } + + if (strncasecmp((char *)values->values[i].data, + (char *)sought_values->values[j].data, + values->values[i].length) == 0) + return true; + } + } + + return false; +} + +bool sss_selinux_match(struct sysdb_attrs *usermap, + struct sysdb_attrs *user, + struct sysdb_attrs *host, + uint32_t *_priority) +{ + struct ldb_message_element *users_el = NULL; + struct ldb_message_element *usercat = NULL; + struct ldb_message_element *hosts_el = NULL; + struct ldb_message_element *hostcat = NULL; + struct ldb_message_element *dn; + struct ldb_message_element *memberof; + int i; + uint32_t priority = 0; + bool matched_name; + bool matched_group; + bool matched_category; + errno_t ret; + + if (usermap == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "NULL given as usermap! Skipping ...\n"); + return false; + } + + /* Search for user and host related elements */ + for (i = 0; i < usermap->num; i++) { + if (!strcasecmp(usermap->a[i].name, SYSDB_ORIG_MEMBER_USER)) { + users_el = &usermap->a[i]; + } else if (!strcasecmp(usermap->a[i].name, SYSDB_ORIG_MEMBER_HOST)) { + hosts_el = &usermap->a[i]; + } else if (!strcasecmp(usermap->a[i].name, SYSDB_USER_CATEGORY)) { + usercat = &usermap->a[i]; + } else if (!strcasecmp(usermap->a[i].name, SYSDB_HOST_CATEGORY)) { + hostcat = &usermap->a[i]; + } + } + + if (user) { + ret = sysdb_attrs_get_el(user, SYSDB_ORIG_DN, &dn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "User does not have origDN\n"); + return false; + } + ret = sysdb_attrs_get_el(user, SYSDB_ORIG_MEMBEROF, &memberof); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_ALL, + "User does not have orig memberof, " + "therefore it can't match to any rule\n"); + return false; + } + + /** + * The rule won't match if user category != "all" and user map doesn't + * contain neither user nor any of his groups in memberUser attribute + */ + matched_category = false; + if (usercat != NULL) { + for (i = 0; i < usercat->num_values; i++) { + if (strcasecmp((char *)usercat->values[i].data, "all") == 0) { + matched_category = true; + break; + } + } + } + + if (!matched_category) { + if (users_el == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No users specified in the rule!\n"); + return false; + } else { + matched_name = match_entity(users_el, dn); + matched_group = match_entity(users_el, memberof); + if (matched_name) { + priority |= SELINUX_PRIORITY_USER_NAME; + } else if (matched_group) { + priority |= SELINUX_PRIORITY_USER_GROUP; + } else { + DEBUG(SSSDBG_TRACE_ALL, "User did not match\n"); + return false; + } + } + } else { + priority |= SELINUX_PRIORITY_USER_CAT; + } + } + + if (host) { + ret = sysdb_attrs_get_el(host, SYSDB_ORIG_DN, &dn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Host does not have origDN\n"); + return false; + } + ret = sysdb_attrs_get_el(host, SYSDB_ORIG_MEMBEROF, &memberof); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_ALL, + "Host does not have orig memberof, " + "therefore it can't match to any rule\n"); + return false; + } + + /** + * The rule won't match if host category != "all" and user map doesn't + * contain neither host nor any of its groups in memberHost attribute + */ + matched_category = false; + if (hostcat != NULL) { + for (i = 0; i < hostcat->num_values; i++) { + if (strcasecmp((char *)hostcat->values[i].data, "all") == 0) { + matched_category = true; + break; + } + } + } + if (!matched_category) { + if (hosts_el == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No users specified in the rule!\n"); + return false; + } else { + matched_name = match_entity(hosts_el, dn); + matched_group = match_entity(hosts_el, memberof); + if (matched_name) { + priority |= SELINUX_PRIORITY_HOST_NAME; + } else if (matched_group) { + priority |= SELINUX_PRIORITY_HOST_GROUP; + } else { + DEBUG(SSSDBG_TRACE_ALL, "Host did not match\n"); + return false; + } + } + } else { + priority |= SELINUX_PRIORITY_HOST_CAT; + } + } + + if (_priority != NULL) { + *_priority = priority; + } + + return true; +} + +errno_t sss_selinux_extract_user(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *username, + struct sysdb_attrs **_user_attrs) +{ + TALLOC_CTX *tmp_ctx; + const char **attrs; + struct sysdb_attrs *user_attrs; + struct ldb_message *user_msg; + + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + attrs = talloc_array(tmp_ctx, const char *, 3); + if (attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_array failed.\n"); + ret = ENOMEM; + goto done; + } + attrs[0] = SYSDB_ORIG_DN; + attrs[1] = SYSDB_ORIG_MEMBEROF; + attrs[2] = NULL; + + ret = sysdb_search_user_by_name(tmp_ctx, domain, username, attrs, + &user_msg); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_name failed.\n"); + goto done; + } + + user_attrs = talloc_zero(tmp_ctx, struct sysdb_attrs); + if (user_attrs == NULL) { + ret = ENOMEM; + goto done; + } + user_attrs->a = talloc_steal(user_attrs, user_msg->elements); + user_attrs->num = user_msg->num_elements; + + *_user_attrs = talloc_steal(mem_ctx, user_attrs); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +const char *sss_selinux_map_get_seuser(struct sysdb_attrs *usermap) +{ + int i; + const uint8_t *name; + const uint8_t *template = (const uint8_t *)SYSDB_SELINUX_USER; + + for (i = 0; i < usermap->num; i++) { + name = (const uint8_t *)usermap->a[i].name; + if (sss_utf8_case_eq(name, template) == 0) { + return (const char *)usermap->a[i].values[0].data; + } + } + + return NULL; +} diff --git a/src/util/sss_selinux.h b/src/util/sss_selinux.h new file mode 100644 index 0000000..8821e73 --- /dev/null +++ b/src/util/sss_selinux.h @@ -0,0 +1,54 @@ +/* + SSSD + + SELinux-related utility functions + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef SSS_SELINUX_H_ +#define SSS_SELINUX_H_ + +#include <talloc.h> +#include <errno.h> + +#include <db/sysdb.h> + +#define SELINUX_PRIORITY_USER_CAT 1 +#define SELINUX_PRIORITY_USER_GROUP 2 +#define SELINUX_PRIORITY_USER_NAME 4 +/* According to specification, host has higher priority */ +#define SELINUX_PRIORITY_HOST_CAT 8 +#define SELINUX_PRIORITY_HOST_GROUP 16 +#define SELINUX_PRIORITY_HOST_NAME 32 + +errno_t +sss_selinux_extract_user(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *username, + struct sysdb_attrs **_user_attrs); + +bool sss_selinux_match(struct sysdb_attrs *usermap, + struct sysdb_attrs *user, + struct sysdb_attrs *host, + uint32_t *_priority); + +const char *sss_selinux_map_get_seuser(struct sysdb_attrs *usermap); + +#endif /* SSS_SELINUX_H_ */ diff --git a/src/util/sss_semanage.c b/src/util/sss_semanage.c new file mode 100644 index 0000000..10a567c --- /dev/null +++ b/src/util/sss_semanage.c @@ -0,0 +1,480 @@ +/* + SSSD + + sss_semanage.c + + Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <stdio.h> +#if defined(HAVE_SEMANAGE) && defined(HAVE_SELINUX) +#include <semanage/semanage.h> +#include <selinux/selinux.h> +#endif + +#include "util/util.h" + +#ifndef DEFAULT_SERANGE +#define DEFAULT_SERANGE "s0" +#endif + +#if defined(HAVE_SEMANAGE) && defined(HAVE_SELINUX) +/* turn libselinux messages into SSSD DEBUG() calls */ +static void sss_semanage_error_callback(void *varg, + semanage_handle_t *handle, + const char *fmt, ...) +{ + int level = SSSDBG_INVALID; + va_list ap; + + switch (semanage_msg_get_level(handle)) { + case SEMANAGE_MSG_ERR: + level = SSSDBG_CRIT_FAILURE; + break; + case SEMANAGE_MSG_WARN: + level = SSSDBG_MINOR_FAILURE; + break; + case SEMANAGE_MSG_INFO: + level = SSSDBG_TRACE_FUNC; + break; + } + + va_start(ap, fmt); + sss_vdebug_fn(__FILE__, __LINE__, "libsemanage", level, + APPEND_LINE_FEED, fmt, ap); + va_end(ap); +} + +static void sss_semanage_close(semanage_handle_t *handle) +{ + if (handle == NULL) { + return; /* semanage uses asserts */ + } + + if (semanage_is_connected(handle)) { + semanage_disconnect(handle); + } + semanage_handle_destroy(handle); +} + +static int sss_is_selinux_managed(semanage_handle_t *handle) +{ + int ret; + + if (handle == NULL) { + return EINVAL; + } + + if (!is_selinux_enabled()) { + return ERR_SELINUX_NOT_MANAGED; + } + + ret = semanage_is_managed(handle); + if (ret == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "SELinux policy not managed via libsemanage\n"); + return ERR_SELINUX_NOT_MANAGED; + } else if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "Call to semanage_is_managed failed\n"); + return EIO; + } + + return EOK; +} + +static int sss_semanage_init(semanage_handle_t **_handle) +{ + int ret; + semanage_handle_t *handle = NULL; + + handle = semanage_handle_create(); + if (!handle) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create SELinux management handle\n"); + ret = EIO; + goto done; + } + + semanage_msg_set_callback(handle, + sss_semanage_error_callback, + NULL); + + ret = sss_is_selinux_managed(handle); + if (ret != EOK) { + goto done; + } + + ret = semanage_access_check(handle); + if (ret < SEMANAGE_CAN_READ) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot read SELinux policy store\n"); + ret = EACCES; + goto done; + } + + ret = semanage_connect(handle); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot estabilish SELinux management connection\n"); + ret = EIO; + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + sss_semanage_close(handle); + } else { + *_handle = handle; + } + + return ret; +} + +static int sss_semanage_user_add(semanage_handle_t *handle, + semanage_seuser_key_t *key, + const char *login_name, + const char *seuser_name, + const char *mls) +{ + int ret; + semanage_seuser_t *seuser = NULL; + + ret = semanage_seuser_create(handle, &seuser); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot create SELinux login mapping for %s\n", login_name); + ret = EIO; + goto done; + } + + ret = semanage_seuser_set_name(handle, seuser, login_name); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not set name for %s\n", login_name); + ret = EIO; + goto done; + } + + ret = semanage_seuser_set_mlsrange(handle, seuser, + mls ? mls : DEFAULT_SERANGE); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not set serange for %s\n", login_name); + ret = EIO; + goto done; + } + + ret = semanage_seuser_set_sename(handle, seuser, seuser_name); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not set SELinux user for %s\n", login_name); + ret = EIO; + goto done; + } + + ret = semanage_seuser_modify_local(handle, key, seuser); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add login mapping for %s\n", login_name); + ret = EIO; + goto done; + } + + ret = EOK; +done: + semanage_seuser_free(seuser); + return ret; +} + +static int sss_semanage_user_mod(semanage_handle_t *handle, + semanage_seuser_key_t *key, + const char *login_name, + const char *seuser_name, + const char *mls) +{ + int ret; + semanage_seuser_t *seuser = NULL; + + semanage_seuser_query(handle, key, &seuser); + if (seuser == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not query seuser for %s\n", login_name); + ret = EIO; + goto done; + } + + ret = semanage_seuser_set_mlsrange(handle, seuser, + mls ? mls : DEFAULT_SERANGE); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not set serange for %s\n", login_name); + ret = EIO; + goto done; + } + + ret = semanage_seuser_set_sename(handle, seuser, seuser_name); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not set sename for %s\n", login_name); + ret = EIO; + goto done; + } + + ret = semanage_seuser_modify_local(handle, key, seuser); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not modify login mapping for %s\n", login_name); + ret = EIO; + goto done; + } + + ret = EOK; +done: + semanage_seuser_free(seuser); + return ret; +} + +int sss_seuser_exists(const char *linuxuser) +{ + int ret; + int exists; + semanage_seuser_key_t *sm_key = NULL; + semanage_handle_t *sm_handle = NULL; + + ret = sss_semanage_init(&sm_handle); + if (ret != EOK) { + return ret; + } + + ret = semanage_seuser_key_create(sm_handle, linuxuser, &sm_key); + if (ret < 0) { + sss_semanage_close(sm_handle); + return EIO; + } + + ret = semanage_seuser_exists(sm_handle, sm_key, &exists); + semanage_seuser_key_free(sm_key); + sss_semanage_close(sm_handle); + if (ret < 0) { + return EIO; + } + + DEBUG(SSSDBG_TRACE_FUNC, "seuser exists: %s\n", exists ? "yes" : "no"); + + return exists ? EOK : ERR_SELINUX_USER_NOT_FOUND; +} + +int sss_get_seuser(const char *linuxuser, + char **selinuxuser, + char **level) +{ + int ret; + semanage_handle_t *handle; + + handle = semanage_handle_create(); + if (handle == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create SELinux management handle\n"); + return EIO; + } + + semanage_msg_set_callback(handle, + sss_semanage_error_callback, + NULL); + + /* We only needed the handle for this call. Close the handle right + * after it */ + ret = sss_is_selinux_managed(handle); + sss_semanage_close(handle); + if (ret != EOK) { + return ret; + } + + return getseuserbyname(linuxuser, selinuxuser, level); +} + +int sss_set_seuser(const char *login_name, const char *seuser_name, + const char *mls) +{ + semanage_handle_t *handle = NULL; + semanage_seuser_key_t *key = NULL; + int ret; + int seuser_exists = 0; + + if (seuser_name == NULL) { + /* don't care, just let system pick the defaults */ + return EOK; + } + + ret = sss_semanage_init(&handle); + if (ret == ERR_SELINUX_NOT_MANAGED) { + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create SELinux handle\n"); + goto done; + } + + ret = semanage_begin_transaction(handle); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot begin SELinux transaction\n"); + ret = EIO; + goto done; + } + + ret = semanage_seuser_key_create(handle, login_name, &key); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create SELinux user key\n"); + ret = EIO; + goto done; + } + + ret = semanage_seuser_exists(handle, key, &seuser_exists); + if (ret < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot verify the SELinux user\n"); + ret = EIO; + goto done; + } + + if (seuser_exists) { + ret = sss_semanage_user_mod(handle, key, login_name, seuser_name, + mls); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot modify SELinux user mapping\n"); + ret = EIO; + goto done; + } + } else { + ret = sss_semanage_user_add(handle, key, login_name, seuser_name, + mls); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot add SELinux user mapping\n"); + ret = EIO; + goto done; + } + } + + ret = semanage_commit(handle); + if (ret < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot commit SELinux transaction\n"); + ret = EIO; + goto done; + } + + ret = EOK; +done: + if (key != NULL) { + semanage_seuser_key_free(key); + } + sss_semanage_close(handle); + return ret; +} + +int sss_del_seuser(const char *login_name) +{ + semanage_handle_t *handle = NULL; + semanage_seuser_key_t *key = NULL; + int ret; + int exists = 0; + + ret = sss_semanage_init(&handle); + if (ret == ERR_SELINUX_NOT_MANAGED) { + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create SELinux handle\n"); + goto done; + } + + ret = semanage_begin_transaction(handle); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot begin SELinux transaction\n"); + ret = EIO; + goto done; + } + + ret = semanage_seuser_key_create(handle, login_name, &key); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create SELinux user key\n"); + ret = EIO; + goto done; + } + + ret = semanage_seuser_exists(handle, key, &exists); + if (ret < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot verify the SELinux user\n"); + ret = EIO; + goto done; + } + + if (!exists) { + DEBUG(SSSDBG_FUNC_DATA, + "Login mapping for %s is not defined, OK if default mapping " + "was used\n", login_name); + ret = EOK; /* probably default mapping */ + goto done; + } + + ret = semanage_seuser_exists_local(handle, key, &exists); + if (ret < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot verify the SELinux user\n"); + ret = EIO; + goto done; + } + + if (!exists) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Login mapping for %s is defined in policy, cannot be deleted\n", + login_name); + ret = ENOENT; + goto done; + } + + ret = semanage_seuser_del_local(handle, key); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not delete login mapping for %s\n", login_name); + ret = EIO; + goto done; + } + + ret = semanage_commit(handle); + if (ret < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot commit SELinux transaction\n"); + ret = EIO; + goto done; + } + + ret = EOK; +done: + sss_semanage_close(handle); + return ret; +} +#else /* HAVE_SEMANAGE && HAVE_SELINUX */ +int sss_set_seuser(const char *login_name, const char *seuser_name, + const char *mls) +{ + return EOK; +} + +int sss_del_seuser(const char *login_name) +{ + return EOK; +} + +int sss_get_seuser(const char *linuxuser, + char **selinuxuser, + char **level) +{ + return EOK; +} +#endif /* HAVE_SEMANAGE */ diff --git a/src/util/sss_sockets.c b/src/util/sss_sockets.c new file mode 100644 index 0000000..60312d5 --- /dev/null +++ b/src/util/sss_sockets.c @@ -0,0 +1,436 @@ +/* + SSSD + + Socket utils + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2016 + Copyright (C) Sumit Bose <sbose@redhat.com> 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> + +#include "util/util.h" + + +static errno_t set_fcntl_flags(int fd, int fd_flags, int fl_flags) +{ + int ret; + int cur_flags; + + ret = fcntl(fd, F_GETFD, 0); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fcntl F_GETFD failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + cur_flags = ret; + + ret = fcntl(fd, F_SETFD, cur_flags | fd_flags); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fcntl F_SETFD failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + ret = fcntl(fd, F_GETFL, 0); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fcntl F_GETFD failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + cur_flags = ret; + + ret = fcntl(fd, F_SETFL, cur_flags | fl_flags); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fcntl F_SETFD failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + return EOK; +} + +errno_t set_fd_common_opts(int fd, int timeout) +{ + int dummy = 1; + int ret; + struct timeval tv; + unsigned int milli; + int domain; + int type; + socklen_t optlen = sizeof(int); + + /* Get protocol domain. */ + ret = getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &domain, &optlen); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_FUNC_DATA, "Unable to get socket domain [%d]: %s.\n", + ret, strerror(ret)); + /* Assume IPV6. */ + domain = AF_INET6; + } + + /* Get protocol type. */ + ret = getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &optlen); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_FUNC_DATA, "Unable to get socket type [%d]: %s.\n", + ret, strerror(ret)); + /* Assume TCP. */ + type = SOCK_STREAM; + } + + /* SO_KEEPALIVE and TCP_NODELAY are set by OpenLDAP client libraries but + * failures are ignored.*/ + if (domain != AF_UNIX && type == SOCK_STREAM) { + + ret = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &dummy, sizeof(dummy)); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_FUNC_DATA, + "setsockopt SO_KEEPALIVE failed.[%d][%s].\n", ret, + strerror(ret)); + } + + ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &dummy, sizeof(dummy)); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_FUNC_DATA, + "setsockopt TCP_NODELAY failed.[%d][%s].\n", ret, + strerror(ret)); + } + } + + if (timeout > 0) { + /* Set socket read & write timeout */ + tv = tevent_timeval_set(timeout, 0); + + ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_FUNC_DATA, + "setsockopt SO_RCVTIMEO failed.[%d][%s].\n", ret, + strerror(ret)); + } + + ret = setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_FUNC_DATA, + "setsockopt SO_SNDTIMEO failed.[%d][%s].\n", ret, + strerror(ret)); + } + + if (domain != AF_UNIX && type == SOCK_STREAM) { + milli = timeout * 1000; /* timeout in milliseconds */ + ret = setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &milli, + sizeof(milli)); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_FUNC_DATA, + "setsockopt TCP_USER_TIMEOUT failed.[%d][%s].\n", ret, + strerror(ret)); + } + } + } + + return EOK; +} + + +struct sssd_async_connect_state { + struct tevent_fd *fde; + int fd; + socklen_t addr_len; + struct sockaddr_storage addr; +}; + +static void sssd_async_connect_done(struct tevent_context *ev, + struct tevent_fd *fde, uint16_t flags, + void *priv); + +struct tevent_req *sssd_async_connect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, + const struct sockaddr *addr, + socklen_t addr_len) +{ + struct tevent_req *req; + struct sssd_async_connect_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct sssd_async_connect_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->fd = fd; + state->addr_len = addr_len; + memcpy(&state->addr, addr, addr_len); + + ret = connect(fd, addr, addr_len); + if (ret == EOK) { + goto done; + } + + ret = errno; + switch (ret) { + case EINPROGRESS: + case EINTR: + + /* Despite the connect() man page says waiting on a non-blocking + * connect should be done by checking for writability, we need to check + * also for readability. + * With TEVENT_FD_READ, connect fails much faster in offline mode with + * errno 113/No route to host. + */ + state->fde = tevent_add_fd(ev, state, fd, + TEVENT_FD_READ | TEVENT_FD_WRITE, + sssd_async_connect_done, req); + if (state->fde == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_fd failed.\n"); + ret = ENOMEM; + goto done; + } + + return req; + + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "connect failed [%d][%s].\n", ret, strerror(ret)); + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + tevent_req_post(req, ev); + return req; +} + +static void sssd_async_connect_done(struct tevent_context *ev, + struct tevent_fd *fde, uint16_t flags, + void *priv) +{ + struct tevent_req *req = talloc_get_type(priv, struct tevent_req); + struct sssd_async_connect_state *state = + tevent_req_data(req, struct sssd_async_connect_state); + int ret; + + errno = 0; + ret = connect(state->fd, (struct sockaddr *) &state->addr, + state->addr_len); + if (ret == -1) { + ret = errno; + if (ret == EALREADY || ret == EINPROGRESS || ret == EINTR) { + return; /* Try again later */ + } + } + + talloc_zfree(fde); + + if (ret == EOK) { + tevent_req_done(req); + } else { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "connect failed [%d][%s].\n", ret, strerror(ret)); + tevent_req_error(req, ret); + } +} + +int sssd_async_connect_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + + +static void sssd_async_connect_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *connection_request; + + DEBUG(SSSDBG_CONF_SETTINGS, + "The connection timed out [ldap_network_timeout]\n"); + + connection_request = talloc_get_type(pvt, struct tevent_req); + tevent_req_error(connection_request, ETIMEDOUT); +} + + +struct sssd_async_socket_state { + struct tevent_timer *connect_timeout; + int sd; +}; + +static int sssd_async_socket_state_destructor(void *data); +static void sssd_async_socket_init_done(struct tevent_req *subreq); + +struct tevent_req *sssd_async_socket_init_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + bool use_udp, + struct sockaddr *addr, + socklen_t addr_len, int timeout) +{ + struct sssd_async_socket_state *state; + struct tevent_req *req, *subreq; + struct timeval tv; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sssd_async_socket_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + state->sd = -1; + + talloc_set_destructor((TALLOC_CTX *)state, + sssd_async_socket_state_destructor); + + state->sd = socket(addr->sa_family, use_udp ? SOCK_DGRAM : SOCK_STREAM, 0); + if (state->sd == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "socket failed [%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + ret = set_fd_common_opts(state->sd, timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "set_fd_common_opts failed.\n"); + goto fail; + } + + ret = set_fcntl_flags(state->sd, FD_CLOEXEC, O_NONBLOCK); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "setting fd flags failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Using file descriptor [%d] for the connection.\n", state->sd); + + subreq = sssd_async_connect_send(state, ev, state->sd, + (struct sockaddr *) addr, addr_len); + if (subreq == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "sssd_async_connect_send failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Setting %d seconds timeout [ldap_network_timeout] for connecting\n", + timeout); + tv = tevent_timeval_current_ofs(timeout, 0); + + state->connect_timeout = tevent_add_timer(ev, subreq, tv, + sssd_async_connect_timeout, + subreq); + if (state->connect_timeout == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, sssd_async_socket_init_done, req); + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void sssd_async_socket_init_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct sssd_async_socket_state *state = + tevent_req_data(req, struct sssd_async_socket_state); + int ret; + + /* kill the timeout handler now that we got a reply */ + talloc_zfree(state->connect_timeout); + + ret = sssd_async_connect_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + if (ret == ETIMEDOUT) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_async_sys_connect request failed: [%d]: %s " + "[ldap_network_timeout].\n", + ret, sss_strerror(ret)); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_async_sys_connect request failed: [%d]: %s.\n", + ret, sss_strerror(ret)); + } + goto fail; + } + + tevent_req_done(req); + return; + +fail: + tevent_req_error(req, ret); +} + +int sssd_async_socket_init_recv(struct tevent_req *req, int *sd) +{ + struct sssd_async_socket_state *state = + tevent_req_data(req, struct sssd_async_socket_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + /* steal the sd and neutralize destructor actions */ + *sd = state->sd; + state->sd = -1; + + return EOK; +} + +static int sssd_async_socket_state_destructor(void *data) +{ + struct sssd_async_socket_state *state = + talloc_get_type(data, struct sssd_async_socket_state); + + if (state->sd != -1) { + DEBUG(SSSDBG_TRACE_FUNC, "closing socket [%d]\n", state->sd); + close(state->sd); + state->sd = -1; + } + + return 0; +} diff --git a/src/util/sss_sockets.h b/src/util/sss_sockets.h new file mode 100644 index 0000000..1fe5f0a --- /dev/null +++ b/src/util/sss_sockets.h @@ -0,0 +1,42 @@ +/* + SSSD + + Socket utils + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __SSS_SOCKETS_H__ +#define __SSS_SOCKETS_H__ + +errno_t set_fd_common_opts(int fd, int timeout); + +struct tevent_req *sssd_async_connect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, + const struct sockaddr *addr, + socklen_t addr_len); +int sssd_async_connect_recv(struct tevent_req *req); + + +struct tevent_req *sssd_async_socket_init_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + bool use_udp, + struct sockaddr *addr, + socklen_t addr_len, int timeout); +int sssd_async_socket_init_recv(struct tevent_req *req, int *sd); + +#endif /* __SSS_SOCKETS_H__ */ diff --git a/src/util/sss_ssh.c b/src/util/sss_ssh.c new file mode 100644 index 0000000..8b60886 --- /dev/null +++ b/src/util/sss_ssh.c @@ -0,0 +1,271 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <arpa/inet.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "util/sss_ssh.h" + +errno_t +sss_ssh_make_ent(TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + struct sss_ssh_ent **result) +{ + TALLOC_CTX *tmp_ctx; + struct sss_ssh_ent *res = NULL; + errno_t ret; + const char *name; + struct ldb_message_element *el; + unsigned int i; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (!name) { + ret = EINVAL; + DEBUG(SSSDBG_CRIT_FAILURE, "Host is missing name attribute\n"); + goto done; + } + + res = talloc_zero(tmp_ctx, struct sss_ssh_ent); + if (!res) { + ret = ENOMEM; + goto done; + } + + res->name = talloc_strdup(res, name); + if (!res->name) { + ret = ENOMEM; + goto done; + } + + el = ldb_msg_find_element(msg, SYSDB_SSH_PUBKEY); + if (el) { + res->num_pubkeys = el->num_values; + + res->pubkeys = talloc_array(res, struct sss_ssh_pubkey, + res->num_pubkeys); + if (!res->pubkeys) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < el->num_values; i++) { + res->pubkeys[i].data = sss_base64_decode(res->pubkeys, + (char *)el->values[i].data, &res->pubkeys[i].data_len); + if (!res->pubkeys[i].data) { + ret = ENOMEM; + goto done; + } + } + } + + el = ldb_msg_find_element(msg, SYSDB_NAME_ALIAS); + if (el) { + res->num_aliases = el->num_values; + + res->aliases = talloc_array(res, char *, res->num_aliases); + if (!res->aliases) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < el->num_values; i++) { + res->aliases[i] = talloc_strdup(res->aliases, + (char *)el->values[i].data); + if (!res->aliases[i]) { + ret = ENOMEM; + goto done; + } + } + } + + *result = talloc_steal(mem_ctx, res); + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sss_ssh_get_pubkey_algorithm(TALLOC_CTX *mem_ctx, + struct sss_ssh_pubkey *pubkey, + char **result) +{ + size_t c = 0; + uint32_t algo_len; + char *algo; + + if (pubkey->data_len < 5) { + return EINVAL; + } + + SAFEALIGN_COPY_UINT32(&algo_len, pubkey->data, &c); + algo_len = ntohl(algo_len); + if (algo_len < 1 || algo_len > 64 || algo_len > pubkey->data_len - 4) { + /* the maximum length of 64 is defined in RFC 4250 */ + return EINVAL; + } + + algo = talloc_zero_array(mem_ctx, char, algo_len+1); + if (!algo) { + return ENOMEM; + } + + memcpy(algo, pubkey->data+c, algo_len); + + *result = algo; + return EOK; +} + +errno_t +sss_ssh_format_pubkey(TALLOC_CTX *mem_ctx, + struct sss_ssh_pubkey *pubkey, + char **result) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + char *blob; + char *algo; + char *out = NULL; + size_t i, len; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + if (pubkey->data_len > 4 && memcmp(pubkey->data, "\0\0\0", 3) == 0) { + /* All valid public key blobs start with 3 null bytes (see RFC 4253 + * section 6.6, RFC 4251 section 5 and RFC 4250 section 4.6) + */ + blob = sss_base64_encode(tmp_ctx, pubkey->data, pubkey->data_len); + if (!blob) { + ret = ENOMEM; + goto done; + } + + ret = sss_ssh_get_pubkey_algorithm(tmp_ctx, pubkey, &algo); + if (ret != EOK) { + goto done; + } + + out = talloc_asprintf(mem_ctx, "%s %s", algo, blob); + if (!out) { + ret = ENOMEM; + goto done; + } + } else { + /* Not a valid public key blob, so this must be a textual public key */ + for (i = 0; i < pubkey->data_len; i++) { + if (pubkey->data[i] == '\0' || + (pubkey->data[i] == '\n' && i != pubkey->data_len - 1) || + pubkey->data[i] == '\r') { + ret = EINVAL; + goto done; + } + } + + len = pubkey->data_len; + if (pubkey->data[len - 1] == '\n') { + len--; + } + + out = talloc_array(mem_ctx, char, len + 1); + if (out == NULL) { + ret = ENOMEM; + goto done; + } + + memcpy(out, pubkey->data, len); + out[len] = '\0'; + } + + *result = out; + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +sss_ssh_print_pubkey(struct sss_ssh_pubkey *pubkey) +{ + TALLOC_CTX *tmp_ctx; + char *repr = NULL; + char *repr_break = NULL; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sss_ssh_format_pubkey(tmp_ctx, pubkey, &repr); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_ssh_format_pubkey() failed (%d): %s\n", + ret, strerror(ret)); + sss_log(SSS_LOG_ERR, "SSH key is malformed: %s\n", strerror(ret)); + goto end; + } + + /* OpenSSH expects a linebreak after each key */ + repr_break = talloc_asprintf(tmp_ctx, "%s\n", repr); + talloc_zfree(repr); + if (repr_break == NULL) { + ret = ENOMEM; + goto end; + } + + ret = sss_atomic_write_s(STDOUT_FILENO, repr_break, strlen(repr_break)); + /* Avoid spiking memory with too many large keys */ + talloc_zfree(repr_break); + if (ret < 0) { + ret = errno; + if (ret == EPIPE) { + DEBUG(SSSDBG_MINOR_FAILURE, + "SSHD closed the pipe before all keys could be written\n"); + /* Return 0 so that openssh doesn't abort pubkey auth */ + ret = 0; + goto end; + } + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_atomic_write_s() failed (%d): %s\n", + ret, strerror(ret)); + goto end; + } + + ret = EOK; + + end: + talloc_zfree(tmp_ctx); + + return ret; +} diff --git a/src/util/sss_ssh.h b/src/util/sss_ssh.h new file mode 100644 index 0000000..d35ffb9 --- /dev/null +++ b/src/util/sss_ssh.h @@ -0,0 +1,56 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SSS_SSH_H_ +#define _SSS_SSH_H_ + +#define SSS_SSH_REQ_ALIAS 0x01 +#define SSS_SSH_REQ_DOMAIN 0x02 +#define SSS_SSH_REQ_MASK 0x03 + +struct sss_ssh_pubkey { + uint8_t *data; + size_t data_len; +}; + +struct sss_ssh_ent { + char *name; + + struct sss_ssh_pubkey *pubkeys; + size_t num_pubkeys; + + char **aliases; + size_t num_aliases; +}; + +errno_t +sss_ssh_make_ent(TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + struct sss_ssh_ent **result); + +errno_t +sss_ssh_format_pubkey(TALLOC_CTX *mem_ctx, + struct sss_ssh_pubkey *pubkey, + char **result); + +errno_t +sss_ssh_print_pubkey(struct sss_ssh_pubkey *pubkey); + +#endif /* _SSS_SSH_H_ */ diff --git a/src/util/sss_tc_utf8.c b/src/util/sss_tc_utf8.c new file mode 100644 index 0000000..75d5c71 --- /dev/null +++ b/src/util/sss_tc_utf8.c @@ -0,0 +1,82 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2011 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <stdlib.h> +#include <unistr.h> +#include <unicase.h> + +#include <talloc.h> +#include "util/util.h" + +/* Expects and returns NULL-terminated string; + * result must be freed with sss_utf8_free() + */ +static inline char *sss_utf8_tolower(const char *s) +{ + size_t llen; + return (char *)u8_tolower((const uint8_t *)s, strlen(s) + 1, NULL, NULL, NULL, &llen); +} + +char *sss_tc_utf8_str_tolower(TALLOC_CTX *mem_ctx, const char *s) +{ + char *lower; + char *ret = NULL; + + lower = sss_utf8_tolower(s); + if (lower) { + ret = talloc_strdup(mem_ctx, lower); + free(lower); + } + + return ret; +} + +errno_t sss_filter_sanitize_for_dom(TALLOC_CTX *mem_ctx, + const char *input, + struct sss_domain_info *dom, + char **sanitized, + char **lc_sanitized) +{ + int ret; + + ret = sss_filter_sanitize(mem_ctx, input, sanitized); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_filter_sanitize failed.\n"); + return ret; + } + + if (dom->case_sensitive) { + *lc_sanitized = talloc_strdup(mem_ctx, *sanitized); + } else { + *lc_sanitized = sss_tc_utf8_str_tolower(mem_ctx, *sanitized); + } + + if (*lc_sanitized == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "%s failed.\n", + dom->case_sensitive ? + "talloc_strdup" : + "sss_tc_utf8_str_tolower"); + return ENOMEM; + } + + return EOK; +} diff --git a/src/util/sss_time.c b/src/util/sss_time.c new file mode 100644 index 0000000..bd985ab --- /dev/null +++ b/src/util/sss_time.c @@ -0,0 +1,76 @@ +/* + SSSD - time utils + + Copyright (C) Sumit Bose <sbose@redhat.com> 2021 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" + +#define TV_TO_US(tv) ((tv).tv_sec * 1000000 + (tv).tv_usec) + +uint64_t get_start_time() +{ + struct timeval tv; + + if (gettimeofday(&tv, NULL) != 0) { + DEBUG(SSSDBG_OP_FAILURE, "gettimeofday failed.\n"); + return 0; + } + + return TV_TO_US(tv); +} + +const char *sss_format_time(uint64_t us) +{ + static char out[128]; + int ret; + + if (us == 0) { + return "[- unavailable -]"; + } + + ret = snprintf(out, sizeof(out), "[%.3f] milliseconds", (double) us/1000); + if (ret < 0 || ret >= sizeof(out)) { + return "[- formatting error -]"; + } + + return out; +} + +uint64_t get_spend_time_us(uint64_t st) +{ + struct timeval tv; + uint64_t time_now; + + if (st == 0) { + DEBUG(SSSDBG_OP_FAILURE, "Missing start time.\n"); + return 0; + } + + if (gettimeofday(&tv, NULL) != 0) { + DEBUG(SSSDBG_OP_FAILURE, "gettimeofday failed.\n"); + return 0; + } + + time_now = TV_TO_US(tv); + + if (st > time_now) { + DEBUG(SSSDBG_OP_FAILURE, "Start time in future.\n"); + return 0; + } + + return time_now - st; +} diff --git a/src/util/sss_utf8.c b/src/util/sss_utf8.c new file mode 100644 index 0000000..ac2c1ff --- /dev/null +++ b/src/util/sss_utf8.c @@ -0,0 +1,81 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <string.h> +#include <errno.h> + +#include <stdlib.h> +#include <unistr.h> +#include <unicase.h> + +#include "sss_utf8.h" + +bool sss_utf8_check(const uint8_t *s, size_t n) +{ + if (u8_check(s, n) == NULL) { + return true; + } + return false; +} + +errno_t sss_utf8_case_eq(const uint8_t *s1, const uint8_t *s2) +{ + + /* Do a case-insensitive comparison. + * The input must be encoded in UTF8. + * We have no way of knowing the language, + * so we'll pass NULL for the language and + * hope for the best. + */ + int ret; + int resultp; + size_t n1, n2; + errno = 0; + + n1 = u8_strlen(s1); + n2 = u8_strlen(s2); + + ret = u8_casecmp(s1, n1, + s2, n2, + NULL, NULL, + &resultp); + if (ret < 0) { + /* An error occurred */ + return errno; + } + + if (resultp == 0) { + return EOK; + } + return ENOMATCH; +} + +bool sss_string_equal(bool cs, const char *s1, const char *s2) +{ + if (cs) { + return strcmp(s1, s2) == 0; + } + + return sss_utf8_case_eq((const uint8_t *)s1, (const uint8_t *)s2) == EOK; +} diff --git a/src/util/sss_utf8.h b/src/util/sss_utf8.h new file mode 100644 index 0000000..2769f95 --- /dev/null +++ b/src/util/sss_utf8.h @@ -0,0 +1,44 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef SSS_UTF8_H_ +#define SSS_UTF8_H_ + +#ifndef ENOMATCH +#define ENOMATCH -1 +#endif + +#include <stdint.h> +#include <stdbool.h> + +#include "util/util_errors.h" + +bool sss_utf8_check(const uint8_t *s, size_t n); + +/* Returns EOK on match, ENOTUNIQ if comparison succeeds but + * does not match. + * May return other errno error codes on failure + */ +errno_t sss_utf8_case_eq(const uint8_t *s1, const uint8_t *s2); + + +#endif /* SSS_UTF8_H_ */ diff --git a/src/util/string_utils.c b/src/util/string_utils.c new file mode 100644 index 0000000..78966e3 --- /dev/null +++ b/src/util/string_utils.c @@ -0,0 +1,252 @@ +/* + SSSD + + Authors: + Lukas Slebodnik <slebodnikl@redhat.com> + + Copyright (C) 2014 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" + +char *sss_replace_char(TALLOC_CTX *mem_ctx, + const char *in, + const char match, + const char sub) +{ + char *p; + char *out; + + out = talloc_strdup(mem_ctx, in); + if (out == NULL) { + return NULL; + } + + for (p = out; *p != '\0'; ++p) { + if (*p == match) { + *p = sub; + } + } + + return out; +} + +char * sss_replace_space(TALLOC_CTX *mem_ctx, + const char *orig_name, + const char subst) +{ + if (subst == '\0' || subst == ' ') { + return talloc_strdup(mem_ctx, orig_name); + } + + if (strchr(orig_name, subst) != NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Input [%s] already contains replacement character [%c].\n", + orig_name, subst); + sss_log(SSS_LOG_CRIT, + "Name [%s] already contains replacement character [%c]. " \ + "No replacement will be done.\n", + orig_name, subst); + return talloc_strdup(mem_ctx, orig_name); + } + + return sss_replace_char(mem_ctx, orig_name, ' ', subst); +} + +char * sss_reverse_replace_space(TALLOC_CTX *mem_ctx, + const char *orig_name, + const char subst) +{ + if (subst == '\0' || subst == ' ') { + return talloc_strdup(mem_ctx, orig_name); + } + + if (strchr(orig_name, subst) != NULL && strchr(orig_name, ' ') != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "Input [%s] contains replacement character [%c] and space.\n", + orig_name, subst); + return talloc_strdup(mem_ctx, orig_name); + } + + return sss_replace_char(mem_ctx, orig_name, subst, ' '); +} + +errno_t guid_blob_to_string_buf(const uint8_t *blob, char *str_buf, + size_t buf_size) +{ + int ret; + + if (blob == NULL || str_buf == NULL || buf_size < GUID_STR_BUF_SIZE) { + DEBUG(SSSDBG_OP_FAILURE, "Buffer too small.\n"); + return EINVAL; + } + + ret = snprintf(str_buf, buf_size, + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + blob[3], blob[2], blob[1], blob[0], + blob[5], blob[4], + blob[7], blob[6], + blob[8], blob[9], + blob[10], blob[11],blob[12], blob[13],blob[14], blob[15]); + if (ret != (GUID_STR_BUF_SIZE -1)) { + DEBUG(SSSDBG_CRIT_FAILURE, "snprintf failed.\n"); + return EIO; + } + + return EOK; +} + +const char *get_last_x_chars(const char *str, size_t x) +{ + size_t len; + + if (str == NULL) { + return NULL; + } + + len = strlen(str); + + if (len < x) { + return str; + } + + return (str + len - x); +} + +char **concatenate_string_array(TALLOC_CTX *mem_ctx, + char **arr1, size_t len1, + char **arr2, size_t len2) +{ + size_t i, j; + size_t new_size = len1 + len2; + char ** string_array = talloc_realloc(mem_ctx, arr1, char *, new_size + 1); + if (string_array == NULL) { + return NULL; + } + + for (i=len1, j=0; i < new_size; ++i,++j) { + string_array[i] = talloc_steal(string_array, + arr2[j]); + } + + string_array[i] = NULL; + + return string_array; +} + +errno_t mod_defaults_list(TALLOC_CTX *mem_ctx, const char **defaults_list, + char **mod_list, char ***_list) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + size_t mod_list_size; + const char **add_list; + const char **remove_list; + size_t c; + size_t ai = 0; + size_t ri = 0; + size_t j = 0; + char **list; + size_t expected_list_size = 0; + size_t defaults_list_size = 0; + + for (defaults_list_size = 0; + defaults_list != NULL && defaults_list[defaults_list_size] != NULL; + defaults_list_size++); + + for (mod_list_size = 0; + mod_list != NULL && mod_list[mod_list_size] != NULL; + mod_list_size++); + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + add_list = talloc_zero_array(tmp_ctx, const char *, mod_list_size + 1); + remove_list = talloc_zero_array(tmp_ctx, const char *, mod_list_size + 1); + + if (add_list == NULL || remove_list == NULL) { + ret = ENOMEM; + goto done; + } + + for (c = 0; mod_list != NULL && mod_list[c] != NULL; c++) { + switch (mod_list[c][0]) { + case '+': + add_list[ai] = mod_list[c] + 1; + ++ai; + break; + case '-': + remove_list[ri] = mod_list[c] + 1; + ++ri; + break; + default: + DEBUG(SSSDBG_OP_FAILURE, + "The option "CONFDB_PAM_P11_ALLOWED_SERVICES" must start" + "with either '+' (for adding service) or '-' (for " + "removing service) got '%s'\n", mod_list[c]); + ret = EINVAL; + goto done; + } + } + + expected_list_size = defaults_list_size + ai + 1; + + list = talloc_zero_array(tmp_ctx, char *, expected_list_size); + if (list == NULL) { + ret = ENOMEM; + goto done; + } + + for (c = 0; add_list[c] != NULL; ++c) { + if (string_in_list(add_list[c], discard_const(remove_list), false)) { + continue; + } + + list[j] = talloc_strdup(list, add_list[c]); + if (list[j] == NULL) { + ret = ENOMEM; + goto done; + } + j++; + } + + for (c = 0; defaults_list != NULL && defaults_list[c] != NULL; ++c) { + if (string_in_list(defaults_list[c], + discard_const(remove_list), false)) { + continue; + } + + list[j] = talloc_strdup(list, defaults_list[c]); + if (list[j] == NULL) { + ret = ENOMEM; + goto done; + } + j++; + } + + if (_list != NULL) { + *_list = talloc_steal(mem_ctx, list); + } + + ret = EOK; + +done: + talloc_zfree(tmp_ctx); + + return ret; +} diff --git a/src/util/strtonum.c b/src/util/strtonum.c new file mode 100644 index 0000000..8eda8ea --- /dev/null +++ b/src/util/strtonum.c @@ -0,0 +1,77 @@ +/* + SSSD + + SSSD Utility functions + + Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdlib.h> +#include <errno.h> +#include "util/strtonum.h" + +int32_t strtoint32(const char *nptr, char **endptr, int base) +{ + long long ret = 0; + + errno = 0; + ret = strtoll(nptr, endptr, base); + + if (ret > INT32_MAX) { + errno = ERANGE; + return INT32_MAX; + } + else if (ret < INT32_MIN) { + errno = ERANGE; + return INT32_MIN; + } + + /* If errno was set by strtoll, we'll pass it back as-is */ + return (int32_t)ret; +} + + +uint32_t strtouint32(const char *nptr, char **endptr, int base) +{ + unsigned long long ret = 0; + errno = 0; + ret = strtoull(nptr, endptr, base); + + if (ret > UINT32_MAX) { + errno = ERANGE; + return UINT32_MAX; + } + + /* If errno was set by strtoll, we'll pass it back as-is */ + return (uint32_t)ret; +} + + +uint16_t strtouint16(const char *nptr, char **endptr, int base) +{ + unsigned long long ret = 0; + errno = 0; + ret = strtoull(nptr, endptr, base); + + if (ret > UINT16_MAX) { + errno = ERANGE; + return UINT16_MAX; + } + + /* If errno was set by strtoll, we'll pass it back as-is */ + return (uint16_t)ret; +} + diff --git a/src/util/strtonum.h b/src/util/strtonum.h new file mode 100644 index 0000000..ae493b5 --- /dev/null +++ b/src/util/strtonum.h @@ -0,0 +1,32 @@ +/* + SSSD + + SSSD Utility functions + + Copyright (C) Stephen Gallagher 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _STRTONUM_H_ +#define _STRTONUM_H_ + +#include <stdint.h> + +int32_t strtoint32(const char *nptr, char **endptr, int base); +uint32_t strtouint32(const char *nptr, char **endptr, int base); + +uint16_t strtouint16(const char *nptr, char **endptr, int base); + +#endif /* _STRTONUM_H_ */ diff --git a/src/util/user_info_msg.c b/src/util/user_info_msg.c new file mode 100644 index 0000000..1399544 --- /dev/null +++ b/src/util/user_info_msg.c @@ -0,0 +1,57 @@ +/* + SSSD + + Pack user info messages + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "util/user_info_msg.h" +#include "sss_client/sss_cli.h" + +errno_t pack_user_info_chpass_error(TALLOC_CTX *mem_ctx, + const char *user_error_message, + size_t *resp_len, + uint8_t **_resp) +{ + uint32_t resp_type = SSS_PAM_USER_INFO_CHPASS_ERROR; + size_t err_len; + uint8_t *resp; + size_t p; + + err_len = strlen(user_error_message); + *resp_len = 2 * sizeof(uint32_t) + err_len; + resp = talloc_size(mem_ctx, *resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + p = 0; + SAFEALIGN_SET_UINT32(&resp[p], resp_type, &p); + SAFEALIGN_SET_UINT32(&resp[p], err_len, &p); + safealign_memcpy(&resp[p], user_error_message, err_len, &p); + if (p != *resp_len) { + DEBUG(SSSDBG_FATAL_FAILURE, "Size mismatch\n"); + } + + *_resp = resp; + return EOK; +} diff --git a/src/util/user_info_msg.h b/src/util/user_info_msg.h new file mode 100644 index 0000000..c68d538 --- /dev/null +++ b/src/util/user_info_msg.h @@ -0,0 +1,33 @@ +/* + SSSD + + Pack user info messages + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef __USER_INFO_MSG_H__ +#define __USER_INFO_MSG_H__ + + +errno_t pack_user_info_chpass_error(TALLOC_CTX *mem_ctx, + const char *user_error_message, + size_t *len, + uint8_t **_resp); + +#endif /* __USER_INFO_MSG_H__ */ diff --git a/src/util/usertools.c b/src/util/usertools.c new file mode 100644 index 0000000..8084760 --- /dev/null +++ b/src/util/usertools.c @@ -0,0 +1,891 @@ +/* + SSSD + + User tools + + Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <pwd.h> +#include <errno.h> +#include <ctype.h> +#include <talloc.h> +#include <grp.h> + +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "util/strtonum.h" +#include "util/util.h" +#include "util/safe-format-string.h" +#include "responder/common/responder.h" + +#define NAME_DOMAIN_PATTERN_OPTIONS (SSS_REGEXP_DUPNAMES | SSS_REGEXP_EXTENDED) + +/* Function returns given realm name as new uppercase string */ +char *get_uppercase_realm(TALLOC_CTX *memctx, const char *name) +{ + char *realm; + char *c; + + realm = talloc_strdup(memctx, name); + if (!realm) { + return NULL; + } + + c = realm; + while(*c != '\0') { + *c = toupper(*c); + c++; + } + + return realm; +} + +static errno_t get_id_provider_default_re(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *conf_path, + char **re_pattern) +{ + int ret; + size_t c; + char *id_provider = NULL; + + struct provider_default_re { + const char *name; + const char *re; + } provider_default_re[] = {{"ipa", SSS_IPA_AD_DEFAULT_RE}, + {"ad", SSS_IPA_AD_DEFAULT_RE}, + {NULL, NULL}}; + + ret = confdb_get_string(cdb, mem_ctx, conf_path, CONFDB_DOMAIN_ID_PROVIDER, + NULL, &id_provider); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to read ID provider " \ + "from conf db.\n"); + goto done; + } + + if (id_provider == NULL) { + *re_pattern = NULL; + } else { + for (c = 0; provider_default_re[c].name != NULL; c++) { + if (strcmp(id_provider, provider_default_re[c].name) == 0) { + *re_pattern = talloc_strdup(mem_ctx, provider_default_re[c].re); + if (*re_pattern == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + break; + } + } + } + + ret = EOK; + +done: + talloc_free(id_provider); + return ret; +} + +static errno_t sss_fqnames_init(struct sss_names_ctx *nctx, const char *fq_fmt) +{ + char *fq; + + nctx->fq_fmt = talloc_strdup(nctx, fq_fmt); + if (nctx->fq_fmt == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Using fq format [%s].\n", nctx->fq_fmt); + + /* Fail if the name specifier is missing, or if the format is + * invalid */ + fq = sss_tc_fqname2 (nctx, nctx, "unused.example.com", "unused", "the-test-user"); + if (fq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "The fq format is invalid [%s]\n", nctx->fq_fmt); + return EINVAL; + } else if (strstr (fq, "the-test-user") == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Username pattern not found in [%s]\n", nctx->fq_fmt); + return ENOENT; + } + + talloc_free (fq); + return EOK; +} + +int sss_names_init_from_args(TALLOC_CTX *mem_ctx, const char *re_pattern, + const char *fq_fmt, struct sss_names_ctx **out) +{ + struct sss_names_ctx *ctx; + int errval; + int ret; + + ctx = talloc_zero(mem_ctx, struct sss_names_ctx); + if (!ctx) return ENOMEM; + + ctx->re_pattern = talloc_strdup(ctx, re_pattern); + if (ctx->re_pattern == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Using re [%s].\n", ctx->re_pattern); + + ret = sss_fqnames_init(ctx, fq_fmt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not check the FQ names format" + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + errval = sss_regexp_new(ctx, + ctx->re_pattern, + NAME_DOMAIN_PATTERN_OPTIONS, + &(ctx->re)); + if (errval != 0) { + ret = EFAULT; + goto done; + } + + *out = ctx; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(ctx); + } + return ret; +} + +int sss_names_init(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb, + const char *domain, struct sss_names_ctx **out) +{ + TALLOC_CTX *tmpctx = NULL; + char *conf_path = NULL; + char *re_pattern = NULL; + char *fq_fmt = NULL; + int ret; + + tmpctx = talloc_new(NULL); + if (tmpctx == NULL) { + ret = ENOMEM; + goto done; + } + + if (domain != NULL) { + conf_path = talloc_asprintf(tmpctx, CONFDB_DOMAIN_PATH_TMPL, domain); + if (conf_path == NULL) { + ret = ENOMEM; + goto done; + } + + ret = confdb_get_string(cdb, tmpctx, conf_path, + CONFDB_NAME_REGEX, NULL, &re_pattern); + if (ret != EOK) goto done; + } + + /* If not found in the domain, look in globals */ + if (re_pattern == NULL) { + ret = confdb_get_string(cdb, tmpctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_NAME_REGEX, NULL, &re_pattern); + if (ret != EOK) goto done; + } + + if (re_pattern == NULL && conf_path != NULL) { + ret = get_id_provider_default_re(tmpctx, cdb, conf_path, &re_pattern); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get provider default regular " \ + "expression for domain [%s].\n", domain); + goto done; + } + } + + if (!re_pattern) { + re_pattern = talloc_strdup(tmpctx, SSS_DEFAULT_RE); + if (!re_pattern) { + ret = ENOMEM; + goto done; + } + } + + if (conf_path != NULL) { + ret = confdb_get_string(cdb, tmpctx, conf_path, + CONFDB_FULL_NAME_FORMAT, NULL, &fq_fmt); + if (ret != EOK) goto done; + } + + /* If not found in the domain, look in globals */ + if (fq_fmt == NULL) { + ret = confdb_get_string(cdb, tmpctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_FULL_NAME_FORMAT, NULL, &fq_fmt); + if (ret != EOK) goto done; + } + + if (!fq_fmt) { + fq_fmt = talloc_strdup(tmpctx, CONFDB_DEFAULT_FULL_NAME_FORMAT); + if (!fq_fmt) { + ret = ENOMEM; + goto done; + } + } + + ret = sss_names_init_from_args(mem_ctx, re_pattern, fq_fmt, out); + +done: + talloc_free(tmpctx); + return ret; +} + +int sss_ad_default_names_ctx(TALLOC_CTX *mem_ctx, + struct sss_names_ctx **_out) +{ + return sss_names_init_from_args(mem_ctx, SSS_IPA_AD_DEFAULT_RE, + CONFDB_DEFAULT_FULL_NAME_FORMAT, + _out); +} + +int sss_parse_name(TALLOC_CTX *memctx, + struct sss_names_ctx *snctx, + const char *orig, char **_domain, char **_name) +{ + sss_regexp_t *re = snctx->re; + const char *result; + int ret; + + ret = sss_regexp_match(re, orig, 0, SSS_REGEXP_NOTEMPTY); + if (ret == SSS_REGEXP_ERROR_NOMATCH) { + return ERR_REGEX_NOMATCH; + } else if (ret < 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "PCRE Matching error, %d\n", ret); + return EINVAL; + } + + if (ret == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Too many matches, the pattern is invalid.\n"); + } + + if (_name != NULL) { + result = NULL; + ret = sss_regexp_get_named_substring(re, "name", &result); + if (ret < 0 || !result) { + DEBUG(SSSDBG_OP_FAILURE, "Name not found!\n"); + return EINVAL; + } + *_name = talloc_strdup(memctx, result); + if (!*_name) return ENOMEM; + } + + if (_domain != NULL) { + result = NULL; + ret = sss_regexp_get_named_substring(re, "domain", &result); + if (ret < 0 || !result) { + DEBUG(SSSDBG_FUNC_DATA, "Domain not provided!\n"); + *_domain = NULL; + } else { + /* ignore "" string */ + if (*result) { + *_domain = talloc_strdup(memctx, result); + if (!*_domain) return ENOMEM; + } else { + *_domain = NULL; + } + } + } + + return EOK; +} + +static struct sss_domain_info * match_any_domain_or_subdomain_name( + struct sss_domain_info *dom, + const char *dmatch) +{ + if (strcasecmp(dom->name, dmatch) == 0 || + (dom->flat_name != NULL && strcasecmp(dom->flat_name, dmatch) == 0)) { + return dom; + } + + return find_domain_by_name_ex(dom, dmatch, true, SSS_GND_SUBDOMAINS); +} + +int sss_parse_name_for_domains(TALLOC_CTX *memctx, + struct sss_domain_info *domains, + const char *default_domain, + const char *orig, char **domain, char **name) +{ + struct sss_domain_info *dom, *match = NULL; + char *rdomain, *rname; + char *dmatch, *nmatch; + char *candidate_name = NULL; + char *candidate_domain = NULL; + bool name_mismatch = false; + TALLOC_CTX *tmp_ctx; + int ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + rname = NULL; + rdomain = NULL; + + for (dom = domains; dom != NULL; dom = get_next_domain(dom, 0)) { + ret = sss_parse_name(tmp_ctx, dom->names, orig, &dmatch, &nmatch); + if (ret == EOK) { + /* + * If the name matched without the domain part, make note of it. + * All the other domain expressions must agree on the domain-less + * name. + */ + if (dmatch == NULL) { + if (candidate_name == NULL) { + candidate_name = nmatch; + } else if (strcasecmp(candidate_name, nmatch) != 0) { + name_mismatch = true; + } + + /* + * If a domain was returned, then it must match the name of the + * domain that this expression was found on, or one of the + * subdomains. + */ + } else { + match = match_any_domain_or_subdomain_name (dom, dmatch); + if (match != NULL) { + DEBUG(SSSDBG_FUNC_DATA, "name '%s' matched expression for " + "domain '%s', user is %s\n", + orig, match->name, nmatch); + rdomain = talloc_strdup(tmp_ctx, match->name); + if (rdomain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + rname = nmatch; + break; + } else if (candidate_domain == NULL) { + candidate_domain = dmatch; + } + } + + /* EINVAL is returned when name doesn't match */ + } else if (ret != EINVAL) { + goto done; + } + } + + if (rdomain == NULL && rname == NULL) { + if (candidate_name && !name_mismatch) { + DEBUG(SSSDBG_FUNC_DATA, "name '%s' matched without domain, " \ + "user is %s\n", orig, nmatch); + rdomain = NULL; + if (default_domain != NULL) { + rdomain = talloc_strdup(tmp_ctx, default_domain); + if (rdomain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + for (dom = domains; dom != NULL; dom = get_next_domain(dom, 0)) { + match = match_any_domain_or_subdomain_name(dom, rdomain); + if (match != NULL) { + break; + } + } + if (match == NULL) { + DEBUG(SSSDBG_FUNC_DATA, "default domain [%s] is currently " \ + "not known\n", rdomain); + *domain = talloc_steal(memctx, rdomain); + ret = EAGAIN; + goto done; + } + DEBUG(SSSDBG_FUNC_DATA, "using default domain [%s]\n", rdomain); + } + + rname = candidate_name; + } else if (candidate_domain) { + /* This branch is taken when the input matches the configured + * regular expression, but the domain is now known. Normally, this + * is the case with a FQDN of a user from subdomain that was not + * yet discovered + */ + *domain = talloc_steal(memctx, candidate_domain); + ret = EAGAIN; + goto done; + } + } + + if (rdomain == NULL && rname == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "name '%s' did not match any domain's expression\n", orig); + ret = EINVAL; + goto done; + } + + if (domain != NULL) { + *domain = talloc_steal(memctx, rdomain); + } + + if (name != NULL) { + *name = talloc_steal(memctx, rname); + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + +char * +sss_get_cased_name(TALLOC_CTX *mem_ctx, + const char *orig_name, + bool case_sensitive) +{ + return case_sensitive ? talloc_strdup(mem_ctx, orig_name) : + sss_tc_utf8_str_tolower(mem_ctx, orig_name); +} + +errno_t +sss_get_cased_name_list(TALLOC_CTX *mem_ctx, const char * const *orig, + bool case_sensitive, const char ***_cased) +{ + const char **out; + size_t num, i; + + if (orig == NULL) { + *_cased = NULL; + return EOK; + } + + for (num=0; orig[num]; num++); /* count the num of strings */ + + if (num == 0) { + *_cased = NULL; + return EOK; + } + + out = talloc_array(mem_ctx, const char *, num + 1); + if (out == NULL) { + return ENOMEM; + } + + for (i = 0; i < num; i++) { + out[i] = sss_get_cased_name(out, orig[i], case_sensitive); + if (out[i] == NULL) { + talloc_free(out); + return ENOMEM; + } + } + + out[num] = NULL; + *_cased = out; + return EOK; +} + +static inline const char * +calc_flat_name(struct sss_domain_info *domain) +{ + const char *s; + + s = domain->flat_name; + if (s == NULL) { + DEBUG(SSSDBG_FUNC_DATA, "Domain has no flat name set," + "using domain name instead\n"); + s = domain->name; + } + + return s; +} + +char * +sss_tc_fqname(TALLOC_CTX *mem_ctx, struct sss_names_ctx *nctx, + struct sss_domain_info *domain, const char *name) +{ + if (domain == NULL || nctx == NULL) return NULL; + + return sss_tc_fqname2 (mem_ctx, nctx, domain->name, + calc_flat_name (domain), name); +} + +static void +safe_talloc_callback (void *data, + const char *piece, + size_t len) +{ + char **output = data; + if (*output != NULL) + *output = talloc_strndup_append(*output, piece, len); +} + +char * +sss_tc_fqname2(TALLOC_CTX *mem_ctx, struct sss_names_ctx *nctx, + const char *domain_name, const char *flat_dom_name, + const char *name) +{ + const char *args[] = { name, domain_name, flat_dom_name, NULL }; + char *output; + + if (nctx == NULL) return NULL; + + output = talloc_strdup(mem_ctx, ""); + if (safe_format_string_cb(safe_talloc_callback, &output, nctx->fq_fmt, args, 3) < 0) + output = NULL; + else if (output == NULL) + errno = ENOMEM; + return output; +} + +int +sss_fqname(char *str, size_t size, struct sss_names_ctx *nctx, + struct sss_domain_info *domain, const char *name) +{ + if (domain == NULL || nctx == NULL) return -EINVAL; + + return safe_format_string(str, size, nctx->fq_fmt, + name, domain->name, calc_flat_name (domain), NULL); +} + +errno_t sss_user_by_name_or_uid(const char *input, uid_t *_uid, gid_t *_gid) +{ + uid_t uid; + errno_t ret; + char *endptr; + struct passwd *pwd; + + /* Try if it's an ID first */ + uid = strtouint32(input, &endptr, 10); + if ((errno != 0) || (*endptr != '\0') || (input == endptr)) { + ret = errno; + if (ret == ERANGE) { + DEBUG(SSSDBG_OP_FAILURE, + "UID [%s] is out of range.\n", input); + return ret; + } + + /* Nope, maybe a username? */ + pwd = getpwnam(input); + } else { + pwd = getpwuid(uid); + } + + if (pwd == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "[%s] is neither a valid UID nor a user name which could be " + "resolved by getpwnam().\n", input); + return EINVAL; + } + + if (_uid) { + *_uid = pwd->pw_uid; + } + + if (_gid) { + *_gid = pwd->pw_gid; + } + return EOK; +} + +/* Accepts fqname in the format shortname@domname only. */ +errno_t sss_parse_internal_fqname(TALLOC_CTX *mem_ctx, + const char *fqname, + char **_shortname, + char **_dom_name) +{ + errno_t ret; + char *separator; + char *shortname = NULL; + char *dom_name = NULL; + size_t shortname_len; + TALLOC_CTX *tmp_ctx; + + if (fqname == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + separator = strrchr(fqname, '@'); + if (separator == NULL || *(separator + 1) == '\0' || separator == fqname) { + /*The name does not contain name or domain component. */ + ret = ERR_WRONG_NAME_FORMAT; + goto done; + } + + if (_dom_name != NULL) { + dom_name = talloc_strdup(tmp_ctx, separator + 1); + if (dom_name == NULL) { + ret = ENOMEM; + goto done; + } + + *_dom_name = talloc_steal(mem_ctx, dom_name); + } + + if (_shortname != NULL) { + shortname_len = strlen(fqname) - strlen(separator); + shortname = talloc_strndup(tmp_ctx, fqname, shortname_len); + if (shortname == NULL) { + ret = ENOMEM; + goto done; + } + + *_shortname = talloc_steal(mem_ctx, shortname); + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +/* Creates internal fqname in format shortname@domname. + * The domain portion is lowercased. */ +char *sss_create_internal_fqname(TALLOC_CTX *mem_ctx, + const char *shortname, + const char *dom_name) +{ + char *lc_dom_name; + char *fqname = NULL; + + if (shortname == NULL || dom_name == NULL) { + /* Avoid allocating null@null */ + return NULL; + } + + lc_dom_name = sss_tc_utf8_str_tolower(mem_ctx, dom_name); + if (lc_dom_name == NULL) { + goto done; + } + + fqname = talloc_asprintf(mem_ctx, "%s@%s", shortname, lc_dom_name); + talloc_free(lc_dom_name); +done: + return fqname; +} + +/* Creates a list of internal fqnames in format shortname@domname. + * The domain portion is lowercased. */ +char **sss_create_internal_fqname_list(TALLOC_CTX *mem_ctx, + const char * const *shortname_list, + const char *dom_name) +{ + char **fqname_list = NULL; + size_t c; + + if (shortname_list == NULL || dom_name == NULL) { + /* Avoid allocating null@null */ + return NULL; + } + + for (c = 0; shortname_list[c] != NULL; c++); + fqname_list = talloc_zero_array(mem_ctx, char *, c+1); + if (fqname_list == NULL) { + talloc_free(fqname_list); + return NULL; + } + + for (size_t i = 0; shortname_list[i] != NULL; i++) { + fqname_list[i] = sss_create_internal_fqname(fqname_list, + shortname_list[i], + dom_name); + if (fqname_list[i] == NULL) { + talloc_free(fqname_list); + return NULL; + } + } + + return fqname_list; +} + +char *sss_output_name(TALLOC_CTX *mem_ctx, + const char *name, + bool case_sensitive, + const char replace_space) +{ + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + char *shortname; + char *outname = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return NULL; + + ret = sss_parse_internal_fqname(tmp_ctx, name, &shortname, NULL); + if (ret == ERR_WRONG_NAME_FORMAT) { + /* There is no domain name. */ + shortname = talloc_strdup(tmp_ctx, name); + if (shortname == NULL) { + goto done; + } + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_parse_internal_fqname failed\n"); + goto done; + } + + outname = sss_get_cased_name(tmp_ctx, shortname, case_sensitive); + if (outname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_get_cased_name failed, skipping\n"); + goto done; + } + + outname = sss_replace_space(tmp_ctx, outname, replace_space); + if (outname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_replace_space failed\n"); + goto done; + } + + outname = talloc_steal(mem_ctx, outname); +done: + talloc_free(tmp_ctx); + return outname; +} + +const char * +sss_get_name_from_msg(struct sss_domain_info *domain, + struct ldb_message *msg) +{ + const char *name; + + /* If domain has a view associated we return overridden name + * if possible. */ + if (DOM_HAS_VIEWS(domain)) { + name = ldb_msg_find_attr_as_string(msg, OVERRIDE_PREFIX SYSDB_NAME, + NULL); + if (name != NULL) { + return name; + } + } + + /* Otherwise we try to return name override from + * Default Truest View for trusted users. */ + name = ldb_msg_find_attr_as_string(msg, SYSDB_DEFAULT_OVERRIDE_NAME, NULL); + if (name != NULL) { + return name; + } + + /* If no override is found we return the original name. */ + return ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); +} + +int sss_output_fqname(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *name, + char override_space, + char **_output_name) +{ + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + char *output_name; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + output_name = sss_output_name(tmp_ctx, name, domain->case_preserve, + override_space); + if (output_name == NULL) { + ret = EIO; + goto done; + } + + if (sss_domain_info_get_output_fqnames(domain) || domain->fqnames) { + output_name = sss_tc_fqname(tmp_ctx, domain->names, + domain, output_name); + if (output_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_tc_fqname failed\n"); + ret = EIO; + goto done; + } + } + + *_output_name = talloc_steal(mem_ctx, output_name); + ret = EOK; +done: + talloc_zfree(tmp_ctx); + return ret; +} + +void sss_sssd_user_uid_and_gid(uid_t *_uid, gid_t *_gid) +{ + uid_t sssd_uid; + gid_t sssd_gid; + errno_t ret; + + ret = sss_user_by_name_or_uid(SSSD_USER, &sssd_uid, &sssd_gid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "failed to get sssd user (" SSSD_USER ") uid/gid, using root\n"); + sssd_uid = 0; + sssd_gid = 0; + } + + if (_uid != NULL) { + *_uid = sssd_uid; + } + + if (_gid != NULL) { + *_gid = sssd_gid; + } +} + +void sss_set_sssd_user_eid(void) +{ + uid_t uid; + gid_t gid; + + + if (geteuid() == 0) { + sss_sssd_user_uid_and_gid(&uid, &gid); + + if (setegid(gid) != EOK) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Failed to set egid to %"SPRIgid": %s\n", + gid, sss_strerror(errno)); + } + if (seteuid(uid) != EOK) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Failed to set euid to %"SPRIuid": %s\n", + uid, sss_strerror(errno)); + } + } +} + +void sss_restore_sssd_user_eid(void) +{ + if (getuid() == 0) { + if (seteuid(getuid()) != EOK) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Failed to restore euid: %s\n", + sss_strerror(errno)); + } + if (setegid(getgid()) != EOK) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Failed to restore egid: %s\n", + sss_strerror(errno)); + } + } +} diff --git a/src/util/util.c b/src/util/util.c new file mode 100644 index 0000000..6546b60 --- /dev/null +++ b/src/util/util.c @@ -0,0 +1,1103 @@ +/* + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" +#include <ctype.h> +#include <netdb.h> +#include <poll.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <fcntl.h> +#include <talloc.h> +#include <dhash.h> +#include <time.h> + +#include "util/util.h" +#include "util/sss_utf8.h" + +int socket_activated = 0; +int dbus_activated = 0; + +static void free_args(char **args) +{ + int i; + + if (args) { + for (i = 0; args[i]; i++) free(args[i]); + free(args); + } +} + +/* parse a string into arguments. + * arguments are separated by a space + * '\' is an escape character and can be used only to escape + * itself or the white space. + */ +char **parse_args(const char *str) +{ + const char *p; + char **ret, **r; + char *tmp; + int num; + int i; + bool e, w; + + tmp = malloc(strlen(str) + 1); + if (!tmp) return NULL; + + ret = NULL; + num = 0; + i = 0; + e = false; + /* skip leading whitespaces */ + w = true; + p = str; + while (*p) { + if (*p == '\\') { + w = false; + if (e) { + /* if we were already escaping, add a '\' literal */ + tmp[i] = '\\'; + i++; + e = false; + } else { + /* otherwise just start escaping */ + e = true; + } + } else if (isspace(*p)) { + if (e) { + /* Add escaped whitespace literally */ + tmp[i] = *p; + i++; + e = false; + } else if (w == false) { + /* If previous character was non-whitespace, arg break */ + tmp[i] = '\0'; + i++; + w = true; + } + /* previous char was whitespace as well, skip it */ + } else { + w = false; + if (e) { + /* Prepend escaped chars with a literal \ */ + tmp[i] = '\\'; + i++; + e = false; + } + /* Copy character from the source string */ + tmp[i] = *p; + i++; + } + + p++; + + /* check if this was the last char */ + if (*p == '\0') { + if (e) { + tmp[i] = '\\'; + i++; + e = false; + } + tmp[i] = '\0'; + i++; + } + + /* save token to result array */ + if (i > 1 && tmp[i-1] == '\0') { + r = realloc(ret, (num + 2) * sizeof(char *)); + if (!r) goto fail; + ret = r; + ret[num+1] = NULL; + ret[num] = strdup(tmp); + if (!ret[num]) goto fail; + num++; + i = 0; + } + } + + free(tmp); + return ret; + +fail: + free(tmp); + free_args(ret); + return NULL; +} + +const char **dup_string_list(TALLOC_CTX *memctx, const char **str_list) +{ + int i = 0; + int j = 0; + const char **dup_list; + + if (!str_list) { + return NULL; + } + + /* Find the size of the list */ + while (str_list[i]) i++; + + dup_list = talloc_array(memctx, const char *, i+1); + if (!dup_list) { + return NULL; + } + + /* Copy the elements */ + for (j = 0; j < i; j++) { + dup_list[j] = talloc_strdup(dup_list, str_list[j]); + if (!dup_list[j]) { + talloc_free(dup_list); + return NULL; + } + } + + /* NULL-terminate the list */ + dup_list[i] = NULL; + + return dup_list; +} + +/* Take two string lists (terminated on a NULL char*) + * and return up to three arrays of strings based on + * shared ownership. + * + * Pass NULL to any return type you don't care about + */ +errno_t diff_string_lists(TALLOC_CTX *memctx, + char **_list1, + char **_list2, + char ***_list1_only, + char ***_list2_only, + char ***_both_lists) +{ + int error; + errno_t ret; + int i; + int i2 = 0; + int i12 = 0; + hash_table_t *table; + hash_key_t key; + hash_value_t value; + char **list1 = NULL; + char **list2 = NULL; + char **list1_only = NULL; + char **list2_only = NULL; + char **both_lists = NULL; + unsigned long count; + hash_key_t *keys; + + TALLOC_CTX *tmp_ctx = talloc_new(memctx); + if (!tmp_ctx) { + return ENOMEM; + } + + if (!_list1) { + list1 = talloc_array(tmp_ctx, char *, 1); + if (!list1) { + talloc_free(tmp_ctx); + return ENOMEM; + } + list1[0] = NULL; + } + else { + list1 = _list1; + } + + if (!_list2) { + list2 = talloc_array(tmp_ctx, char *, 1); + if (!list2) { + talloc_free(tmp_ctx); + return ENOMEM; + } + list2[0] = NULL; + } + else { + list2 = _list2; + } + + error = hash_create(0, &table, NULL, NULL); + if (error != HASH_SUCCESS) { + talloc_free(tmp_ctx); + return EIO; + } + + key.type = HASH_KEY_STRING; + value.type = HASH_VALUE_UNDEF; + + /* Add all entries from list 1 into a hash table */ + i = 0; + while (list1[i]) { + key.str = talloc_strdup(tmp_ctx, list1[i]); + error = hash_enter(table, &key, &value); + if (error != HASH_SUCCESS) { + ret = EIO; + goto done; + } + i++; + } + + /* Iterate through list 2 and remove matching items */ + i = 0; + while (list2[i]) { + key.str = talloc_strdup(tmp_ctx, list2[i]); + error = hash_delete(table, &key); + if (error == HASH_SUCCESS) { + if (_both_lists) { + /* String was present in both lists */ + i12++; + both_lists = talloc_realloc(tmp_ctx, both_lists, char *, i12+1); + if (!both_lists) { + ret = ENOMEM; + goto done; + } + both_lists[i12-1] = talloc_strdup(both_lists, list2[i]); + if (!both_lists[i12-1]) { + ret = ENOMEM; + goto done; + } + + both_lists[i12] = NULL; + } + } + else if (error == HASH_ERROR_KEY_NOT_FOUND) { + if (_list2_only) { + /* String was present only in list2 */ + i2++; + list2_only = talloc_realloc(tmp_ctx, list2_only, + char *, i2+1); + if (!list2_only) { + ret = ENOMEM; + goto done; + } + list2_only[i2-1] = talloc_strdup(list2_only, list2[i]); + if (!list2_only[i2-1]) { + ret = ENOMEM; + goto done; + } + + list2_only[i2] = NULL; + } + } + else { + /* An error occurred */ + ret = EIO; + goto done; + } + i++; + } + + /* Get the leftover entries in the hash table */ + if (_list1_only) { + error = hash_keys(table, &count, &keys); + if (error != HASH_SUCCESS) { + ret = EIO; + goto done; + } + + list1_only = talloc_array(tmp_ctx, char *, count+1); + if (!list1_only) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < count; i++) { + list1_only[i] = talloc_strdup(list1_only, keys[i].str); + if (!list1_only[i]) { + ret = ENOMEM; + goto done; + } + } + list1_only[count] = NULL; + + free(keys); + + *_list1_only = talloc_steal(memctx, list1_only); + } + + if (_list2_only) { + if (list2_only) { + *_list2_only = talloc_steal(memctx, list2_only); + } + else { + *_list2_only = talloc_array(memctx, char *, 1); + if (!(*_list2_only)) { + ret = ENOMEM; + goto done; + } + *_list2_only[0] = NULL; + } + } + + if (_both_lists) { + if (both_lists) { + *_both_lists = talloc_steal(memctx, both_lists); + } + else { + *_both_lists = talloc_array(memctx, char *, 1); + if (!(*_both_lists)) { + ret = ENOMEM; + goto done; + } + *_both_lists[0] = NULL; + } + } + + ret = EOK; + +done: + hash_destroy(table); + talloc_free(tmp_ctx); + return ret; +} + +static void *hash_talloc(const size_t size, void *pvt) +{ + return talloc_size(pvt, size); +} + +static void hash_talloc_free(void *ptr, void *pvt) +{ + talloc_free(ptr); +} + +errno_t sss_hash_create_ex(TALLOC_CTX *mem_ctx, + unsigned long count, + hash_table_t **tbl, + unsigned int directory_bits, + unsigned int segment_bits, + unsigned long min_load_factor, + unsigned long max_load_factor, + hash_delete_callback *delete_callback, + void *delete_private_data) +{ + errno_t ret; + hash_table_t *table; + int hret; + + TALLOC_CTX *internal_ctx; + internal_ctx = talloc_new(NULL); + if (!internal_ctx) { + return ENOMEM; + } + + hret = hash_create_ex(count, &table, directory_bits, segment_bits, + min_load_factor, max_load_factor, + hash_talloc, hash_talloc_free, internal_ctx, + delete_callback, delete_private_data); + switch (hret) { + case HASH_SUCCESS: + /* Steal the table pointer onto the mem_ctx, + * then make the internal_ctx a child of + * table. + * + * This way, we can clean up the values when + * we talloc_free() the table + */ + *tbl = talloc_steal(mem_ctx, table); + talloc_steal(table, internal_ctx); + return EOK; + + case HASH_ERROR_NO_MEMORY: + ret = ENOMEM; + break; + default: + ret = EIO; + } + + DEBUG(SSSDBG_FATAL_FAILURE, "Could not create hash table: [%d][%s]\n", + hret, hash_error_string(hret)); + + talloc_free(internal_ctx); + return ret; +} + +errno_t sss_hash_create(TALLOC_CTX *mem_ctx, unsigned long count, + hash_table_t **tbl) +{ + return sss_hash_create_ex(mem_ctx, count, tbl, 0, 0, 0, 0, NULL, NULL); +} + +char * +sss_escape_ip_address(TALLOC_CTX *mem_ctx, int family, const char *addr) +{ + return family == AF_INET6 ? talloc_asprintf(mem_ctx, "[%s]", addr) : + talloc_strdup(mem_ctx, addr); +} + +/* out->len includes terminating '\0' */ +void to_sized_string(struct sized_string *out, const char *in) +{ + out->str = in; + if (out->str) { + out->len = strlen(out->str) + 1; + } else { + out->len = 0; + } +} + +/* This function only removes first and last + * character if the first character was '['. + * + * NOTE: This means, that ipv6addr must NOT be followed + * by port number. + */ +errno_t +remove_ipv6_brackets(char *ipv6addr) +{ + size_t len; + + if (ipv6addr && ipv6addr[0] == '[') { + len = strlen(ipv6addr); + if (len < 3) { + return EINVAL; + } + + memmove(ipv6addr, &ipv6addr[1], len - 2); + ipv6addr[len -2] = '\0'; + } + + return EOK; +} + +errno_t add_string_to_list(TALLOC_CTX *mem_ctx, const char *string, + char ***list_p) +{ + size_t c; + char **old_list = NULL; + char **new_list = NULL; + + if (string == NULL || list_p == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing string or list.\n"); + return EINVAL; + } + + old_list = *list_p; + + if (old_list == NULL) { + /* If the input is a NULL list a new one is created with the new + * string and the terminating NULL element. */ + c = 0; + new_list = talloc_array(mem_ctx, char *, 2); + } else { + for (c = 0; old_list[c] != NULL; c++); + /* Allocate one extra space for the new service and one for + * the terminating NULL + */ + new_list = talloc_realloc(mem_ctx, old_list, char *, c + 2); + } + + if (new_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array/talloc_realloc failed.\n"); + return ENOMEM; + } + + new_list[c] = talloc_strdup(new_list, string); + if (new_list[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + talloc_free(new_list); + return ENOMEM; + } + + new_list[c + 1] = NULL; + + *list_p = new_list; + + return EOK; +} + +errno_t del_string_from_list(const char *string, + char ***list_p, bool case_sensitive) +{ + char **list; + int(*compare)(const char *s1, const char *s2); + + if (string == NULL || list_p == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing string or list.\n"); + return EINVAL; + } + + if (!string_in_list(string, *list_p, case_sensitive)) { + return ENOENT; + } + + compare = case_sensitive ? strcmp : strcasecmp; + list = *list_p; + int matches = 0; + int index = 0; + while (list[index]) { + if (compare(string, list[index]) == 0) { + matches++; + TALLOC_FREE(list[index]); + } else if (matches) { + list[index - matches] = list[index]; + list[index] = NULL; + } + index++; + } + + return EOK; +} + +int domain_to_basedn(TALLOC_CTX *memctx, const char *domain, char **basedn) +{ + const char *s; + char *dn; + char *p; + int l; + + if (!domain || !basedn) { + return EINVAL; + } + + s = domain; + dn = talloc_strdup(memctx, "dc="); + + while ((p = strchr(s, '.'))) { + l = p - s; + dn = talloc_asprintf_append_buffer(dn, "%.*s,dc=", l, s); + if (!dn) { + return ENOMEM; + } + s = p + 1; + } + dn = talloc_strdup_append_buffer(dn, s); + if (!dn) { + return ENOMEM; + } + + for (p=dn; *p; ++p) { + *p = tolower(*p); + } + + *basedn = dn; + return EOK; +} + +bool is_host_in_domain(const char *host, const char *domain) +{ + int diff = strlen(host) - strlen(domain); + + if (diff == 0 && strcmp(host, domain) == 0) { + return true; + } + + if (diff > 0 && strcmp(host + diff, domain) == 0 && host[diff - 1] == '.') { + return true; + } + + return false; +} + +#ifndef IN_LOOPBACK /* from <linux/in.h> */ +#define IN_LOOPBACK(a) ((((long int) (a)) & 0xff000000) == 0x7f000000) +#endif +/* addr is in network order for both IPv4 and IPv6 versions */ +bool check_ipv4_addr(struct in_addr *addr, uint8_t flags) +{ + char straddr[INET_ADDRSTRLEN]; + + if (inet_ntop(AF_INET, addr, straddr, INET_ADDRSTRLEN) == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "inet_ntop failed, won't log IP addresses\n"); + snprintf(straddr, INET_ADDRSTRLEN, "unknown"); + } + + if ((flags & SSS_NO_MULTICAST) && IN_MULTICAST(ntohl(addr->s_addr))) { + DEBUG(SSSDBG_FUNC_DATA, "Multicast IPv4 address %s\n", straddr); + return false; + } else if ((flags & SSS_NO_LOOPBACK) + && IN_LOOPBACK(ntohl(addr->s_addr))) { + DEBUG(SSSDBG_FUNC_DATA, "Loopback IPv4 address %s\n", straddr); + return false; + } else if ((flags & SSS_NO_LINKLOCAL) + && (addr->s_addr & htonl(0xffff0000)) == htonl(0xa9fe0000)) { + /* 169.254.0.0/16 */ + DEBUG(SSSDBG_FUNC_DATA, "Link-local IPv4 address %s\n", straddr); + return false; + } else if ((flags & SSS_NO_BROADCAST) + && addr->s_addr == htonl(INADDR_BROADCAST)) { + DEBUG(SSSDBG_FUNC_DATA, "Broadcast IPv4 address %s\n", straddr); + return false; + } + + return true; +} + +bool check_ipv6_addr(struct in6_addr *addr, uint8_t flags) +{ + char straddr[INET6_ADDRSTRLEN]; + + if (inet_ntop(AF_INET6, addr, straddr, INET6_ADDRSTRLEN) == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "inet_ntop failed, won't log IP addresses\n"); + snprintf(straddr, INET6_ADDRSTRLEN, "unknown"); + } + + if ((flags & SSS_NO_LINKLOCAL) && IN6_IS_ADDR_LINKLOCAL(addr)) { + DEBUG(SSSDBG_FUNC_DATA, "Link local IPv6 address %s\n", straddr); + return false; + } else if ((flags & SSS_NO_LOOPBACK) && IN6_IS_ADDR_LOOPBACK(addr)) { + DEBUG(SSSDBG_FUNC_DATA, "Loopback IPv6 address %s\n", straddr); + return false; + } else if ((flags & SSS_NO_MULTICAST) && IN6_IS_ADDR_MULTICAST(addr)) { + DEBUG(SSSDBG_FUNC_DATA, "Multicast IPv6 address %s\n", straddr); + return false; + } + + return true; +} + +const char * const * get_known_services(void) +{ + static const char *svc[] = {"nss", "pam", "sudo", "autofs", + "ssh", "pac", "ifp", NULL }; + + return svc; +} + +errno_t add_strings_lists_ex(TALLOC_CTX *mem_ctx, + const char **l1, const char **l2, + bool copy_strings, bool skip_dups, + const char ***_new_list) +{ + size_t c; + size_t n; + size_t l1_count = 0; + size_t l2_count = 0; + size_t new_count = 0; + const char **new; + int ret; + + if (l1 != NULL) { + for (l1_count = 0; l1[l1_count] != NULL; l1_count++); + } + + if (l2 != NULL) { + for (l2_count = 0; l2[l2_count] != NULL; l2_count++); + } + + new_count = l1_count + l2_count; + + new = talloc_zero_array(mem_ctx, const char *, new_count + 1); + if (new == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + return ENOMEM; + } + + if (copy_strings || skip_dups) { + n = 0; + for(c = 0; c < l1_count; c++) { + if (skip_dups) { + if (string_in_list_size(l1[c], new, n, false)) { + continue; + } + } + if (copy_strings) { + new[n] = talloc_strdup(new, l1[c]); + if (new[n] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } else { + new[n] = discard_const(l1[c]); + } + n++; + } + for(c = 0; c < l2_count; c++) { + if (skip_dups) { + if (string_in_list_size(l2[c], new, n, false)) { + continue; + } + } + if (copy_strings) { + new[n] = talloc_strdup(new, l2[c]); + if (new[n] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } else { + new[n] = discard_const(l2[c]); + } + n++; + } + } else { + if (l1 != NULL) { + memcpy(new, l1, sizeof(char *) * l1_count); + } + + if (l2 != NULL) { + memcpy(&new[l1_count], l2, sizeof(char *) * l2_count); + } + } + + *_new_list = new; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(new); + } + + return ret; +} + +/* Set the nonblocking flag to the fd */ +errno_t sss_fd_nonblocking(int fd) +{ + int flags; + int ret; + + flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "F_GETFL failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "F_SETFL failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + return EOK; +} + +/* Convert GeneralizedTime (http://en.wikipedia.org/wiki/GeneralizedTime) + * to unix time (seconds since epoch). Use UTC time zone. + */ +errno_t sss_utc_to_time_t(const char *str, const char *format, time_t *_unix_time) +{ + char *end; + struct tm tm; + size_t len; + time_t ut; + + if (str == NULL) { + return EINVAL; + } + + len = strlen(str); + if (str[len-1] != 'Z') { + DEBUG(SSSDBG_TRACE_INTERNAL, + "%s does not seem to be in UTZ time zone.\n", str); + return ERR_TIMESPEC_NOT_SUPPORTED; + } + + memset(&tm, 0, sizeof(tm)); + + end = strptime(str, format, &tm); + /* not all characters from format were matched */ + if (end == NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "String [%s] failed to match format [%s].\n", str, format); + return EINVAL; + } + + /* str is 'longer' than format */ + if (*end != '\0') { + DEBUG(SSSDBG_TRACE_INTERNAL, + "String [%s] is longer than format [%s].\n", str, format); + return EINVAL; + } + + ut = mktime(&tm); + if (ut == -1) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "mktime failed to convert [%s].\n", str); + return EINVAL; + } + + tzset(); + ut -= timezone; + *_unix_time = ut; + return EOK; +} + +struct tmpfile_watch { + const char *filename; +}; + +static int unlink_dbg(const char *filename) +{ + errno_t ret; + + ret = unlink(filename); + if (ret != 0) { + ret = errno; + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "File already removed: [%s]\n", filename); + return 0; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot remove temporary file [%s] %d [%s]\n", + filename, ret, strerror(ret)); + return -1; + } + } + + return 0; +} + +static int unique_filename_destructor(void *memptr) +{ + struct tmpfile_watch *tw = talloc_get_type(memptr, struct tmpfile_watch); + + if (tw == NULL || tw->filename == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Wrong private pointer\n"); + return -1; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Unlinking [%s]\n", tw->filename); + + return unlink_dbg(tw->filename); +} + +static struct tmpfile_watch *tmpfile_watch_set(TALLOC_CTX *owner, + const char *filename) +{ + struct tmpfile_watch *tw = NULL; + + tw = talloc_zero(owner, struct tmpfile_watch); + if (tw == NULL) { + return NULL; + } + + tw->filename = talloc_strdup(tw, filename); + if (tw->filename == NULL) { + talloc_free(tw); + return NULL; + } + + talloc_set_destructor((TALLOC_CTX *) tw, + unique_filename_destructor); + return tw; +} + +int sss_unique_file_ex(TALLOC_CTX *owner, + char *path_tmpl, + mode_t file_umask, + errno_t *_err) +{ + size_t tmpl_len; + errno_t ret; + int fd = -1; + mode_t old_umask; + struct tmpfile_watch *tw = NULL; + + tmpl_len = strlen(path_tmpl); + if (tmpl_len < 6 || strcmp(path_tmpl + (tmpl_len - 6), "XXXXXX") != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Template too short or doesn't end with XXXXXX!\n"); + ret = EINVAL; + goto done; + } + + old_umask = umask(file_umask); + fd = mkstemp(path_tmpl); + umask(old_umask); + if (fd == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "mkstemp(\"%s\") failed [%d]: %s!\n", + path_tmpl, ret, strerror(ret)); + goto done; + } + + if (owner != NULL) { + tw = tmpfile_watch_set(owner, path_tmpl); + if (tw == NULL) { + unlink_dbg(path_tmpl); + ret = ENOMEM; + goto done; + } + } + + ret = EOK; +done: + if (_err) { + *_err = ret; + } + return fd; +} + +int sss_unique_file(TALLOC_CTX *owner, + char *path_tmpl, + errno_t *_err) +{ + return sss_unique_file_ex(owner, path_tmpl, SSS_DFL_UMASK, _err); +} + +errno_t sss_unique_filename(TALLOC_CTX *owner, char *path_tmpl) +{ + int fd; + errno_t ret; + + fd = sss_unique_file(owner, path_tmpl, &ret); + /* We only care about a unique file name */ + if (fd >= 0) { + close(fd); + } + + return ret; +} + +bool is_user_or_group_name(const char *sudo_user_value) +{ + if (sudo_user_value == NULL) { + return false; + } + + /* See man sudoers.ldap for explanation */ + if (strcmp(sudo_user_value, "ALL") == 0) { + return false; + } + + switch (sudo_user_value[0]) { + case '#': /* user id */ + case '+': /* netgroup */ + case '\0': /* empty value */ + return false; + } + + if (sudo_user_value[0] == '%') { + switch (sudo_user_value[1]) { + case '#': /* POSIX group ID */ + case ':': /* non-POSIX group */ + case '\0': /* empty value */ + return false; + } + } + + /* Now it's either a username or a groupname */ + return true; +} + +bool is_socket_activated(void) +{ +#ifdef HAVE_SYSTEMD + return !!socket_activated; +#else + return false; +#endif +} + +bool is_dbus_activated(void) +{ +#ifdef HAVE_SYSTEMD + return !!dbus_activated; +#else + return false; +#endif +} + +int sss_rand(void) +{ + static bool srand_done = false; + + /* Coverity might complain here: "DC.WEAK_CRYPTO (CWE-327)" + * It is safe to ignore as this helper function is *NOT* intended + * to be used in security relevant context. + */ + if (!srand_done) { + srand(time(NULL) * getpid()); + srand_done = true; + } + return rand(); +} + +errno_t sss_canonicalize_ip_address(TALLOC_CTX *mem_ctx, + const char *address, + char **canonical_address) +{ + struct addrinfo hints; + struct addrinfo *result = NULL; + char buf[INET6_ADDRSTRLEN + 1]; + int ret; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + + ret = getaddrinfo(address, NULL, &hints, &result); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to canonicalize address [%s]: %s", + address, gai_strerror(ret)); + return EINVAL; + } + + ret = getnameinfo(result->ai_addr, result->ai_addrlen, buf, sizeof(buf), + NULL, 0, NI_NUMERICHOST); + freeaddrinfo(result); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to canonicalize address [%s]: %s", + address, gai_strerror(ret)); + return EINVAL; + } + + *canonical_address = talloc_strdup(mem_ctx, buf); + if (*canonical_address == NULL) { + return ENOMEM; + } + + return EOK; +} + +/* According to the https://tools.ietf.org/html/rfc2181#section-11. + * practically no restrictions are imposed to a domain name per se. + * + * But since SSSD uses this name as a part of log file name, + * it is still required to avoid '/' as a safety measure. + */ +bool is_valid_domain_name(const char *domain) +{ + if (strchr(domain, '/') != NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Forbidden symbol '/' in the domain name '%s'\n", domain); + return false; + } + + return true; +} + +errno_t sss_getenv(TALLOC_CTX *mem_ctx, + const char *variable_name, + const char *default_value, + char **_value) +{ + char *value = getenv(variable_name); + if (value == NULL && default_value == NULL) { + return ENOENT; + } + + *_value = talloc_strdup(mem_ctx, value != NULL ? value : default_value); + if (*_value == NULL) { + return ENOMEM; + } + + return value != NULL ? EOK : ENOENT; +} diff --git a/src/util/util.h b/src/util/util.h new file mode 100644 index 0000000..f3a4926 --- /dev/null +++ b/src/util/util.h @@ -0,0 +1,902 @@ +/* + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __SSSD_UTIL_H__ +#define __SSSD_UTIL_H__ + +#include "config.h" +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <libintl.h> +#include <locale.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <limits.h> +#include <sys/un.h> + +#include <talloc.h> +#include <tevent.h> +#include <ldb.h> +#include <dhash.h> + +#include "confdb/confdb.h" +#include "shared/io.h" +#include "shared/safealign.h" +#include "util/atomic_io.h" +#include "util/util_errors.h" +#include "util/sss_format.h" +#include "util/sss_regexp.h" +#include "util/debug.h" + +/* name of the monitor server instance */ +#define SSSD_MONITOR_NAME "sssd" +#define SSSD_PIDFILE PID_PATH"/"SSSD_MONITOR_NAME".pid" +#define MAX_PID_LENGTH 10 + +#define _(STRING) gettext (STRING) + +#define ENUM_INDICATOR "*" + +/* + * CLEAR_MC_FLAG is a flag file used to notify NSS responder + * that SIGHUP signal it received was triggered by sss_cache + * as a call for memory cache clearing. During the procedure + * this file is deleted by NSS responder to notify back + * sss_cache that memory cache clearing was completed. + */ +#define CLEAR_MC_FLAG "clear_mc_flag" + +/* Default secure umask */ +#define SSS_DFL_UMASK 0177 + +/* Secure mask with executable bit */ +#define SSS_DFL_X_UMASK 0077 + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef ALLPERMS +#define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */ +#endif + +#define SSSD_MAIN_OPTS SSSD_DEBUG_OPTS + +#define SSSD_SERVER_OPTS(uid, gid) \ + {"uid", 0, POPT_ARG_INT, &uid, 0, \ + _("The user ID to run the server as"), NULL}, \ + {"gid", 0, POPT_ARG_INT, &gid, 0, \ + _("The group ID to run the server as"), NULL}, + +extern int socket_activated; +extern int dbus_activated; + +#ifdef HAVE_SYSTEMD +#define SSSD_RESPONDER_OPTS \ + { "socket-activated", 0, POPT_ARG_NONE, &socket_activated, 0, \ + _("Informs that the responder has been socket-activated"), NULL }, \ + { "dbus-activated", 0, POPT_ARG_NONE, &dbus_activated, 0, \ + _("Informs that the responder has been dbus-activated"), NULL }, +#else +#define SSSD_RESPONDER_OPTS +#endif + +#define FLAGS_NONE 0x0000 +#define FLAGS_DAEMON 0x0001 +#define FLAGS_INTERACTIVE 0x0002 +#define FLAGS_PID_FILE 0x0004 +#define FLAGS_GEN_CONF 0x0008 +#define FLAGS_NO_WATCHDOG 0x0010 + +enum sssd_exit_status { + CHILD_TIMEOUT_EXIT_CODE = 7, + CA_DB_NOT_FOUND_EXIT_CODE = 50, + SSS_WATCHDOG_EXIT_CODE = 70 /* to match EX_SOFTWARE in sysexits.h */ +}; + +#define PIPE_INIT { -1, -1 } + +#define PIPE_FD_CLOSE(fd) do { \ + if (fd != -1) { \ + close(fd); \ + fd = -1; \ + } \ +} while(0); + +#define PIPE_CLOSE(p) do { \ + PIPE_FD_CLOSE(p[0]); \ + PIPE_FD_CLOSE(p[1]); \ +} while(0); + +#ifndef talloc_zfree +#define talloc_zfree(ptr) do { talloc_free(discard_const(ptr)); ptr = NULL; } while(0) +#endif + +#ifndef discard_const_p +#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T) +# define discard_const_p(type, ptr) ((type *)((intptr_t)(ptr))) +#else +# define discard_const_p(type, ptr) ((type *)(ptr)) +#endif +#endif + +#define TEVENT_REQ_RETURN_ON_ERROR(req) do { \ + enum tevent_req_state TRROEstate; \ + uint64_t TRROEuint64; \ + errno_t TRROEerr; \ + \ + if (tevent_req_is_error(req, &TRROEstate, &TRROEuint64)) { \ + TRROEerr = (errno_t)TRROEuint64; \ + switch (TRROEstate) { \ + case TEVENT_REQ_USER_ERROR: \ + if (TRROEerr == 0) { \ + return ERR_INTERNAL; \ + } \ + return TRROEerr; \ + case TEVENT_REQ_TIMED_OUT: \ + return ETIMEDOUT; \ + default: \ + return ERR_INTERNAL; \ + } \ + } \ +} while (0) + +#define OUT_OF_ID_RANGE(id, min, max) \ + (id == 0 || (min && (id < min)) || (max && (id > max))) + +#include "util/dlinklist.h" + +/* From sss_log.c */ +#define SSS_LOG_EMERG 0 /* system is unusable */ +#define SSS_LOG_ALERT 1 /* action must be taken immediately */ +#define SSS_LOG_CRIT 2 /* critical conditions */ +#define SSS_LOG_ERR 3 /* error conditions */ +#define SSS_LOG_WARNING 4 /* warning conditions */ +#define SSS_LOG_NOTICE 5 /* normal but significant condition */ +#define SSS_LOG_INFO 6 /* informational */ +#define SSS_LOG_DEBUG 7 /* debug-level messages */ + +void sss_log(int priority, const char *format, ...) SSS_ATTRIBUTE_PRINTF(2, 3); +void sss_log_ext(int priority, int facility, const char *format, ...) SSS_ATTRIBUTE_PRINTF(3, 4); + +/* from server.c */ +#define DEBUG_CHAIN_ID_FMT_RID "[RID#%"PRIu64"] %s" +#define DEBUG_CHAIN_ID_FMT_CID "[CID#%"PRIu64"] %s" + +struct main_context { + struct tevent_context *event_ctx; + struct confdb_ctx *confdb_ctx; + pid_t parent_pid; +}; + +struct sbus_request; + +errno_t server_common_rotate_logs(struct confdb_ctx *confdb, + const char *conf_entry); +errno_t generic_get_debug_level(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + void *pvt_data, + uint32_t *_debug_level); +errno_t generic_set_debug_level(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + void *pvt_data, + uint32_t new_debug_level); +int die_if_parent_died(void); +int check_pidfile(const char *file); +int pidfile(const char *file); +int server_setup(const char *name, bool is_responder, + int flags, + uid_t uid, gid_t gid, + const char *conf_entry, + struct main_context **main_ctx, + bool allow_sss_loop); +void server_loop(struct main_context *main_ctx); +void orderly_shutdown(int status); + +/* from signal.c */ +void BlockSignals(bool block, int signum); +void (*CatchSignal(int signum,void (*handler)(int )))(int); + +/* from memory.c */ +typedef int (void_destructor_fn_t)(void *); +/* sssd_mem_attach + * This function will take a non-talloc pointer and "attach" it to a talloc + * memory context. It will accept a destructor for the original pointer + * so that when the parent memory context is freed, the non-talloc + * pointer will also be freed properly. + * Returns EOK in case of success. + */ +int sss_mem_attach(TALLOC_CTX *mem_ctx, void *ptr, void_destructor_fn_t *fn); + +/* sss_erase_talloc_mem_securely() function always returns 0 as an int value + * to make it possible to use it as talloc destructor. + */ +int sss_erase_talloc_mem_securely(void *p); +void sss_erase_mem_securely(void *p, size_t size); + +/* from usertools.c */ +char *get_uppercase_realm(TALLOC_CTX *memctx, const char *name); + +struct sss_names_ctx { + char *re_pattern; + char *fq_fmt; + + sss_regexp_t *re; +}; + +#define SSS_DEFAULT_RE "^((?P<name>.+)@(?P<domain>[^@]+)|(?P<name>[^@]+))$" + +#define SSS_IPA_AD_DEFAULT_RE "^(((?P<domain>[^\\\\]+)\\\\(?P<name>.+))|" \ + "((?P<name>.+)@(?P<domain>[^@]+))|" \ + "((?P<name>[^@\\\\]+)))$" + +/* initialize sss_names_ctx directly from arguments */ +int sss_names_init_from_args(TALLOC_CTX *mem_ctx, + const char *re_pattern, + const char *fq_fmt, + struct sss_names_ctx **out); + +/* initialize sss_names_ctx from domain configuration */ +int sss_names_init(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *domain, + struct sss_names_ctx **out); + +int sss_ad_default_names_ctx(TALLOC_CTX *mem_ctx, + struct sss_names_ctx **_out); + +int sss_parse_name(TALLOC_CTX *memctx, + struct sss_names_ctx *snctx, + const char *orig, char **_domain, char **_name); + +int sss_parse_name_for_domains(TALLOC_CTX *memctx, + struct sss_domain_info *domains, + const char *default_domain, + const char *orig, char **domain, char **name); + +char * +sss_get_cased_name(TALLOC_CTX *mem_ctx, const char *orig_name, + bool case_sensitive); + +errno_t +sss_get_cased_name_list(TALLOC_CTX *mem_ctx, const char * const *orig, + bool case_sensitive, const char ***_cased); + +/* Return fully-qualified name according to the fq_fmt. The name is allocated using + * talloc on top of mem_ctx + */ +char * +sss_tc_fqname(TALLOC_CTX *mem_ctx, struct sss_names_ctx *nctx, + struct sss_domain_info *domain, const char *name); + +/* Return fully-qualified name according to the fq_fmt. The name is allocated using + * talloc on top of mem_ctx. In contrast to sss_tc_fqname() sss_tc_fqname2() + * expects the domain and flat domain name as separate arguments. + */ +char * +sss_tc_fqname2(TALLOC_CTX *mem_ctx, struct sss_names_ctx *nctx, + const char *dom_name, const char *flat_dom_name, + const char *name); + +/* Return fully-qualified name formatted according to the fq_fmt. The buffer in "str" is + * "size" bytes long. Returns the number of bytes written on success or a negative + * value of failure. + * + * Pass a zero size to calculate the length that would be needed by the fully-qualified + * name. + */ +int +sss_fqname(char *str, size_t size, struct sss_names_ctx *nctx, + struct sss_domain_info *domain, const char *name); + + +/* Accepts fqname in the format shortname@domname only. */ +errno_t sss_parse_internal_fqname(TALLOC_CTX *mem_ctx, + const char *fqname, + char **_shortname, + char **_dom_name); + +/* Creates internal fqname in format shortname@domname. + * The domain portion is lowercased. */ +char *sss_create_internal_fqname(TALLOC_CTX *mem_ctx, + const char *shortname, + const char *dom_name); + +/* Creates internal fqnames list in format shortname@domname. + * The domain portion is lowercased. */ +char **sss_create_internal_fqname_list(TALLOC_CTX *mem_ctx, + const char * const *shortname_list, + const char *dom_name); + +/* Turn fqname into cased shortname with replaced space. */ +char *sss_output_name(TALLOC_CTX *mem_ctx, + const char *fqname, + bool case_sensitive, + const char replace_space); + +int sss_output_fqname(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *name, + char override_space, + char **_output_name); + +const char *sss_get_name_from_msg(struct sss_domain_info *domain, + struct ldb_message *msg); + +/* from backup-file.c */ +int backup_file(const char *src, int dbglvl); + +/* check_file() + * Verify that a file has certain permissions and/or is of a certain + * file type. This function can be used to determine if a file is a + * symlink. + * Warning: use of this function implies a potential race condition + * Opening a file before or after checking it does NOT guarantee that + * it is still the same file. Additional checks should be performed + * on the caller_stat_buf to ensure that it has the same device and + * inode to minimize impact. Permission changes may have occurred, + * however. + */ +errno_t check_file(const char *filename, + uid_t uid, gid_t gid, mode_t mode, mode_t mask, + struct stat *caller_stat_buf, bool follow_symlink); + +/* from util.c */ +#define SSS_NO_LINKLOCAL 0x01 +#define SSS_NO_LOOPBACK 0x02 +#define SSS_NO_MULTICAST 0x04 +#define SSS_NO_BROADCAST 0x08 + +#define SSS_NO_SPECIAL \ + (SSS_NO_LINKLOCAL|SSS_NO_LOOPBACK|SSS_NO_MULTICAST|SSS_NO_BROADCAST) + +/* These two functions accept addr in network order */ +bool check_ipv4_addr(struct in_addr *addr, uint8_t check); +bool check_ipv6_addr(struct in6_addr *addr, uint8_t check); + +/* Returns the canonical form of an IPv4 or IPv6 address */ +errno_t sss_canonicalize_ip_address(TALLOC_CTX *mem_ctx, + const char *address, + char **canonical_address); + +const char * const * get_known_services(void); + +errno_t sss_user_by_name_or_uid(const char *input, uid_t *_uid, gid_t *_gid); +void sss_sssd_user_uid_and_gid(uid_t *_uid, gid_t *_gid); +void sss_set_sssd_user_eid(void); +void sss_restore_sssd_user_eid(void); + +int split_on_separator(TALLOC_CTX *mem_ctx, const char *str, + const char sep, bool trim, bool skip_empty, + char ***_list, int *size); + +char **parse_args(const char *str); + +errno_t sss_hash_create(TALLOC_CTX *mem_ctx, + unsigned long count, + hash_table_t **tbl); + +errno_t sss_hash_create_ex(TALLOC_CTX *mem_ctx, + unsigned long count, + hash_table_t **tbl, + unsigned int directory_bits, + unsigned int segment_bits, + unsigned long min_load_factor, + unsigned long max_load_factor, + hash_delete_callback *delete_callback, + void *delete_private_data); + +/* Returns true if sudoUser value is a username or a groupname */ +bool is_user_or_group_name(const char *sudo_user_value); + +/* Returns true if the responder has been socket-activated */ +bool is_socket_activated(void); + +/* Returns true if the responder has been dbus-activated */ +bool is_dbus_activated(void); + +/** + * @brief Add two list of strings + * + * Create a new NULL-terminated list of strings by adding two lists together. + * + * @param[in] mem_ctx Talloc memory context for the new list. + * @param[in] l1 First NULL-terminated list of strings. + * @param[in] l2 Second NULL-terminated list of strings. + * @param[in] copy_strings If set to 'true' the list items will be copied + * otherwise only the pointers to the items are + * copied. + * @param[in] skip_dups Whether the function should skip duplicate values. + * @param[out] new_list New NULL-terminated list of strings. Must be freed + * with talloc_free() by the caller. If copy_strings + * is 'true' the new elements will be freed as well. + */ +errno_t add_strings_lists_ex(TALLOC_CTX *mem_ctx, + const char **l1, const char **l2, + bool copy_strings, bool skip_dups, + const char ***_new_list); + +/** + * @overload errno_t add_strings_lists_ex(TALLOC_CTX *mem_ctx, + * const char **l1, const char **l2, + * bool copy_strings, bool skip_dups, + * const char ***_new_list) + */ +static inline errno_t add_strings_lists(TALLOC_CTX *mem_ctx, + const char **l1, const char **l2, + bool copy_strings, + const char ***_new_list) +{ + return add_strings_lists_ex(mem_ctx, l1, l2, copy_strings, false, _new_list); +} + + +/** + * @brief set file descriptor as nonblocking + * + * Set the O_NONBLOCK flag for the input fd + * + * @param[in] fd The file descriptor to set as nonblocking + * + * @return EOK on success, errno code otherwise + */ +errno_t sss_fd_nonblocking(int fd); + +/* Copy a NULL-terminated string list + * Returns NULL on out of memory error or invalid input + */ +const char **dup_string_list(TALLOC_CTX *memctx, const char **str_list); + +/* Take two string lists (terminated on a NULL char*) + * and return up to three arrays of strings based on + * shared ownership. + * + * Pass NULL to any return type you don't care about + */ +errno_t diff_string_lists(TALLOC_CTX *memctx, + char **string1, + char **string2, + char ***string1_only, + char ***string2_only, + char ***both_strings); + +/* Sanitize an input string (e.g. a username) for use in + * an LDAP/LDB filter + * Returns a newly-constructed string attached to mem_ctx + * It will fail only on an out of memory condition, where it + * will return ENOMEM. + */ +errno_t sss_filter_sanitize(TALLOC_CTX *mem_ctx, + const char *input, + char **sanitized); + +errno_t sss_filter_sanitize_ex(TALLOC_CTX *mem_ctx, + const char *input, + char **sanitized, + const char *ignore); + +errno_t sss_filter_sanitize_for_dom(TALLOC_CTX *mem_ctx, + const char *input, + struct sss_domain_info *dom, + char **sanitized, + char **lc_sanitized); + +/* Sanitize an input string (e.g. a DN) for use in + * an LDAP/LDB filter + * + * It is basically the same as sss_filter_sanitize(_ex), + * just extra spaces inside DN around '=' and ',' are removed + * before sanitizing other characters . According the documentation + * spaces in DN are allowed and some ldap servers can return them + * in isMemberOf or member attributes. + * + * (dc = my example, dc = com => dc=my\20example,dc=com) + * + * Returns a newly-constructed string attached to mem_ctx + * It will fail only on an out of memory condition, where it + * will return ENOMEM. + * + */ +errno_t sss_filter_sanitize_dn(TALLOC_CTX *mem_ctx, + const char *input, + char **sanitized); + +char * +sss_escape_ip_address(TALLOC_CTX *mem_ctx, int family, const char *addr); + +/* This function only removes first and last + * character if the first character was '['. + * + * NOTE: This means, that ipv6addr must NOT be followed + * by port number. + */ +errno_t +remove_ipv6_brackets(char *ipv6addr); + + +errno_t add_string_to_list(TALLOC_CTX *mem_ctx, const char *string, + char ***list_p); + +errno_t del_string_from_list(const char *string, + char ***list_p, bool case_sensitive); + +bool string_in_list(const char *string, char **list, bool case_sensitive); + +bool string_in_list_size(const char *string, const char **list, size_t size, + bool case_sensitive); + +int domain_to_basedn(TALLOC_CTX *memctx, const char *domain, char **basedn); + +bool is_host_in_domain(const char *host, const char *domain); + +bool is_valid_domain_name(const char *domain); + +/* This is simple wrapper around libc rand() intended to avoid calling srand() + * explicitly, thus *not* suitable to be used in security relevant context. + * If CS properties are desired (security relevant functionality/FIPS/etc) then + * use sss_crypto.h:sss_generate_csprng_buffer() instead! + */ +int sss_rand(void); + +/* from nscd.c */ +errno_t sss_nscd_parse_conf(const char *conf_path); + +/* from sss_tc_utf8.c */ +char * +sss_tc_utf8_str_tolower(TALLOC_CTX *mem_ctx, const char *s); +uint8_t * +sss_tc_utf8_tolower(TALLOC_CTX *mem_ctx, const uint8_t *s, size_t len, size_t *_nlen); +/* from sss_utf8.c */ +bool sss_string_equal(bool cs, const char *s1, const char *s2); + +/* len includes terminating '\0' */ +struct sized_string { + const char *str; + size_t len; +}; + +void to_sized_string(struct sized_string *out, const char *in); + +/* from domain_info.c */ +struct sss_domain_info *get_domains_head(struct sss_domain_info *domain); + +#define SSS_GND_DESCEND 0x01 +#define SSS_GND_INCLUDE_DISABLED 0x02 +/* Descend to sub-domains of current domain but do not go to next parent */ +#define SSS_GND_SUBDOMAINS 0x04 +#define SSS_GND_ALL_DOMAINS (SSS_GND_DESCEND | SSS_GND_INCLUDE_DISABLED) +#define SSS_GND_ALL_SUBDOMAINS (SSS_GND_SUBDOMAINS | SSS_GND_INCLUDE_DISABLED) + +struct sss_domain_info *get_next_domain(struct sss_domain_info *domain, + uint32_t gnd_flags); +struct sss_domain_info *find_domain_by_name(struct sss_domain_info *domain, + const char *name, + bool match_any); +struct sss_domain_info *find_domain_by_name_ex(struct sss_domain_info *domain, + const char *name, + bool match_any, + uint32_t gnd_flags); +struct sss_domain_info *find_domain_by_sid(struct sss_domain_info *domain, + const char *sid); +enum sss_domain_state sss_domain_get_state(struct sss_domain_info *dom); +void sss_domain_set_state(struct sss_domain_info *dom, + enum sss_domain_state state); +#ifdef BUILD_FILES_PROVIDER +bool sss_domain_fallback_to_nss(struct sss_domain_info *dom); +#endif +bool sss_domain_is_forest_root(struct sss_domain_info *dom); +const char *sss_domain_type_str(struct sss_domain_info *dom); + +struct sss_domain_info* +sss_get_domain_by_sid_ldap_fallback(struct sss_domain_info *domain, + const char* sid); + +struct sss_domain_info * +find_domain_by_object_name(struct sss_domain_info *domain, + const char *object_name); + +struct sss_domain_info * +find_domain_by_object_name_ex(struct sss_domain_info *domain, + const char *object_name, bool strict, + uint32_t gnd_flags); + +bool subdomain_enumerates(struct sss_domain_info *parent, + const char *sd_name); + +char *subdomain_create_conf_path_from_str(TALLOC_CTX *mem_ctx, + const char *parent_name, + const char *subdom_name); +char *subdomain_create_conf_path(TALLOC_CTX *mem_ctx, + struct sss_domain_info *subdomain); + +errno_t sssd_domain_init(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *domain_name, + const char *db_path, + struct sss_domain_info **_domain); + +void sss_domain_info_set_output_fqnames(struct sss_domain_info *domain, + bool output_fqname); + +bool sss_domain_info_get_output_fqnames(struct sss_domain_info *domain); + +bool sss_domain_is_mpg(struct sss_domain_info *domain); + +bool sss_domain_is_hybrid(struct sss_domain_info *domain); + +enum sss_domain_mpg_mode get_domain_mpg_mode(struct sss_domain_info *domain); +const char *str_domain_mpg_mode(enum sss_domain_mpg_mode mpg_mode); +enum sss_domain_mpg_mode str_to_domain_mpg_mode(const char *str_mpg_mode); + +#define IS_SUBDOMAIN(dom) ((dom)->parent != NULL) + +#define DOM_HAS_VIEWS(dom) ((dom)->has_views) + +/* the directory domain - realm mappings and other krb5 config snippers are + * written to */ +#define KRB5_MAPPING_DIR PUBCONF_PATH"/krb5.include.d" + +errno_t sss_get_domain_mappings_content(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + char **content); + +errno_t sss_write_domain_mappings(struct sss_domain_info *domain); + +char *get_hidden_tmp_path(TALLOC_CTX *mem_ctx, const char *path); + +errno_t sss_write_krb5_conf_snippet(const char *path, bool canonicalize, + bool udp_limit); + +errno_t get_dom_names(TALLOC_CTX *mem_ctx, + struct sss_domain_info *start_dom, + char ***_dom_names, + int *_dom_names_count); + +__attribute__((always_inline)) +static inline bool is_domain_provider(struct sss_domain_info *domain, + const char *provider) +{ + return domain != NULL && + domain->provider != NULL && + strcasecmp(domain->provider, provider) == 0; +} + +/* Returns true if the provider used for the passed domain is the "files" + * one. Otherwise returns false. */ +__attribute__((always_inline)) +static inline bool is_files_provider(struct sss_domain_info *domain) +{ +#ifdef BUILD_FILES_PROVIDER + return domain != NULL && + domain->provider != NULL && + strcasecmp(domain->provider, "files") == 0; +#else + return false; +#endif +} + +/* from util_lock.c */ +errno_t sss_br_lock_file(int fd, size_t start, size_t len, + int num_tries, useconds_t wait); + +#ifdef HAVE_PAC_RESPONDER +#define BUILD_WITH_PAC_RESPONDER true +#else +#define BUILD_WITH_PAC_RESPONDER false +#endif + +/* from well_known_sids.c */ +errno_t well_known_sid_to_name(const char *sid, const char **dom, + const char **name); + +errno_t name_to_well_known_sid(const char *dom, const char *name, + const char **sid); + +/* from string_utils.c */ +char *sss_replace_char(TALLOC_CTX *mem_ctx, + const char *in, + const char match, + const char sub); + +char * sss_replace_space(TALLOC_CTX *mem_ctx, + const char *orig_name, + const char replace_char); +char * sss_reverse_replace_space(TALLOC_CTX *mem_ctx, + const char *orig_name, + const char replace_char); + +#define GUID_BIN_LENGTH 16 +/* 16 2-digit hex values + 4 dashes + terminating 0 */ +#define GUID_STR_BUF_SIZE (2 * GUID_BIN_LENGTH + 4 + 1) + +errno_t guid_blob_to_string_buf(const uint8_t *blob, char *str_buf, + size_t buf_size); + +const char *get_last_x_chars(const char *str, size_t x); + +char **concatenate_string_array(TALLOC_CTX *mem_ctx, + char **arr1, size_t len1, + char **arr2, size_t len2); + +errno_t mod_defaults_list(TALLOC_CTX *mem_ctx, const char **defaults_list, + char **mod_list, char ***_list); + +/* from become_user.c */ +errno_t become_user(uid_t uid, gid_t gid); +struct sss_creds; +errno_t switch_creds(TALLOC_CTX *mem_ctx, + uid_t uid, gid_t gid, + int num_gids, gid_t *gids, + struct sss_creds **saved_creds); +errno_t restore_creds(struct sss_creds *saved_creds); + +/* from sss_semanage.c */ +/* Please note that libsemange relies on files and directories created with + * certain permissions. Therefore the caller should make sure the umask is + * not too restricted (especially when called from the daemon code). + */ +int sss_set_seuser(const char *login_name, const char *seuser_name, + const char *mlsrange); +int sss_del_seuser(const char *login_name); +int sss_get_seuser(const char *linuxuser, + char **selinuxuser, + char **level); +int sss_seuser_exists(const char *linuxuser); + +/* convert time from generalized form to unix time */ +errno_t sss_utc_to_time_t(const char *str, const char *format, time_t *unix_time); + +/* Creates a unique file using mkstemp with provided umask. The template + * must end with XXXXXX. Returns the fd, sets _err to an errno value on error. + * + * Prefer using sss_unique_file() as it uses a secure umask internally. + */ +int sss_unique_file_ex(TALLOC_CTX *mem_ctx, + char *path_tmpl, + mode_t file_umask, + errno_t *_err); +int sss_unique_file(TALLOC_CTX *owner, + char *path_tmpl, + errno_t *_err); + +/* Creates a unique filename using mkstemp with secure umask. The template + * must end with XXXXXX + * + * path_tmpl must be a talloc context. Destructor would be set on the filename + * so that it's guaranteed the file is removed. + */ +int sss_unique_filename(TALLOC_CTX *owner, char *path_tmpl); + +/* from util_watchdog.c */ +int setup_watchdog(struct tevent_context *ev, int interval); +void teardown_watchdog(void); +int get_watchdog_ticks(void); + +/* The arm_watchdog() and disarm_watchdog() calls will disable and re-enable + * the watchdog reset, respectively. This means that after arm_watchdog() is + * called the watchdog will not be resetted anymore and it will kill the + * process if disarm_watchdog() wasn't called before. + * Those calls should only be used when there is no other way to handle + * waiting request and recover into a stable state. + * Those calls cannot be nested, i.e. after calling arm_watchdog() it should + * not be called a second time in a different request because then + * disarm_watchdog() will disable the watchdog coverage for both. */ +void arm_watchdog(void); +void disarm_watchdog(void); + +/* from files.c */ +int sss_remove_tree(const char *root); +int sss_remove_subtree(const char *root); + +int sss_copy_tree(const char *src_root, + const char *dst_root, + mode_t mode_root, + uid_t uid, gid_t gid); + +int sss_copy_file_secure(const char *src, + const char *dest, + mode_t mode, + uid_t uid, gid_t gid, + bool force); + +int sss_create_dir(const char *parent_dir_path, + const char *dir_name, + mode_t mode, + uid_t uid, gid_t gid); + +/* from selinux.c */ +int selinux_file_context(const char *dst_name); +int reset_selinux_file_context(void); + +/* from cert_derb64_to_ldap_filter.c */ +struct sss_certmap_ctx; +errno_t sss_cert_derb64_to_ldap_filter(TALLOC_CTX *mem_ctx, const char *derb64, + const char *attr_name, + struct sss_certmap_ctx *certmap_ctx, + struct sss_domain_info *dom, + char **ldap_filter); + + +/* from util_preauth.c */ +errno_t create_preauth_indicator(void); + +#ifdef SSSD_LIBEXEC_PATH +#define P11_CHILD_LOG_FILE "p11_child" +#define P11_CHILD_PATH SSSD_LIBEXEC_PATH"/p11_child" +#define P11_CHILD_TIMEOUT_DEFAULT 10 +#define P11_WAIT_FOR_CARD_TIMEOUT_DEFAULT 60 +#define PASSKEY_CHILD_TIMEOUT_DEFAULT 15 +#define PASSKEY_CHILD_LOG_FILE "passkey_child" +#define PASSKEY_CHILD_PATH SSSD_LIBEXEC_PATH"/passkey_child" + +#endif /* SSSD_LIBEXEC_PATH */ + +#ifndef N_ELEMENTS +#define N_ELEMENTS(arr) (sizeof(arr) / sizeof(arr[0])) +#endif + +/* If variable is not set, it stores a copy of default_value (if not NULL) + * in _value but returns ENOENT so the information is propagated to the caller. + */ +errno_t sss_getenv(TALLOC_CTX *mem_ctx, + const char *variable_name, + const char *default_value, + char **_value); + +/* from sss_time.c */ +uint64_t get_start_time(void); + +const char *sss_format_time(uint64_t us); +uint64_t get_spend_time_us(uint64_t st); + +/* from pac_utils.h */ +#define CHECK_PAC_NO_CHECK_STR "no_check" +#define CHECK_PAC_PRESENT_STR "pac_present" +#define CHECK_PAC_PRESENT (1 << 0) +#define CHECK_PAC_CHECK_UPN_STR "check_upn" +#define CHECK_PAC_CHECK_UPN (1 << 1) +#define CHECK_PAC_UPN_DNS_INFO_PRESENT_STR "upn_dns_info_present" +#define CHECK_PAC_UPN_DNS_INFO_PRESENT (1 << 2) +#define CHECK_PAC_CHECK_UPN_DNS_INFO_EX_STR "check_upn_dns_info_ex" +#define CHECK_PAC_CHECK_UPN_DNS_INFO_EX (1 << 3) +#define CHECK_PAC_UPN_DNS_INFO_EX_PRESENT_STR "upn_dns_info_ex_present" +#define CHECK_PAC_UPN_DNS_INFO_EX_PRESENT (1 << 4) +#define CHECK_PAC_CHECK_UPN_ALLOW_MISSING_STR "check_upn_allow_missing" +#define CHECK_PAC_CHECK_UPN_ALLOW_MISSING (1 << 5) + +errno_t get_pac_check_config(struct confdb_ctx *cdb, uint32_t *pac_check_opts); + +static inline struct timeval sss_tevent_timeval_current_ofs_time_t(time_t secs) +{ + uint32_t secs32 = (secs > UINT_MAX ? UINT_MAX : secs); + return tevent_timeval_current_ofs(secs32, 0); +} +#endif /* __SSSD_UTIL_H__ */ diff --git a/src/util/util_creds.h b/src/util/util_creds.h new file mode 100644 index 0000000..994d0a9 --- /dev/null +++ b/src/util/util_creds.h @@ -0,0 +1,84 @@ +/* + Authors: + Simo Sorce <simo@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __SSSD_UTIL_CREDS_H__ +#define __SSSD_UTIL_CREDS_H__ + +/* following code comes from gss-proxy's gp_selinux.h file */ +#ifdef HAVE_SELINUX + +#include <selinux/context.h> +typedef context_t SELINUX_CTX; +#include <selinux/selinux.h> +typedef char * SEC_CTX; + +#define SELINUX_context_new context_new +#define SELINUX_context_free context_free +#define SELINUX_context_str context_str +#define SELINUX_context_type_get context_type_get +#define SELINUX_context_user_get context_user_get +#define SELINUX_context_role_get context_role_get +#define SELINUX_context_range_get context_range_get +#define SELINUX_getpeercon getpeercon +#define SELINUX_freecon freecon + +#else /* not HAVE_SELINUX */ + +typedef void * SELINUX_CTX; +typedef void * SEC_CTX; + +#define SELINUX_context_new(x) NULL +#define SELINUX_context_free(x) (x) = NULL +#define SELINUX_context_dummy_get(x) "<SELinux not compiled in>" +#define SELINUX_context_str SELINUX_context_dummy_get +#define SELINUX_context_type_get SELINUX_context_dummy_get +#define SELINUX_context_user_get SELINUX_context_dummy_get +#define SELINUX_context_role_get SELINUX_context_dummy_get +#define SELINUX_context_range_get SELINUX_context_dummy_get + +#include <errno.h> +#define SELINUX_getpeercon(x, y) -1; do { \ + *(y) = NULL; \ + errno = ENOTSUP; \ +} while(0) + +#define SELINUX_freecon(x) (x) = NULL + +#endif /* done HAVE_SELINUX */ + +#ifdef HAVE_UCRED +#include <sys/socket.h> +struct cli_creds { + struct ucred ucred; + SELINUX_CTX selinux_ctx; +}; + +#define cli_creds_get_uid(x) (x->ucred.uid) +#define cli_creds_get_gid(x) (x->ucred.gid) + +#else /* not HAVE_UCRED */ +struct cli_creds { + SELINUX_CTX selinux_ctx; +}; +#define cli_creds_get_uid(x) (-1) +#define cli_creds_get_gid(x) (-1) +#endif /* done HAVE_UCRED */ + +#endif /* __SSSD_UTIL_CREDS_H__ */ diff --git a/src/util/util_errors.c b/src/util/util_errors.c new file mode 100644 index 0000000..a9e7cf7 --- /dev/null +++ b/src/util/util_errors.c @@ -0,0 +1,194 @@ +/* + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + Authors: + Simo Sorce <ssorce@redhat.com> +*/ + +#include "util/util.h" +#include <ldb.h> + +struct err_string { + const char *msg; +}; + +struct err_string error_to_str[] = { + { "Invalid Error" }, /* ERR_INVALID */ + { "Internal Error" }, /* ERR_INTERNAL */ + { "SSSD is running" }, /* ERR_SSSD_RUNNING */ + { "SSSD is not running" }, /* ERR_SSSD_NOT_RUNNING */ + { "SSSD is offline" }, /* ERR_OFFLINE */ + { "Terminated" }, /* ERR_TERMINATED */ + { "Invalid data type" }, /* ERR_INVALID_DATA_TYPE */ + { "DP target is not configured" }, /* ERR_MISSING_DP_TARGET */ + { "Account Unknown" }, /* ERR_ACCOUNT_UNKNOWN */ + { "No suitable principal found in keytab" }, /* ERR_KRB5_PRINCIPAL_NOT_FOUND */ + { "Invalid credential type" }, /* ERR_INVALID_CRED_TYPE */ + { "No credentials available" }, /* ERR_NO_CREDS */ + { "Credentials are expired" }, /* ERR_CREDS_EXPIRED */ + { "Credentials are expired, old ccache was removed" }, /* ERR_CREDS_EXPIRED_CCACHE */ + { "Failure setting user credentials"}, /* ERR_CREDS_INVALID */ + { "No cached credentials available" }, /* ERR_NO_CACHED_CREDS */ + { "No matching credentials found" }, /* ERR_NO_MATCHING_CREDS */ + { "Cached credentials are expired" }, /* ERR_CACHED_CREDS_EXPIRED */ + { "Authentication Denied" }, /* ERR_AUTH_DENIED */ + { "Authentication Failed" }, /* ERR_AUTH_FAILED */ + { "Password Change Denied" }, /* ERR_CHPASS_DENIED */ + { "Password Change Failed" }, /* ERR_CHPASS_FAILED */ + { "Network I/O Error" }, /* ERR_NETWORK_IO */ + { "Account Expired" }, /* ERR_ACCOUNT_EXPIRED */ + { "Password Expired" }, /* ERR_PASSWORD_EXPIRED */ + { "Password Expired (reject access)" }, /* ERR_PASSWORD_EXPIRED_REJECT */ + { "Password Expired (warn user)" }, /* ERR_PASSWORD_EXPIRED_WARN */ + { "Password Expired (ask for new password)" }, /* ERR_PASSWORD_EXPIRED_RENEW */ + { "Host Access Denied" }, /* ERR_ACCESS_DENIED */ + { "SRV record not found" }, /* ERR_SRV_NOT_FOUND */ + { "SRV lookup error" }, /* ERR_SRV_LOOKUP_ERROR */ + { "SRV lookup did not return any new server" }, /* ERR_SRV_DUPLICATES */ + { "Dynamic DNS update failed" }, /* ERR_DYNDNS_FAILED */ + { "Dynamic DNS update timed out" }, /* ERR_DYNDNS_TIMEOUT */ + { "Dynamic DNS update not possible while offline" }, /* ERR_DYNDNS_OFFLINE */ + { "Cannot parse input" }, /* ERR_INPUT_PARSE */ + { "Entry not found" }, /* ERR_NOT_FOUND */ + { "Domain not found" }, /* ERR_DOMAIN_NOT_FOUND */ + { "No domain is enabled" }, /* ERR_NO_DOMAIN_ENABLED */ + { "Malformed search filter" }, /* ERR_INVALID_FILTER, */ + { "No POSIX attributes detected" }, /* ERR_NO_POSIX */ + { "Extra attribute is a duplicate" }, /* ERR_DUP_EXTRA_ATTR */ + { "Malformed extra attribute" }, /* ERR_INVALID_EXTRA_ATTR */ + { "Cannot get bus message sender" }, /* ERR_SBUS_GET_SENDER_ERROR */ + { "Bus message has no sender" }, /* ERR_SBUS_NO_SENDER */ + { "Invalid SBUS path provided" }, /* ERR_SBUS_INVALID_PATH */ + { "User/Group SIDs not found" }, /* ERR_NO_SIDS */ + { "Bus method not supported" }, /* ERR_SBUS_NOSUP */ + { "Cannot connect to system bus" }, /* ERR_NO_SYSBUS */ + { "LDAP search returned a referral" }, /* ERR_REFERRAL */ + { "Error setting SELinux user context" }, /* ERR_SELINUX_CONTEXT */ + { "SELinux is not managed by libsemanage" }, /* ERR_SELINUX_NOT_MANAGED */ + { "SELinux user does not exist" }, /* ERR_SELINUX_USER_NOT_FOUND */ + { "Username format not allowed by re_expression" }, /* ERR_REGEX_NOMATCH */ + { "Time specification not supported" }, /* ERR_TIMESPEC_NOT_SUPPORTED */ + { "Invalid SSSD configuration detected" }, /* ERR_INVALID_CONFIG */ + { "Malformed cache entry" }, /* ERR_MALFORMED_ENTRY */ + { "Unexpected cache entry type" }, /* ERR_UNEXPECTED_ENTRY_TYPE */ + { "Failed to resolve one of user groups" }, /* ERR_SIMPLE_GROUPS_MISSING */ + { "Home directory is NULL" }, /* ERR_HOMEDIR_IS_NULL */ + { "Unsupported trust direction" }, /* ERR_TRUST_NOT_SUPPORTED */ + { "Retrieving keytab failed" }, /* ERR_IPA_GETKEYTAB_FAILED */ + { "Trusted forest root unknown" }, /* ERR_TRUST_FOREST_UNKNOWN */ + { "p11_child failed" }, /* ERR_P11_CHILD */ + { "p11_child timeout" }, /* ERR_P11_CHILD_TIMEOUT */ + { "PIN locked" }, /* ERR_P11_PIN_LOCKED */ + { "passkey_child failed" }, /* ERR_PASSKEY_CHILD */ + { "passkey_child timeout" }, /* ERR_PASSKEY_CHILD_TIMEOUT */ + { "Address family not supported" }, /* ERR_ADDR_FAMILY_NOT_SUPPORTED */ + { "Message sender is the bus" }, /* ERR_SBUS_SENDER_BUS */ + { "Subdomain is inactive" }, /* ERR_SUBDOM_INACTIVE */ + { "Account is locked" }, /* ERR_ACCOUNT_LOCKED */ + { "AD renewal child failed" }, /* ERR_RENEWAL_CHILD */ + { "SBUS request already handled" }, /* ERR_SBUS_REQUEST_HANDLED */ + { "Sysdb version is too old" }, /* ERR_SYSDB_VERSION_TOO_OLD */ + { "Sysdb version is too new" }, /* ERR_SYSDB_VERSION_TOO_NEW */ + { "Domain has to timestamp cache" }, /* ERR_NO_TS */ + { "No timestamp cache record" }, /* ERR_TS_CACHE_MISS */ + { "Dereference threshold reached" }, /* ERR_DEREF_THRESHOLD */ + { "The user is not handled by SSSD" }, /* ERR_NON_SSSD_USER */ + { "The internal name format cannot be parsed" }, /* ERR_WRONG_NAME_FORMAT */ + { "The maximum level of nested containers has been reached" }, /* ERR_SEC_INVALID_CONTAINERS_NEST_LEVEL */ + { "The maximum number of stored secrets has been reached" }, /* ERR_SEC_INVALID_TOO_MANY_SECRETS */ + { "The secret payload size is too large" }, /* ERR_SEC_PAYLOAD_SIZE_IS_TOO_LARGE */ + { "No authentication method available" }, /* ERR_NO_AUTH_METHOD_AVAILABLE */ + { "Smartcard authentication not supported" }, /* ERR_SC_AUTH_NOT_SUPPORTED */ + { "Malformed input KCM packet" }, /* ERR_KCM_MALFORMED_IN_PKT */ + { "KCM operation not implemented" }, /* ERR_KCM_OP_NOT_IMPLEMENTED */ + { "End of credential cache reached" }, /* ERR_KCM_CC_END */ + { "Credential cache name not allowed" }, /* ERR_KCM_WRONG_CCNAME_FORMAT */ + { "Cannot encode a JSON object to string" }, /* ERR_JSON_ENCODING */ + { "Cannot decode a JSON object from string" }, /* ERR_JSON_DECODING */ + { "Invalid certificate provided" }, /* ERR_INVALID_CERT */ + { "Unable to initialize SSL" }, /* ERR_SSL_FAILURE */ + { "Unable to verify peer" }, /* ERR_UNABLE_TO_VERIFY_PEER */ + { "Unable to resolve host" }, /* ERR_UNABLE_TO_RESOLVE_HOST */ + { "GetAccountDomain() not supported" }, /* ERR_GET_ACCT_DOM_NOT_SUPPORTED */ + { "Subid ranges are not supported by this provider" }, /* ERR_GET_ACCT_SUBID_RANGES_NOT_SUPPORTED */ + { "The last GetAccountDomain() result is still valid" }, /* ERR_GET_ACCT_DOM_CACHED */ + { "ID is outside the allowed range" }, /* ERR_ID_OUTSIDE_RANGE */ + { "Group ID is duplicated" }, /* ERR_GID_DUPLICATED */ + { "Multiple objects were found when only one was expected" }, /* ERR_MULTIPLE_ENTRIES */ + { "Unsupported range type" }, /* ERR_UNSUPPORTED_RANGE_TYPE */ + { "proxy_child terminated by a signal" }, /* ERR_PROXY_CHILD_SIGNAL */ + { "PAC check failed" }, /* ERR_CHECK_PAC_FAILED */ + + /* DBUS Errors */ + { "Connection was killed on demand" }, /* ERR_SBUS_KILL_CONNECTION */ + { "NULL string cannot be sent over D-Bus" }, /* ERR_SBUS_EMPTY_STRING */ + { "Maximum number of connections was reached" }, /* ERR_SBUS_CONNECTION_LIMIT */ + { "String contains invalid characters" }, /* ERR_SBUS_INVALID_STRING */ + { "Unexpected argument type provided" }, /* ERR_SBUS_INVALID_TYPE */ + { "Unknown service" }, /* ERR_SBUS_UNKNOWN_SERVICE */ + { "Unknown interface" }, /* ERR_SBUS_UNKNOWN_INTERFACE */ + { "Unknown property" }, /* ERR_SBUS_UNKNOWN_PROPERTY */ + { "Unknown bus owner" }, /* ERR_SBUS_UNKNOWN_OWNER */ + { "No reply was received" }, /* ERR_SBUS_NO_REPLY */ + + /* ini parsing errors */ + { "Failed to open configuration" }, /* ERR_INI_OPEN_FAILED */ + { "File ownership and permissions check failed" }, /* ERR_INI_INVALID_PERMISSION */ + { "Error while parsing configuration file" }, /* ERR_INI_PARSE_FAILED */ + { "Failed to add configuration snippets" }, /* ERR_INI_ADD_SNIPPETS_FAILED */ + + { "TLS handshake was interrupted"}, /* ERR_TLS_HANDSHAKE_INTERRUPTED */ + + { "Certificate authority file not found"}, /* ERR_CA_DB_NOT_FOUND */ + + { "ERR_LAST" } /* ERR_LAST */ +}; + + +const char *sss_strerror(errno_t error) +{ + if (IS_SSSD_ERROR(error)) { + return error_to_str[SSSD_ERR_IDX(error)].msg; + } + + return strerror(error); +} + +/* TODO: make a more complete and precise mapping */ +errno_t sss_ldb_error_to_errno(int ldberr) +{ + switch (ldberr) { + case LDB_SUCCESS: + return EOK; + case LDB_ERR_OPERATIONS_ERROR: + return EIO; + case LDB_ERR_NO_SUCH_OBJECT: + case LDB_ERR_NO_SUCH_ATTRIBUTE: + return ENOENT; + case LDB_ERR_BUSY: + return EBUSY; + case LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS: + case LDB_ERR_ENTRY_ALREADY_EXISTS: + return EEXIST; + case LDB_ERR_INVALID_ATTRIBUTE_SYNTAX: + return EINVAL; + default: + DEBUG(SSSDBG_MINOR_FAILURE, + "LDB returned unexpected error: [%i]\n", + ldberr); + return EFAULT; + } +} diff --git a/src/util/util_errors.h b/src/util/util_errors.h new file mode 100644 index 0000000..c3558a2 --- /dev/null +++ b/src/util/util_errors.h @@ -0,0 +1,204 @@ +/* + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + Authors: + Simo Sorce <ssorce@redhat.com> +*/ + +#ifndef __SSSD_UTIL_ERRORS_H__ +#define __SSSD_UTIL_ERRORS_H__ + +#ifndef HAVE_ERRNO_T +#define HAVE_ERRNO_T +typedef int errno_t; +#endif + +/* + * We define a specific number space so that we do not overlap with other + * generic errors returned by various libraries. This will make it easy + * to have functions that double check that what was returned was an SSSD + * specific error where it matters. For example we may want to ensure some + * particularly sensitive paths only return SSSD-specific errors as that + * will ensure all error conditions have been explicitly dealt with, + * and are not the result of assigning the wrong return result. + * + * Basic system errno errors can still be used, but when an error condition + * does not properly map to a system error we should use an SSSD specific one + */ + +#define ERR_BASE 0x555D0000 +#define ERR_MASK 0x0000FFFF + +/* never use ERR_INVALID, it is used for catching and returning + * information on invalid error numbers */ +/* never use ERR_LAST, this represents the maximum error value available + * and is used to validate error codes */ +enum sssd_errors { + ERR_INVALID = ERR_BASE + 0, + ERR_INTERNAL, + ERR_SSSD_RUNNING, + ERR_SSSD_NOT_RUNNING, + ERR_OFFLINE, + ERR_TERMINATED, + ERR_INVALID_DATA_TYPE, + ERR_MISSING_DP_TARGET, + ERR_ACCOUNT_UNKNOWN, + ERR_KRB5_PRINCIPAL_NOT_FOUND, + ERR_INVALID_CRED_TYPE, + ERR_NO_CREDS, + ERR_CREDS_EXPIRED, + ERR_CREDS_EXPIRED_CCACHE, + ERR_CREDS_INVALID, + ERR_NO_CACHED_CREDS, + ERR_NO_MATCHING_CREDS, + ERR_CACHED_CREDS_EXPIRED, + ERR_AUTH_DENIED, + ERR_AUTH_FAILED, + ERR_CHPASS_DENIED, + ERR_CHPASS_FAILED, + ERR_NETWORK_IO, + ERR_ACCOUNT_EXPIRED, + ERR_PASSWORD_EXPIRED, + ERR_PASSWORD_EXPIRED_REJECT, + ERR_PASSWORD_EXPIRED_WARN, + ERR_PASSWORD_EXPIRED_RENEW, + ERR_ACCESS_DENIED, + ERR_SRV_NOT_FOUND, + ERR_SRV_LOOKUP_ERROR, + ERR_SRV_DUPLICATES, + ERR_DYNDNS_FAILED, + ERR_DYNDNS_TIMEOUT, + ERR_DYNDNS_OFFLINE, + ERR_INPUT_PARSE, + ERR_NOT_FOUND, + ERR_DOMAIN_NOT_FOUND, + ERR_NO_DOMAIN_ENABLED, + ERR_INVALID_FILTER, + ERR_NO_POSIX, + ERR_DUP_EXTRA_ATTR, + ERR_INVALID_EXTRA_ATTR, + ERR_SBUS_GET_SENDER_ERROR, + ERR_SBUS_NO_SENDER, + ERR_SBUS_INVALID_PATH, + ERR_NO_SIDS, + ERR_SBUS_NOSUP, + ERR_NO_SYSBUS, + ERR_REFERRAL, + ERR_SELINUX_CONTEXT, + ERR_SELINUX_NOT_MANAGED, + ERR_SELINUX_USER_NOT_FOUND, + ERR_REGEX_NOMATCH, + ERR_TIMESPEC_NOT_SUPPORTED, + ERR_INVALID_CONFIG, + ERR_MALFORMED_ENTRY, + ERR_UNEXPECTED_ENTRY_TYPE, + ERR_SIMPLE_GROUPS_MISSING, + ERR_HOMEDIR_IS_NULL, + ERR_TRUST_NOT_SUPPORTED, + ERR_IPA_GETKEYTAB_FAILED, + ERR_TRUST_FOREST_UNKNOWN, + ERR_P11_CHILD, + ERR_P11_CHILD_TIMEOUT, + ERR_P11_PIN_LOCKED, + ERR_PASSKEY_CHILD, + ERR_PASSKEY_CHILD_TIMEOUT, + ERR_ADDR_FAMILY_NOT_SUPPORTED, + ERR_SBUS_SENDER_BUS, + ERR_SUBDOM_INACTIVE, + ERR_ACCOUNT_LOCKED, + ERR_RENEWAL_CHILD, + ERR_SBUS_REQUEST_HANDLED, + ERR_SYSDB_VERSION_TOO_OLD, + ERR_SYSDB_VERSION_TOO_NEW, + ERR_NO_TS, + ERR_TS_CACHE_MISS, + ERR_DEREF_THRESHOLD, + ERR_NON_SSSD_USER, + ERR_WRONG_NAME_FORMAT, + ERR_SEC_INVALID_CONTAINERS_NEST_LEVEL, + ERR_SEC_INVALID_TOO_MANY_SECRETS, + ERR_SEC_PAYLOAD_SIZE_IS_TOO_LARGE, + ERR_NO_AUTH_METHOD_AVAILABLE, + ERR_SC_AUTH_NOT_SUPPORTED, + ERR_KCM_MALFORMED_IN_PKT, + ERR_KCM_OP_NOT_IMPLEMENTED, + ERR_KCM_CC_END, + ERR_KCM_WRONG_CCNAME_FORMAT, + ERR_JSON_ENCODING, + ERR_JSON_DECODING, + ERR_INVALID_CERT, + ERR_SSL_FAILURE, + ERR_UNABLE_TO_VERIFY_PEER, + ERR_UNABLE_TO_RESOLVE_HOST, + ERR_GET_ACCT_DOM_NOT_SUPPORTED, + ERR_GET_ACCT_SUBID_RANGES_NOT_SUPPORTED, + ERR_GET_ACCT_DOM_CACHED, + ERR_ID_OUTSIDE_RANGE, + ERR_GID_DUPLICATED, + ERR_MULTIPLE_ENTRIES, + ERR_UNSUPPORTED_RANGE_TYPE, + ERR_PROXY_CHILD_SIGNAL, + ERR_CHECK_PAC_FAILED, + + /* DBUS Errors */ + ERR_SBUS_KILL_CONNECTION, + ERR_SBUS_EMPTY_STRING, + ERR_SBUS_CONNECTION_LIMIT, + ERR_SBUS_INVALID_STRING, + ERR_SBUS_INVALID_TYPE, + ERR_SBUS_UNKNOWN_SERVICE, + ERR_SBUS_UNKNOWN_INTERFACE, + ERR_SBUS_UNKNOWN_PROPERTY, + ERR_SBUS_UNKNOWN_OWNER, + ERR_SBUS_NO_REPLY, + + /* ini parsing errors */ + ERR_INI_OPEN_FAILED, + ERR_INI_INVALID_PERMISSION, + ERR_INI_PARSE_FAILED, + ERR_INI_ADD_SNIPPETS_FAILED, + + ERR_TLS_HANDSHAKE_INTERRUPTED, + + ERR_CA_DB_NOT_FOUND, + + ERR_LAST /* ALWAYS LAST */ +}; + +#define SSSD_ERR_BASE(err) ((err) & ~ERR_MASK) +#define SSSD_ERR_IDX(err) ((err) & ERR_MASK) +#define IS_SSSD_ERROR(err) \ + (((err) > 0) && (SSSD_ERR_BASE(err) == ERR_BASE) && ((err) <= ERR_LAST)) + +#define ERR_OK 0 +/* Backwards compat */ +#ifndef EOK +#define EOK ERR_OK +#endif + +/** + * @brief return a string describing the error number like strerror() + * + * @param error An errno_t number, can be an SSSD error or a system error + * + * @return A statically allocated string. + */ +const char *sss_strerror(errno_t error); + +/* return ldb error converted to an errno */ +errno_t sss_ldb_error_to_errno(int ldberr); + +#endif /* __SSSD_UTIL_ERRORS_H__ */ diff --git a/src/util/util_ext.c b/src/util/util_ext.c new file mode 100644 index 0000000..c9839a9 --- /dev/null +++ b/src/util/util_ext.c @@ -0,0 +1,389 @@ +/* + SSSD helper calls - can be used by libraries for external use as well + + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2017 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <talloc.h> +#include <stdbool.h> +#include <errno.h> +#include <ctype.h> +#include <string.h> +#include <strings.h> + +#define EOK 0 + +#ifndef HAVE_ERRNO_T +#define HAVE_ERRNO_T +typedef int errno_t; +#endif + +int split_on_separator(TALLOC_CTX *mem_ctx, const char *str, + const char sep, bool trim, bool skip_empty, + char ***_list, int *size) +{ + int ret; + const char *substr_end = str; + const char *substr_begin = str; + const char *sep_pos = NULL; + size_t substr_len; + char **list = NULL; + int num_strings = 0; + TALLOC_CTX *tmp_ctx = NULL; + + if (str == NULL || *str == '\0' || _list == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + do { + substr_len = 0; + + /* If this is not the first substring, then move from the separator. */ + if (sep_pos != NULL) { + substr_end = sep_pos + 1; + substr_begin = sep_pos + 1; + } + + /* Find end of the first substring */ + while (*substr_end != sep && *substr_end != '\0') { + substr_end++; + substr_len++; + } + + sep_pos = substr_end; + + if (trim) { + /* Trim leading whitespace */ + while (isspace(*substr_begin) && substr_begin < substr_end) { + substr_begin++; + substr_len--; + } + + /* Trim trailing whitespace */ + while (substr_end - 1 > substr_begin && isspace(*(substr_end-1))) { + substr_end--; + substr_len--; + } + } + + /* Copy the substring to the output list of strings */ + if (skip_empty == false || substr_len > 0) { + list = talloc_realloc(tmp_ctx, list, char*, num_strings + 2); + if (list == NULL) { + ret = ENOMEM; + goto done; + } + + /* empty string is stored for substr_len == 0 */ + list[num_strings] = talloc_strndup(list, substr_begin, substr_len); + if (list[num_strings] == NULL) { + ret = ENOMEM; + goto done; + } + num_strings++; + } + + } while (*sep_pos != '\0'); + + if (list == NULL) { + /* No allocations were done, make space for the NULL */ + list = talloc(tmp_ctx, char *); + if (list == NULL) { + ret = ENOMEM; + goto done; + } + } + list[num_strings] = NULL; + + if (size) { + *size = num_strings; + } + + *_list = talloc_steal(mem_ctx, list); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +bool string_in_list(const char *string, char **list, bool case_sensitive) +{ + size_t c; + int(*compare)(const char *s1, const char *s2); + + if (string == NULL || list == NULL || *list == NULL) { + return false; + } + + compare = case_sensitive ? strcmp : strcasecmp; + + for (c = 0; list[c] != NULL; c++) { + if (compare(string, list[c]) == 0) { + return true; + } + } + + return false; +} + +bool string_in_list_size(const char *string, const char **list, size_t size, + bool case_sensitive) +{ + size_t c; + int(*compare)(const char *s1, const char *s2); + + if (string == NULL || list == NULL || size == 0) { + return false; + } + + compare = case_sensitive ? strcmp : strcasecmp; + + for (c = 0; c < size; c++) { + if (compare(string, list[c]) == 0) { + return true; + } + } + + return false; +} + +errno_t sss_filter_sanitize_ex(TALLOC_CTX *mem_ctx, + const char *input, + char **sanitized, + const char *ignore) +{ + char *output; + size_t i = 0; + size_t j = 0; + char *allowed; + + /* Assume the worst-case. We'll resize it later, once */ + output = talloc_array(mem_ctx, char, strlen(input) * 3 + 1); + if (!output) { + return ENOMEM; + } + + while (input[i]) { + /* Even though this character might have a special meaning, if it's + * explicitly allowed, just copy it and move on + */ + if (ignore == NULL) { + allowed = NULL; + } else { + allowed = strchr(ignore, input[i]); + } + if (allowed) { + output[j++] = input[i++]; + continue; + } + + switch(input[i]) { + case '\t': + output[j++] = '\\'; + output[j++] = '0'; + output[j++] = '9'; + break; + case ' ': + output[j++] = '\\'; + output[j++] = '2'; + output[j++] = '0'; + break; + case '*': + output[j++] = '\\'; + output[j++] = '2'; + output[j++] = 'a'; + break; + case '(': + output[j++] = '\\'; + output[j++] = '2'; + output[j++] = '8'; + break; + case ')': + output[j++] = '\\'; + output[j++] = '2'; + output[j++] = '9'; + break; + case '\\': + output[j++] = '\\'; + output[j++] = '5'; + output[j++] = 'c'; + break; + case '\r': + output[j++] = '\\'; + output[j++] = '0'; + output[j++] = 'd'; + break; + case '\n': + output[j++] = '\\'; + output[j++] = '0'; + output[j++] = 'a'; + break; + default: + output[j++] = input[i]; + } + + i++; + } + output[j] = '\0'; + *sanitized = talloc_realloc(mem_ctx, output, char, j+1); + if (!*sanitized) { + talloc_free(output); + return ENOMEM; + } + + return EOK; +} + +errno_t sss_filter_sanitize(TALLOC_CTX *mem_ctx, + const char *input, + char **sanitized) +{ + return sss_filter_sanitize_ex(mem_ctx, input, sanitized, NULL); +} + +/* There is similar function ldap_dn_normalize in openldap. + * To avoid dependecies across project we have this own func. + * Also ldb can do this but doesn't handle all the cases + */ +static errno_t sss_trim_dn(TALLOC_CTX *mem_ctx, + const char *input, + char **trimmed) +{ + int i = 0; + int o = 0; + int s; + char *output; + enum sss_trim_dn_state { + SSS_TRIM_DN_STATE_READING_NAME, + SSS_TRIM_DN_STATE_READING_VALUE + } state = SSS_TRIM_DN_STATE_READING_NAME; + + *trimmed = NULL; + + output = talloc_array(mem_ctx, char, strlen(input) + 1); + if (!output) { + return ENOMEM; + } + + /* skip leading spaces */ + while(isspace(input[i])) { + ++i; + } + + while(input[i] != '\0') { + if (!isspace(input[i])) { + switch (input[i]) { + case '=': + output[o++] = input[i++]; + if (state == SSS_TRIM_DN_STATE_READING_NAME) { + while (isspace(input[i])) { + ++i; + } + state = SSS_TRIM_DN_STATE_READING_VALUE; + } + break; + case ',': + output[o++] = input[i++]; + if (state == SSS_TRIM_DN_STATE_READING_VALUE) { + while (isspace(input[i])) { + ++i; + } + state = SSS_TRIM_DN_STATE_READING_NAME; + } + break; + case '\\': + output[o++] = input[i++]; + if (input[i] != '\0') { + output[o++] = input[i++]; + } + break; + default: + if (input[i] != '\0') { + output[o++] = input[i++]; + } + break; + } + + continue; + } + + /* non escaped space found */ + s = 1; + while (isspace(input[i + s])) { + ++s; + } + + switch (state) { + case SSS_TRIM_DN_STATE_READING_NAME: + if (input[i + s] != '=') { + /* this is not trailing space - should not be removed */ + while (isspace(input[i])) { + output[o++] = input[i++]; + } + } else { + i += s; + } + break; + case SSS_TRIM_DN_STATE_READING_VALUE: + if (input[i + s] != ',') { + /* this is not trailing space - should not be removed */ + while (isspace(input[i])) { + output[o++] = input[i++]; + } + } else { + i += s; + } + break; + } + } + + output[o--] = '\0'; + + /* trim trailing space */ + while (o >= 0 && isspace(output[o])) { + output[o--] = '\0'; + } + + *trimmed = output; + return EOK; +} + +errno_t sss_filter_sanitize_dn(TALLOC_CTX *mem_ctx, + const char *input, + char **sanitized) +{ + errno_t ret; + char *trimmed_dn = NULL; + + ret = sss_trim_dn(mem_ctx, input, &trimmed_dn); + if (ret != EOK) { + goto done; + } + + ret = sss_filter_sanitize_ex(mem_ctx, trimmed_dn, sanitized, NULL); + + done: + talloc_free(trimmed_dn); + return ret; +} diff --git a/src/util/util_lock.c b/src/util/util_lock.c new file mode 100644 index 0000000..9f28858 --- /dev/null +++ b/src/util/util_lock.c @@ -0,0 +1,91 @@ +/* + SSSD + + util_lock.c + + Authors: + Michal Zidek <mzidek@redhat.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include "util/util.h" + +errno_t sss_br_lock_file(int fd, size_t start, size_t len, + int num_tries, useconds_t wait) +{ + int ret = EAGAIN; + struct flock lock; + int retries_left; + + if (num_tries <= 0) { + return EINVAL; + } + + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = start; + lock.l_len = len; + lock.l_pid = 0; + + for (retries_left = num_tries; retries_left > 0; retries_left--) { + ret = fcntl(fd, F_SETLK, &lock); + if (ret == -1) { + ret = errno; + if (ret == EACCES || ret == EAGAIN || ret == EINTR) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to lock file. Retries left: %d\n", + retries_left - 1); + + if ((ret == EACCES || ret == EAGAIN) && (retries_left <= 1)) { + /* File is locked by someone else. Return EACCESS + * if this is the last try. */ + return EACCES; + } + + if (retries_left - 1 > 0) { + ret = usleep(wait); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "usleep() failed with %d -> ignoring\n", ret); + } + } + } else { + /* Error occurred */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to lock file.\n"); + return ret; + } + } else if (ret == 0) { + /* File successfully locked */ + break; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unexpected fcntl() return code: %d\n", ret); + } + } + if (retries_left == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lock file.\n"); + return ret; + } + + return EOK; +} diff --git a/src/util/util_preauth.c b/src/util/util_preauth.c new file mode 100644 index 0000000..a2b0ac6 --- /dev/null +++ b/src/util/util_preauth.c @@ -0,0 +1,86 @@ +/* + SSSD + + Calls to manage the preauth indicator file + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2018 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "util/util.h" +#include "sss_client/sss_cli.h" + +static void cleanup_preauth_indicator(void) +{ + int ret; + + ret = unlink(PAM_PREAUTH_INDICATOR); + if (ret != EOK && errno != ENOENT) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "Failed to remove preauth indicator file [%s] %d [%s].\n", + PAM_PREAUTH_INDICATOR, ret, sss_strerror(ret)); + } +} + +errno_t create_preauth_indicator(void) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + int fd; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + fd = open(PAM_PREAUTH_INDICATOR, O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW, + 0644); + if (fd < 0) { + if (errno != EEXIST) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to create preauth indicator file [%s].\n", + PAM_PREAUTH_INDICATOR); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Preauth indicator file [%s] already exists. Continuing.\n", + PAM_PREAUTH_INDICATOR); + } else { + close(fd); + } + + ret = atexit(cleanup_preauth_indicator); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "atexit failed. Continuing.\n"); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} diff --git a/src/util/util_sss_idmap.c b/src/util/util_sss_idmap.c new file mode 100644 index 0000000..4ce4250 --- /dev/null +++ b/src/util/util_sss_idmap.c @@ -0,0 +1,32 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2013 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <talloc.h> +#include "util/util_sss_idmap.h" + +void *sss_idmap_talloc(size_t size, void *pvt) +{ + return talloc_size(pvt, size); +} + +void sss_idmap_talloc_free(void *ptr, void *pvt) +{ + talloc_free(ptr); +} diff --git a/src/util/util_sss_idmap.h b/src/util/util_sss_idmap.h new file mode 100644 index 0000000..bde4727 --- /dev/null +++ b/src/util/util_sss_idmap.h @@ -0,0 +1,28 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2013 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __UTIL_SSS_IDMAP_H__ +#define __UTIL_SSS_IDMAP_H__ + +void *sss_idmap_talloc(size_t size, void *pvt); + +void sss_idmap_talloc_free(void *ptr, void *pvt); + +#endif /* __UTIL_SSS_IDMAP_H__ */ diff --git a/src/util/util_watchdog.c b/src/util/util_watchdog.c new file mode 100644 index 0000000..abafd94 --- /dev/null +++ b/src/util/util_watchdog.c @@ -0,0 +1,290 @@ +/* + SSSD + + Timer Watchdog routines + + Copyright (C) Simo Sorce 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <signal.h> + +#include "util/util.h" + +#define WATCHDOG_DEF_INTERVAL 10 +#define WATCHDOG_MAX_TICKS 3 +#define DEFAULT_BUFFER_SIZE 4096 + +/* this is intentionally a global variable */ +struct watchdog_ctx { + timer_t timerid; + struct timeval interval; + struct tevent_timer *te; + volatile int ticks; + + /* To detect time shift. */ + struct tevent_context *ev; + int input_interval; + time_t timestamp; + struct tevent_fd *tfd; + int pipefd[2]; + bool armed; /* if 'true' ticks counter will not be reset */ +} watchdog_ctx; + +static void watchdog_detect_timeshift(void) +{ + time_t prev_time; + time_t cur_time; + + prev_time = watchdog_ctx.timestamp; + cur_time = watchdog_ctx.timestamp = time(NULL); + if (cur_time < prev_time) { + /* Time shift detected. We need to restart watchdog. */ + if (write(watchdog_ctx.pipefd[1], "1", 1) != 1) { + if (getpid() == getpgrp()) { + kill(-getpgrp(), SIGTERM); + } + _exit(1); + } + } +} + +/* the watchdog is purposefully *not* handled by the tevent + * signal handler as it is meant to check if the daemon is + * still processing the event queue itself. A stuck process + * may not handle the event queue at all and thus not handle + * signals either */ +static void watchdog_handler(int sig) +{ + + watchdog_detect_timeshift(); + + /* if a pre-defined number of ticks passed by kills itself */ + if (__sync_add_and_fetch(&watchdog_ctx.ticks, 1) >= WATCHDOG_MAX_TICKS) { + if (getpid() == getpgrp()) { + kill(-getpgrp(), SIGTERM); + } + _exit(SSS_WATCHDOG_EXIT_CODE); + } +} + +static void watchdog_reset(void) +{ + __sync_and_and_fetch(&watchdog_ctx.ticks, 0); +} + +static void watchdog_event_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + if (!watchdog_ctx.armed) { + /* first thing reset the watchdog ticks */ + watchdog_reset(); + } else { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Watchdog armed, process might be terminated soon.\n"); + } + + /* then set a new watchodg event */ + watchdog_ctx.te = tevent_add_timer(ev, ev, + tevent_timeval_current_ofs(watchdog_ctx.interval.tv_sec, 0), + watchdog_event_handler, NULL); + /* if the function fails the watchdog will kill the + * process soon enough, so we just warn */ + if (!watchdog_ctx.te) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to create a watchdog timer event!\n"); + } +} + +static errno_t watchdog_fd_recv_data(int fd) +{ + ssize_t len; + char buffer[DEFAULT_BUFFER_SIZE]; + errno_t ret; + + errno = 0; + len = read(fd, buffer, DEFAULT_BUFFER_SIZE); + if (len == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + return EAGAIN; + } else { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "write failed [%d]: %s\n", ret, strerror(ret)); + return ret; + } + } + + return EOK; +} + +static void watchdog_fd_read_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *data) +{ + errno_t ret; + + ret = watchdog_fd_recv_data(watchdog_ctx.pipefd[0]); + switch(ret) { + case EAGAIN: + DEBUG(SSSDBG_TRACE_ALL, + "Interrupted before any data could be read, retry later.\n"); + return; + case EOK: + /* all fine */ + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to receive data [%d]: %s. " + "orderly_shutdown() will be called.\n", ret, strerror(ret)); + orderly_shutdown(1); + } + + DEBUG(SSSDBG_IMPORTANT_INFO, "Time shift detected, " + "restarting watchdog!\n"); + teardown_watchdog(); + ret = setup_watchdog(watchdog_ctx.ev, watchdog_ctx.input_interval); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to restart watchdog " + "[%d]: %s\n", ret, sss_strerror(ret)); + orderly_shutdown(1); + } + if (strncmp(debug_prg_name, "be[", sizeof("be[") - 1) == 0) { + kill(getpid(), SIGUSR2); + DEBUG(SSSDBG_IMPORTANT_INFO, "SIGUSR2 sent to %s\n", debug_prg_name); + } +} + +int setup_watchdog(struct tevent_context *ev, int interval) +{ + struct sigevent sev; + struct itimerspec its; + struct tevent_fd *tfd; + int signum = SIGRTMIN; + int ret; + + memset(&sev, 0, sizeof(sev)); + CatchSignal(signum, watchdog_handler); + + sev.sigev_notify = SIGEV_SIGNAL; + sev.sigev_signo = signum; + sev.sigev_value.sival_ptr = &watchdog_ctx.timerid; + errno = 0; + ret = timer_create(CLOCK_MONOTONIC, &sev, &watchdog_ctx.timerid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to create watchdog timer (%d) [%s]\n", + ret, strerror(ret)); + return ret; + } + + if (interval == 0) { + interval = WATCHDOG_DEF_INTERVAL; + } + watchdog_ctx.interval.tv_sec = interval; + watchdog_ctx.interval.tv_usec = 0; + + watchdog_ctx.ev = ev; + watchdog_ctx.input_interval = interval; + watchdog_ctx.timestamp = time(NULL); + watchdog_ctx.armed = false; + + ret = pipe(watchdog_ctx.pipefd); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "pipe failed [%d] [%s].\n", ret, strerror(ret)); + return ret; + } + + sss_fd_nonblocking(watchdog_ctx.pipefd[0]); + sss_fd_nonblocking(watchdog_ctx.pipefd[1]); + + tfd = tevent_add_fd(ev, (TALLOC_CTX *)ev, watchdog_ctx.pipefd[0], + TEVENT_FD_READ, watchdog_fd_read_handler, NULL); + watchdog_ctx.tfd = tfd; + + /* Start the timer */ + /* we give 1 second head start to the watchdog event */ + its.it_value.tv_sec = interval + 1; + its.it_value.tv_nsec = 0; + its.it_interval.tv_sec = interval; + its.it_interval.tv_nsec = 0; + errno = 0; + ret = timer_settime(watchdog_ctx.timerid, 0, &its, NULL); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to create watchdog timer (%d) [%s]\n", + ret, strerror(ret)); + return ret; + } + + /* Add the watchdog event and make it fire as fast as the timer */ + watchdog_event_handler(ev, NULL, tevent_timeval_zero(), NULL); + + return EOK; +} + +void teardown_watchdog(void) +{ + int ret; + + /* Disarm the timer */ + errno = 0; + ret = timer_delete(watchdog_ctx.timerid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to destroy watchdog timer (%d) [%s]\n", + ret, strerror(ret)); + } + + /* Free the tevent_fd */ + talloc_zfree(watchdog_ctx.tfd); + + /* Close the pipefds */ + PIPE_FD_CLOSE(watchdog_ctx.pipefd[0]); + PIPE_FD_CLOSE(watchdog_ctx.pipefd[1]); + + /* and kill the watchdog event */ + talloc_free(watchdog_ctx.te); +} + +int get_watchdog_ticks(void) +{ + return __sync_add_and_fetch(&watchdog_ctx.ticks, 0); +} + +void arm_watchdog(void) +{ + if (watchdog_ctx.armed) { + DEBUG(SSSDBG_CRIT_FAILURE, + "arm_watchdog() is called although the watchdog is already armed. " + "This indicates a programming error and should be avoided because " + "it will most probably not work as expected.\n"); + } + + watchdog_ctx.armed = true; +} + +void disarm_watchdog(void) +{ + watchdog_ctx.armed = false; +} diff --git a/src/util/well_known_sids.c b/src/util/well_known_sids.c new file mode 100644 index 0000000..03fc261 --- /dev/null +++ b/src/util/well_known_sids.c @@ -0,0 +1,478 @@ +/* + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2013 Red Hat + + Translate well-known SIDs to domains and names + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <ctype.h> +#include "util/util.h" +#include "util/strtonum.h" + +/* Well-Known SIDs are documented in section 2.4.2.4 "Well-Known SID + * Structures" of the "[MS-DTYP]: Windows Data Types" document. */ + +#define DOM_SID_PREFIX "S-1-5-21-" +#define DOM_SID_PREFIX_LEN (sizeof(DOM_SID_PREFIX) - 1) + +#define BUILTIN_SID_PREFIX "S-1-5-32-" +#define BUILTIN_SID_PREFIX_LEN (sizeof(BUILTIN_SID_PREFIX) - 1) +#define BUILTIN_DOM_NAME "BUILTIN" + +#define NT_SID_PREFIX "S-1-5-" +#define NT_SID_PREFIX_LEN (sizeof(NT_SID_PREFIX) - 1) +#define NT_DOM_NAME "NT AUTHORITY" + +#define NT_AUTH_SID_PREFIX "S-1-5-64-" +#define NT_AUTH_SID_PREFIX_LEN (sizeof(NT_AUTH_SID_PREFIX) - 1) +#define NT_AUTH_DOM_NAME NT_DOM_NAME + +#define NT_THIS_SID_PREFIX "S-1-5-65-" +#define NT_THIS_SID_PREFIX_LEN (sizeof(NT_THIS_SID_PREFIX) - 1) +#define NT_THIS_DOM_NAME NT_DOM_NAME + +#define SPECIAL_SID_PREFIX "S-1-" +#define SPECIAL_SID_PREFIX_LEN (sizeof(SPECIAL_SID_PREFIX) - 1) +#define NULL_DOM_NAME "NULL AUTHORITY" +#define WORLD_DOM_NAME "WORLD AUTHORITY" +#define LOCAL_DOM_NAME "LOCAL AUTHORITY" +#define CREATOR_DOM_NAME "CREATOR AUTHORITY" +#define MANDATORY_DOM_NAME "MANDATORY LABEL AUTHORITY" +#define AUTHENTICATION_DOM_NAME "AUTHENTICATION AUTHORITY" + +#define NT_MAP_ENTRY(rid, name) {rid, NT_SID_PREFIX #rid, name} +#define NT_AUTH_MAP_ENTRY(rid, name) {rid, NT_AUTH_SID_PREFIX #rid, name} +#define NT_THIS_MAP_ENTRY(rid, name) {rid, NT_THIS_SID_PREFIX #rid, name} +#define BUILTIN_MAP_ENTRY(rid, name) {rid, BUILTIN_SID_PREFIX #rid, name} +#define SPECIAL_MAP_ENTRY(id_auth, rid, dom, name) \ + {id_auth, rid, SPECIAL_SID_PREFIX #id_auth "-" #rid, dom, name} + +struct rid_sid_name { + uint32_t rid; + const char *sid; + const char *name; +}; + +struct special_map { + uint32_t id_auth; + uint32_t rid; + const char *sid; + const char *dom; + const char *name; +}; + +struct rid_sid_name builtin_map[] = { + BUILTIN_MAP_ENTRY(544, "Administrators"), + BUILTIN_MAP_ENTRY(545, "Users"), + BUILTIN_MAP_ENTRY(546, "Guests"), + BUILTIN_MAP_ENTRY(547, "Power Users"), + BUILTIN_MAP_ENTRY(548, "Account Operators"), + BUILTIN_MAP_ENTRY(549, "Server Operators"), + BUILTIN_MAP_ENTRY(550, "Print Operators"), + BUILTIN_MAP_ENTRY(551, "Backup Operators"), + BUILTIN_MAP_ENTRY(552, "Replicator"), + BUILTIN_MAP_ENTRY(554, "Pre-Windows 2000 Compatible Access"), + BUILTIN_MAP_ENTRY(555, "Remote Desktop Users"), + BUILTIN_MAP_ENTRY(556, "Network Configuration Operators"), + BUILTIN_MAP_ENTRY(557, "Incoming Forest Trust Builders"), + BUILTIN_MAP_ENTRY(558, "Performance Monitor Users"), + BUILTIN_MAP_ENTRY(559, "Performance Log Users"), + BUILTIN_MAP_ENTRY(560, "Windows Authorization Access Group"), + BUILTIN_MAP_ENTRY(561, "Terminal Server License Servers"), + BUILTIN_MAP_ENTRY(562, "Distributed COM Users"), + BUILTIN_MAP_ENTRY(568, "IIS Users"), + BUILTIN_MAP_ENTRY(569, "Cryptographic Operators"), + BUILTIN_MAP_ENTRY(573, "Event Log Readers"), + BUILTIN_MAP_ENTRY(574, "Certificate Service DCOM Access"), + BUILTIN_MAP_ENTRY(575, "RDS Remote Access Servers"), + BUILTIN_MAP_ENTRY(576, "RDS Endpoint Servers"), + BUILTIN_MAP_ENTRY(577, "RDS Management Servers"), + BUILTIN_MAP_ENTRY(578, "Hyper-V Admins"), + BUILTIN_MAP_ENTRY(579, "Access Control Assistance OPS"), + BUILTIN_MAP_ENTRY(580, "Remote Management Users"), + + {UINT32_MAX, NULL, NULL} +}; + +#define LOGON_ID_NAME "LOGON ID" +#define LOGON_ID_MODEL "S-1-5-5-0-0" +struct rid_sid_name nt_map[] = { + NT_MAP_ENTRY(1, "DIALUP"), + NT_MAP_ENTRY(2, "NETWORK"), + NT_MAP_ENTRY(3, "BATCH"), + NT_MAP_ENTRY(4, "INTERACTIVE"), + /* NT_MAP_ENTRY(5-x-y, "LOGON ID"), special case treated separately */ + NT_MAP_ENTRY(6, "SERVICE"), + NT_MAP_ENTRY(7, "ANONYMOUS LOGON"), + NT_MAP_ENTRY(8, "PROXY"), + NT_MAP_ENTRY(9, "ENTERPRISE DOMAIN CONTROLLERS"), + NT_MAP_ENTRY(10, "SELF"), + NT_MAP_ENTRY(11, "Authenticated Users"), + NT_MAP_ENTRY(12, "RESTRICTED CODE"), + NT_MAP_ENTRY(13, "TERMINAL SERVER USER"), + NT_MAP_ENTRY(14, "REMOTE INTERACTIVE LOGON"), + NT_MAP_ENTRY(15, "This Organization"), + NT_MAP_ENTRY(17, "IUSR"), + NT_MAP_ENTRY(18, "LOCAL SYSTEM"), + NT_MAP_ENTRY(19, "LOCAL SERVICE"), + NT_MAP_ENTRY(20, "NETWORK SERVICE"), + NT_MAP_ENTRY(33, "WRITE_RESTRICTED_CODE"), + NT_MAP_ENTRY(80, "NT_SERVICE"), + NT_MAP_ENTRY(113, "LOCAL_ACCOUNT"), + NT_MAP_ENTRY(114, "LOCAL MEMBER OF ADMINISTRATORS GROUP"), + NT_MAP_ENTRY(1000, "OTHER_ORGANIZATIONS"), + + {UINT32_MAX, NULL, NULL} +}; + +struct rid_sid_name nt_auth_map[] = { + NT_AUTH_MAP_ENTRY(10, "NTLM AUTHENTICATION"), + NT_AUTH_MAP_ENTRY(14, "SCHANNEL AUTHENTICATION"), + NT_AUTH_MAP_ENTRY(21, "DIGEST AUTHENTICATION"), + + {UINT32_MAX, NULL, NULL} +}; + +struct rid_sid_name nt_this_map[] = { + NT_THIS_MAP_ENTRY(1, "THIS ORGANIZATION CERTIFICATE"), + + {UINT32_MAX, NULL, NULL} +}; + +struct special_map sp_map[] = { + SPECIAL_MAP_ENTRY(0, 0, NULL_DOM_NAME, "NULL SID"), + SPECIAL_MAP_ENTRY(1, 0, WORLD_DOM_NAME, "Everyone"), + SPECIAL_MAP_ENTRY(2, 0, LOCAL_DOM_NAME, "LOCAL"), + SPECIAL_MAP_ENTRY(2, 1, LOCAL_DOM_NAME, "CONSOLE LOGON"), + SPECIAL_MAP_ENTRY(3, 0, CREATOR_DOM_NAME, "CREATOR OWNER"), + SPECIAL_MAP_ENTRY(3, 1, CREATOR_DOM_NAME, "CREATOR GROUP"), + SPECIAL_MAP_ENTRY(3, 2, CREATOR_DOM_NAME, "CREATOR OWNER SERVER"), + SPECIAL_MAP_ENTRY(3, 3, CREATOR_DOM_NAME, "CREATOR GROUP SERVER"), + SPECIAL_MAP_ENTRY(3, 4, CREATOR_DOM_NAME, "OWNER RIGHTS"), + SPECIAL_MAP_ENTRY(16, 0, MANDATORY_DOM_NAME, "UNTRUSTED"), + SPECIAL_MAP_ENTRY(16, 4096, MANDATORY_DOM_NAME, "LOW"), + SPECIAL_MAP_ENTRY(16, 8192, MANDATORY_DOM_NAME, "MEDIUM"), + SPECIAL_MAP_ENTRY(16, 8448, MANDATORY_DOM_NAME, "MEDIUM_PLUS"), + SPECIAL_MAP_ENTRY(16, 12288, MANDATORY_DOM_NAME, "HIGH"), + SPECIAL_MAP_ENTRY(16, 16384, MANDATORY_DOM_NAME, "SYSTEM"), + SPECIAL_MAP_ENTRY(16, 20480, MANDATORY_DOM_NAME, "PROTECTED_PROCESS"), + SPECIAL_MAP_ENTRY(16, 28672, MANDATORY_DOM_NAME, "SECURE_PROCESS"), + SPECIAL_MAP_ENTRY(18, 1, AUTHENTICATION_DOM_NAME, "AUTHENTICATION ASSERTION"), + SPECIAL_MAP_ENTRY(18, 2, AUTHENTICATION_DOM_NAME, "SERVICE ASSERTION"), + SPECIAL_MAP_ENTRY(18, 3, AUTHENTICATION_DOM_NAME, "FRESH_PUBLIC_KEY_IDENTITY"), + SPECIAL_MAP_ENTRY(18, 4, AUTHENTICATION_DOM_NAME, "KEY_TRUST_IDENTITY"), + SPECIAL_MAP_ENTRY(18, 5, AUTHENTICATION_DOM_NAME, "KEY_PROPERTY_MFA"), + SPECIAL_MAP_ENTRY(18, 6, AUTHENTICATION_DOM_NAME, "KEY_PROPERTY_ATTESTATION"), + + {'\0', '\0', NULL, NULL, NULL} +}; + +static errno_t handle_special_sids(const char *sid, const char **dom, + const char **name) +{ + size_t c; + uint32_t rid; + uint32_t id_auth; + char *endptr; + + id_auth = strtouint32(sid + SPECIAL_SID_PREFIX_LEN, &endptr, 10); + if (errno != 0 || *endptr != '-' || *(endptr + 1) == '\0') { + return EINVAL; + } + + rid = strtouint32(endptr + 1, &endptr, 10); + if (errno != 0 || *endptr != '\0') { + return EINVAL; + } + + for (c = 0; sp_map[c].name != NULL; c++) { + if (id_auth == sp_map[c].id_auth && rid == sp_map[c].rid) { + *name = sp_map[c].name; + *dom = sp_map[c].dom; + return EOK; + } + } + + return EINVAL; +} + +static errno_t handle_special_names(const char *dom, const char *name, + const char **sid) +{ + size_t c; + + for (c = 0; sp_map[c].name != NULL; c++) { + if (strcmp(name, sp_map[c].name) == 0 + && strcmp(dom, sp_map[c].dom) == 0) { + *sid = sp_map[c].sid; + return EOK; + } + } + + return EINVAL; +} + +static errno_t handle_rid_to_name_map(const char *sid, size_t prefix_len, + struct rid_sid_name *map, + const char* dom_name, const char **dom, + const char **name) +{ + uint32_t rid; + char *endptr; + size_t c; + + rid = (uint32_t) strtouint32(sid + prefix_len, &endptr, 10); + if (errno != 0 || *endptr != '\0') { + return EINVAL; + } + + for (c = 0; map[c].name != NULL; c++) { + if (rid == map[c].rid) { + *name = map[c].name; + *dom = dom_name; + return EOK; + } + } + + return EINVAL; +} + +static errno_t handle_name_to_sid_map(const char *name, + struct rid_sid_name *map, + const char **sid) +{ + size_t c; + + for (c = 0; map[c].name != NULL; c++) { + if (strcmp(name, map[c].name) == 0) { + *sid = map[c].sid; + return EOK; + } + } + + return EINVAL; +} + +/* This function treats a particular case that does not fit in the normal table */ +static errno_t handle_nt_sids_particular_cases(const char *sid, + const char **dom, + const char **name) +{ + uint32_t rid; + char *endptr; + char *startptr; + int i; + + rid = (uint32_t) strtouint32(sid + NT_SID_PREFIX_LEN, &endptr, 10); + if (errno != 0) { + return EINVAL; + } + + if (rid == 5) { + /* S-1-5-5-x-y, we can ignore x and y, but they must be present. + * The x and y values for these SIDs are different for each logon + * session and are recycled when the operating system is restarted. */ + for (i = 1; i <= 2; i++) { + if (*endptr != '-') { + return EINVAL; + } + + startptr = endptr + 1; + (void) strtouint32(startptr, &endptr, 10); + if (errno != 0 || startptr == endptr) { + return EINVAL; + } + } + if (*endptr != '\0') { + return EINVAL; + } + + *dom = NT_DOM_NAME; + *name = LOGON_ID_NAME; + return EOK; + } + + return EINVAL; +} + +static errno_t handle_nt_particular_cases(const char *name, const char **sid) +{ + + + if (strcmp(name, LOGON_ID_NAME) == 0) { + /* We return a model because the SID includes variable data */ + *sid = LOGON_ID_MODEL; + return EOK; + } + + return EINVAL; +} + +static errno_t handle_nt_auth_sids(const char *sid, const char **dom, + const char **name) +{ + return handle_rid_to_name_map(sid, NT_AUTH_SID_PREFIX_LEN, nt_auth_map, + NT_AUTH_DOM_NAME, dom, name); +} + +static errno_t handle_nt_auth_names(const char *name, const char **sid) +{ + return handle_name_to_sid_map(name, nt_auth_map, sid); +} + +static errno_t handle_nt_this_sids(const char *sid, const char **dom, + const char **name) +{ + return handle_rid_to_name_map(sid, NT_THIS_SID_PREFIX_LEN, nt_this_map, + NT_THIS_DOM_NAME, dom, name); +} + +static errno_t handle_nt_this_names(const char *name, const char **sid) +{ + return handle_name_to_sid_map(name, nt_this_map, sid); +} + +static errno_t handle_nt_sids(const char *sid, const char **dom, + const char **name) +{ + errno_t ret; + + ret = handle_rid_to_name_map(sid, NT_SID_PREFIX_LEN, nt_map, NT_DOM_NAME, + dom, name); + if (ret == EINVAL) { + // These are a particular cases that need to be treated separately + ret = handle_nt_sids_particular_cases(sid, dom, name); + } + + return ret; +} + +static errno_t handle_nt_names(const char *name, const char **sid) +{ + return handle_name_to_sid_map(name, nt_map, sid); +} + +static errno_t handle_builtin_sids(const char *sid, const char **dom, + const char **name) +{ + return handle_rid_to_name_map(sid, BUILTIN_SID_PREFIX_LEN, builtin_map, + BUILTIN_DOM_NAME, dom, name); +} + +static errno_t handle_builtin_names(const char *name, const char **sid) +{ + return handle_name_to_sid_map(name, builtin_map, sid); +} + +errno_t well_known_sid_to_name(const char *sid, const char **dom, + const char **name) +{ + int ret; + + if (sid == NULL || dom == NULL || name == NULL) { + return EINVAL; + } + + if (strncmp(sid, DOM_SID_PREFIX, DOM_SID_PREFIX_LEN) == 0) { + ret = ENOENT; + } else if (strncmp(sid, BUILTIN_SID_PREFIX, BUILTIN_SID_PREFIX_LEN) == 0) { + ret = handle_builtin_sids(sid, dom, name); + if (ret != EOK) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "handle_builtin_sids failed for SID: %s\n", sid); + } + } else if (strncmp(sid, NT_AUTH_SID_PREFIX, NT_AUTH_SID_PREFIX_LEN) == 0) { + ret = handle_nt_auth_sids(sid, dom, name); + if (ret != EOK) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "handle_nt_auth_sids failed for SID: %s\n", sid); + } + } else if (strncmp(sid, NT_THIS_SID_PREFIX, NT_THIS_SID_PREFIX_LEN) == 0) { + ret = handle_nt_this_sids(sid, dom, name); + if (ret != EOK) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "handle_nt_this_sids failed for SID: %s\n", sid); + } + } else if (strncmp(sid, NT_SID_PREFIX, NT_SID_PREFIX_LEN) == 0) { + ret = handle_nt_sids(sid, dom, name); + if (ret != EOK) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "handle_nt_sids failed for SID: %s\n", sid); + } + } else if (strncmp(sid, SPECIAL_SID_PREFIX, SPECIAL_SID_PREFIX_LEN) == 0) { + ret = handle_special_sids(sid, dom, name); + if (ret != EOK) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "handle_special_sids failed for SID: %s\n", sid); + } + } else { + ret = EINVAL; + } + + return ret; +} + +errno_t name_to_well_known_sid(const char *dom, const char *name, + const char **sid) +{ + int ret; + + if (sid == NULL || dom == NULL || name == NULL) { + return EINVAL; + } + + if (strcmp(dom, NT_DOM_NAME) == 0) { + /* NT_DOM_NAME == NT_AUTH_DOM_NAME == NT_THIS_DOM_NAME */ + ret = handle_nt_names(name, sid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "handle_nt_name failed.\n"); + ret = handle_nt_auth_names(name, sid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "handle_nt_auth_names failed.\n"); + ret = handle_nt_this_names(name, sid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "handle_nt_this_names failed.\n"); + ret = handle_nt_particular_cases(name, sid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "handle_nt_particular_cases failed.\n"); + } + } + } + } + } else if (strcmp(dom, BUILTIN_DOM_NAME) == 0) { + ret = handle_builtin_names(name, sid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "handle_builtin_name failed.\n"); + } + } else if (strcmp(dom, NULL_DOM_NAME) == 0 + || strcmp(dom, WORLD_DOM_NAME) == 0 + || strcmp(dom, LOCAL_DOM_NAME) == 0 + || strcmp(dom, CREATOR_DOM_NAME) == 0 + || strcmp(dom, MANDATORY_DOM_NAME) == 0 + || strcmp(dom, AUTHENTICATION_DOM_NAME) == 0) { + ret = handle_special_names(dom, name, sid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "handle_special_name failed.\n"); + } + } else { + ret = ENOENT; + } + + return ret; +} |