diff options
Diffstat (limited to 'source4/nsswitch')
32 files changed, 13857 insertions, 0 deletions
diff --git a/source4/nsswitch/.cvsignore b/source4/nsswitch/.cvsignore new file mode 100644 index 0000000000..658d50a680 --- /dev/null +++ b/source4/nsswitch/.cvsignore @@ -0,0 +1,4 @@ +*.po +*.po32 +diffs +winbindd_proto.h diff --git a/source4/nsswitch/README b/source4/nsswitch/README new file mode 100644 index 0000000000..9f0c581df6 --- /dev/null +++ b/source4/nsswitch/README @@ -0,0 +1,13 @@ +This extension provides a "wins" module for NSS on glibc2/Linux. This +allows you to use a WINS entry in /etc/nsswitch.conf for hostname +resolution, allowing you to resolve netbios names via start unix +gethostbyname() calls. The end result is that you can use netbios +names as host names in unix apps. + +1) run configure +2) run "make nsswitch" +3) cp nsswitch/libnss_wins.so /lib/libnss_wins.so.2 +4) add a wins entry to the hosts line in /etc/nsswitch.conf +5) use it + +tridge@linuxcare.com diff --git a/source4/nsswitch/hp_nss_common.h b/source4/nsswitch/hp_nss_common.h new file mode 100644 index 0000000000..5bd5374182 --- /dev/null +++ b/source4/nsswitch/hp_nss_common.h @@ -0,0 +1,51 @@ +#ifndef _HP_NSS_COMMON_H +#define _HP_NSS_COMMON_H + +/* + Unix SMB/CIFS implementation. + + Donated by HP to enable Winbindd to build on HPUX 11.x. + Copyright (C) Jeremy Allison 2002. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifdef HAVE_SYNCH_H +#include <synch.h> +#endif +#ifdef HAVE_PTHREAD_H +#include <pthread.h> +#endif + +typedef enum { + NSS_SUCCESS, + NSS_NOTFOUND, + NSS_UNAVAIL, + NSS_TRYAGAIN +} nss_status_t; + +struct nss_backend; + +typedef nss_status_t (*nss_backend_op_t)(struct nss_backend *, void *args); + +struct nss_backend { + nss_backend_op_t *ops; + int n_ops; +}; +typedef struct nss_backend nss_backend_t; +typedef int nss_dbop_t; + +#endif /* _HP_NSS_COMMON_H */ diff --git a/source4/nsswitch/hp_nss_dbdefs.h b/source4/nsswitch/hp_nss_dbdefs.h new file mode 100644 index 0000000000..bd24772e33 --- /dev/null +++ b/source4/nsswitch/hp_nss_dbdefs.h @@ -0,0 +1,105 @@ +#ifndef _HP_NSS_DBDEFS_H +#define _HP_NSS_DBDEFS_H + +/* + Unix SMB/CIFS implementation. + + Donated by HP to enable Winbindd to build on HPUX 11.x. + Copyright (C) Jeremy Allison 2002. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include <errno.h> +#include <netdb.h> +#include <limits.h> + +#ifndef NSS_INCLUDE_UNSAFE +#define NSS_INCLUDE_UNSAFE 1 /* Build old, MT-unsafe interfaces, */ +#endif /* NSS_INCLUDE_UNSAFE */ + +enum nss_netgr_argn { + NSS_NETGR_MACHINE, + NSS_NETGR_USER, + NSS_NETGR_DOMAIN, + NSS_NETGR_N +}; + +enum nss_netgr_status { + NSS_NETGR_FOUND, + NSS_NETGR_NO, + NSS_NETGR_NOMEM +}; + +typedef unsigned nss_innetgr_argc; +typedef char **nss_innetgr_argv; + +struct nss_innetgr_1arg { + nss_innetgr_argc argc; + nss_innetgr_argv argv; +}; + +typedef struct { + void *result; /* "result" parameter to getXbyY_r() */ + char *buffer; /* "buffer" " " */ + int buflen; /* "buflen" " " */ +} nss_XbyY_buf_t; + +extern nss_XbyY_buf_t *_nss_XbyY_buf_alloc(int struct_size, int buffer_size); +extern void _nss_XbyY_buf_free(nss_XbyY_buf_t *); + +union nss_XbyY_key { + uid_t uid; + gid_t gid; + const char *name; + int number; + struct { + long net; + int type; + } netaddr; + struct { + const char *addr; + int len; + int type; + } hostaddr; + struct { + union { + const char *name; + int port; + } serv; + const char *proto; + } serv; + void *ether; +}; + +typedef struct nss_XbyY_args { + nss_XbyY_buf_t buf; + int stayopen; + /* + * Support for setXXXent(stayopen) + * Used only in hosts, protocols, + * networks, rpc, and services. + */ + int (*str2ent)(const char *instr, int instr_len, void *ent, char *buffer, int buflen); + union nss_XbyY_key key; + + void *returnval; + int erange; + int h_errno; + nss_status_t status; +} nss_XbyY_args_t; + +#endif /* _NSS_DBDEFS_H */ diff --git a/source4/nsswitch/nss.h b/source4/nsswitch/nss.h new file mode 100644 index 0000000000..d83a5e237e --- /dev/null +++ b/source4/nsswitch/nss.h @@ -0,0 +1,104 @@ +#ifndef _NSSWITCH_NSS_H +#define _NSSWITCH_NSS_H +/* + Unix SMB/CIFS implementation. + + a common place to work out how to define NSS_STATUS on various + platforms + + Copyright (C) Tim Potter 2000 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifdef HAVE_NSS_COMMON_H + +/* Sun Solaris */ + +#include <nss_common.h> +#include <nss_dbdefs.h> +#include <nsswitch.h> + +typedef nss_status_t NSS_STATUS; + +#define NSS_STATUS_SUCCESS NSS_SUCCESS +#define NSS_STATUS_NOTFOUND NSS_NOTFOUND +#define NSS_STATUS_UNAVAIL NSS_UNAVAIL +#define NSS_STATUS_TRYAGAIN NSS_TRYAGAIN + +#elif HAVE_NSS_H + +/* GNU */ + +#include <nss.h> + +typedef enum nss_status NSS_STATUS; + +#elif HAVE_NS_API_H + +/* SGI IRIX */ + +/* following required to prevent warnings of double definition + * of datum from ns_api.h +*/ +#ifdef DATUM +#define _DATUM_DEFINED +#endif + +#include <ns_api.h> + +typedef enum +{ + NSS_STATUS_SUCCESS=NS_SUCCESS, + NSS_STATUS_NOTFOUND=NS_NOTFOUND, + NSS_STATUS_UNAVAIL=NS_UNAVAIL, + NSS_STATUS_TRYAGAIN=NS_TRYAGAIN +} NSS_STATUS; + +#define NSD_MEM_STATIC 0 +#define NSD_MEM_VOLATILE 1 +#define NSD_MEM_DYNAMIC 2 + +#elif defined(HPUX) && defined(HAVE_NSSWITCH_H) +/* HP-UX 11 */ + +#include "nsswitch/hp_nss_common.h" +#include "nsswitch/hp_nss_dbdefs.h" +#include <nsswitch.h> + +#ifndef _HAVE_TYPEDEF_NSS_STATUS +#define _HAVE_TYPEDEF_NSS_STATUS +typedef nss_status_t NSS_STATUS; + +#define NSS_STATUS_SUCCESS NSS_SUCCESS +#define NSS_STATUS_NOTFOUND NSS_NOTFOUND +#define NSS_STATUS_UNAVAIL NSS_UNAVAIL +#define NSS_STATUS_TRYAGAIN NSS_TRYAGAIN +#endif /* HPUX */ + +#else /* Nothing's defined. Neither gnu nor sun nor hp */ + +typedef enum +{ + NSS_STATUS_SUCCESS=0, + NSS_STATUS_NOTFOUND=1, + NSS_STATUS_UNAVAIL=2, + NSS_STATUS_TRYAGAIN=3 +} NSS_STATUS; + +#endif + +#endif /* _NSSWITCH_NSS_H */ diff --git a/source4/nsswitch/pam_winbind.c b/source4/nsswitch/pam_winbind.c new file mode 100644 index 0000000000..123f670366 --- /dev/null +++ b/source4/nsswitch/pam_winbind.c @@ -0,0 +1,754 @@ +/* pam_winbind module + + Copyright Andrew Tridgell <tridge@samba.org> 2000 + Copyright Tim Potter <tpot@samba.org> 2000 + Copyright Andrew Bartlett <abartlet@samba.org> 2002 + + largely based on pam_userdb by Christian Gafton <gafton@redhat.com> + also contains large slabs of code from pam_unix by Elliot Lee <sopwith@redhat.com> + (see copyright below for full details) +*/ + +#include "pam_winbind.h" + +/* data tokens */ + +#define MAX_PASSWD_TRIES 3 + +/* some syslogging */ +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +static int _pam_parse(int argc, const char **argv) +{ + int ctrl; + /* step through arguments */ + for (ctrl = 0; argc-- > 0; ++argv) { + + /* generic options */ + + if (!strcmp(*argv,"debug")) + ctrl |= WINBIND_DEBUG_ARG; + else if (!strcasecmp(*argv, "use_authtok")) + ctrl |= WINBIND_USE_AUTHTOK_ARG; + else if (!strcasecmp(*argv, "use_first_pass")) + ctrl |= WINBIND_USE_FIRST_PASS_ARG; + else if (!strcasecmp(*argv, "try_first_pass")) + ctrl |= WINBIND_TRY_FIRST_PASS_ARG; + else if (!strcasecmp(*argv, "unknown_ok")) + ctrl |= WINBIND_UNKNOWN_OK_ARG; + else { + _pam_log(LOG_ERR, "pam_parse: unknown option; %s", *argv); + } + } + + return ctrl; +} + +/* --- authentication management functions --- */ + +/* Attempt a conversation */ + +static int converse(pam_handle_t *pamh, int nargs, + struct pam_message **message, + struct pam_response **response) +{ + int retval; + struct pam_conv *conv; + + retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv ) ; + if (retval == PAM_SUCCESS) { + retval = conv->conv(nargs, (const struct pam_message **)message, + response, conv->appdata_ptr); + } + + return retval; /* propagate error status */ +} + + +static int _make_remark(pam_handle_t * pamh, int type, const char *text) +{ + int retval = PAM_SUCCESS; + + struct pam_message *pmsg[1], msg[1]; + struct pam_response *resp; + + pmsg[0] = &msg[0]; + msg[0].msg = text; + msg[0].msg_style = type; + + resp = NULL; + retval = converse(pamh, 1, pmsg, &resp); + + if (resp) { + _pam_drop_reply(resp, 1); + } + return retval; +} + +static int pam_winbind_request(enum winbindd_cmd req_type, + struct winbindd_request *request, + struct winbindd_response *response) +{ + + /* Fill in request and send down pipe */ + init_request(request, req_type); + + if (write_sock(request, sizeof(*request)) == -1) { + _pam_log(LOG_ERR, "write to socket failed!"); + close_sock(); + return PAM_SERVICE_ERR; + } + + /* Wait for reply */ + if (read_reply(response) == -1) { + _pam_log(LOG_ERR, "read from socket failed!"); + close_sock(); + return PAM_SERVICE_ERR; + } + + /* We are done with the socket - close it and avoid mischeif */ + close_sock(); + + /* Copy reply data from socket */ + if (response->result != WINBINDD_OK) { + if (response->data.auth.pam_error != PAM_SUCCESS) { + _pam_log(LOG_ERR, "request failed: %s, PAM error was %d, NT error was %s", + response->data.auth.error_string, + response->data.auth.pam_error, + response->data.auth.nt_status_string); + return response->data.auth.pam_error; + } else { + _pam_log(LOG_ERR, "request failed, but PAM error 0!"); + return PAM_SERVICE_ERR; + } + } + + return PAM_SUCCESS; +} + +static int pam_winbind_request_log(enum winbindd_cmd req_type, + struct winbindd_request *request, + struct winbindd_response *response, + int ctrl, + const char *user) +{ + int retval; + + retval = pam_winbind_request(req_type, request, response); + + switch (retval) { + case PAM_AUTH_ERR: + /* incorrect password */ + _pam_log(LOG_WARNING, "user `%s' denied access (incorrect password)", user); + return retval; + case PAM_ACCT_EXPIRED: + /* account expired */ + _pam_log(LOG_WARNING, "user `%s' account expired", user); + return retval; + case PAM_AUTHTOK_EXPIRED: + /* password expired */ + _pam_log(LOG_WARNING, "user `%s' password expired", user); + return retval; + case PAM_NEW_AUTHTOK_REQD: + /* password expired */ + _pam_log(LOG_WARNING, "user `%s' new password required", user); + return retval; + case PAM_USER_UNKNOWN: + /* the user does not exist */ + if (ctrl & WINBIND_DEBUG_ARG) + _pam_log(LOG_NOTICE, "user `%s' not found", + user); + if (ctrl & WINBIND_UNKNOWN_OK_ARG) { + return PAM_IGNORE; + } + return retval; + case PAM_SUCCESS: + if (req_type == WINBINDD_PAM_AUTH) { + /* Otherwise, the authentication looked good */ + _pam_log(LOG_NOTICE, "user '%s' granted acces", user); + } else if (req_type == WINBINDD_PAM_CHAUTHTOK) { + /* Otherwise, the authentication looked good */ + _pam_log(LOG_NOTICE, "user '%s' password changed", user); + } else { + /* Otherwise, the authentication looked good */ + _pam_log(LOG_NOTICE, "user '%s' OK", user); + } + return retval; + default: + /* we don't know anything about this return value */ + _pam_log(LOG_ERR, "internal module error (retval = %d, user = `%s'", + retval, user); + return retval; + } +} + +/* talk to winbindd */ +static int winbind_auth_request(const char *user, const char *pass, int ctrl) +{ + struct winbindd_request request; + struct winbindd_response response; + + ZERO_STRUCT(request); + + strncpy(request.data.auth.user, user, + sizeof(request.data.auth.user)-1); + + strncpy(request.data.auth.pass, pass, + sizeof(request.data.auth.pass)-1); + + + return pam_winbind_request_log(WINBINDD_PAM_AUTH, &request, &response, ctrl, user); +} + +/* talk to winbindd */ +static int winbind_chauthtok_request(const char *user, const char *oldpass, + const char *newpass, int ctrl) +{ + struct winbindd_request request; + struct winbindd_response response; + + ZERO_STRUCT(request); + + if (request.data.chauthtok.user == NULL) return -2; + + strncpy(request.data.chauthtok.user, user, + sizeof(request.data.chauthtok.user) - 1); + + if (oldpass != NULL) { + strncpy(request.data.chauthtok.oldpass, oldpass, + sizeof(request.data.chauthtok.oldpass) - 1); + } else { + request.data.chauthtok.oldpass[0] = '\0'; + } + + if (newpass != NULL) { + strncpy(request.data.chauthtok.newpass, newpass, + sizeof(request.data.chauthtok.newpass) - 1); + } else { + request.data.chauthtok.newpass[0] = '\0'; + } + + return pam_winbind_request_log(WINBINDD_PAM_CHAUTHTOK, &request, &response, ctrl, user); +} + +/* + * Checks if a user has an account + * + * return values: + * 1 = User not found + * 0 = OK + * -1 = System error + */ +static int valid_user(const char *user) +{ + if (getpwnam(user)) return 0; + return 1; +} + +static char *_pam_delete(register char *xx) +{ + _pam_overwrite(xx); + _pam_drop(xx); + return NULL; +} + +/* + * obtain a password from the user + */ + +static int _winbind_read_password(pam_handle_t * pamh + ,unsigned int ctrl + ,const char *comment + ,const char *prompt1 + ,const char *prompt2 + ,const char **pass) +{ + int authtok_flag; + int retval; + const char *item; + char *token; + + /* + * make sure nothing inappropriate gets returned + */ + + *pass = token = NULL; + + /* + * which authentication token are we getting? + */ + + authtok_flag = on(WINBIND__OLD_PASSWORD, ctrl) ? PAM_OLDAUTHTOK : PAM_AUTHTOK; + + /* + * should we obtain the password from a PAM item ? + */ + + if (on(WINBIND_TRY_FIRST_PASS_ARG, ctrl) || on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) { + retval = pam_get_item(pamh, authtok_flag, (const void **) &item); + if (retval != PAM_SUCCESS) { + /* very strange. */ + _pam_log(LOG_ALERT, + "pam_get_item returned error to unix-read-password" + ); + return retval; + } else if (item != NULL) { /* we have a password! */ + *pass = item; + item = NULL; + return PAM_SUCCESS; + } else if (on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) { + return PAM_AUTHTOK_RECOVER_ERR; /* didn't work */ + } else if (on(WINBIND_USE_AUTHTOK_ARG, ctrl) + && off(WINBIND__OLD_PASSWORD, ctrl)) { + return PAM_AUTHTOK_RECOVER_ERR; + } + } + /* + * getting here implies we will have to get the password from the + * user directly. + */ + + { + struct pam_message msg[3], *pmsg[3]; + struct pam_response *resp; + int i, replies; + + /* prepare to converse */ + + if (comment != NULL) { + pmsg[0] = &msg[0]; + msg[0].msg_style = PAM_TEXT_INFO; + msg[0].msg = comment; + i = 1; + } else { + i = 0; + } + + pmsg[i] = &msg[i]; + msg[i].msg_style = PAM_PROMPT_ECHO_OFF; + msg[i++].msg = prompt1; + replies = 1; + + if (prompt2 != NULL) { + pmsg[i] = &msg[i]; + msg[i].msg_style = PAM_PROMPT_ECHO_OFF; + msg[i++].msg = prompt2; + ++replies; + } + /* so call the conversation expecting i responses */ + resp = NULL; + retval = converse(pamh, i, pmsg, &resp); + + if (resp != NULL) { + + /* interpret the response */ + + if (retval == PAM_SUCCESS) { /* a good conversation */ + + token = x_strdup(resp[i - replies].resp); + if (token != NULL) { + if (replies == 2) { + + /* verify that password entered correctly */ + if (!resp[i - 1].resp + || strcmp(token, resp[i - 1].resp)) { + _pam_delete(token); /* mistyped */ + retval = PAM_AUTHTOK_RECOVER_ERR; + _make_remark(pamh ,PAM_ERROR_MSG, MISTYPED_PASS); + } + } + } else { + _pam_log(LOG_NOTICE + ,"could not recover authentication token"); + } + + } + /* + * tidy up the conversation (resp_retcode) is ignored + * -- what is it for anyway? AGM + */ + + _pam_drop_reply(resp, i); + + } else { + retval = (retval == PAM_SUCCESS) + ? PAM_AUTHTOK_RECOVER_ERR : retval; + } + } + + if (retval != PAM_SUCCESS) { + if (on(WINBIND_DEBUG_ARG, ctrl)) + _pam_log(LOG_DEBUG, + "unable to obtain a password"); + return retval; + } + /* 'token' is the entered password */ + + /* we store this password as an item */ + + retval = pam_set_item(pamh, authtok_flag, token); + _pam_delete(token); /* clean it up */ + if (retval != PAM_SUCCESS + || (retval = pam_get_item(pamh, authtok_flag + ,(const void **) &item)) + != PAM_SUCCESS) { + + _pam_log(LOG_CRIT, "error manipulating password"); + return retval; + + } + + *pass = item; + item = NULL; /* break link to password */ + + return PAM_SUCCESS; +} + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + const char *username; + const char *password; + int retval = PAM_AUTH_ERR; + + /* parse arguments */ + int ctrl = _pam_parse(argc, argv); + + /* Get the username */ + retval = pam_get_user(pamh, &username, NULL); + if ((retval != PAM_SUCCESS) || (!username)) { + if (ctrl & WINBIND_DEBUG_ARG) + _pam_log(LOG_DEBUG,"can not get the username"); + return PAM_SERVICE_ERR; + } + + retval = _winbind_read_password(pamh, ctrl, NULL, + "Password: ", NULL, + &password); + + if (retval != PAM_SUCCESS) { + _pam_log(LOG_ERR, "Could not retrieve user's password"); + return PAM_AUTHTOK_ERR; + } + + if (ctrl & WINBIND_DEBUG_ARG) { + + /* Let's not give too much away in the log file */ + +#ifdef DEBUG_PASSWORD + _pam_log(LOG_INFO, "Verify user `%s' with password `%s'", + username, password); +#else + _pam_log(LOG_INFO, "Verify user `%s'", username); +#endif + } + + /* Now use the username to look up password */ + return winbind_auth_request(username, password, ctrl); +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + return PAM_SUCCESS; +} + +/* + * Account management. We want to verify that the account exists + * before returning PAM_SUCCESS + */ +PAM_EXTERN +int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + const char *username; + int retval = PAM_USER_UNKNOWN; + + /* parse arguments */ + int ctrl = _pam_parse(argc, argv); + + /* Get the username */ + retval = pam_get_user(pamh, &username, NULL); + if ((retval != PAM_SUCCESS) || (!username)) { + if (ctrl & WINBIND_DEBUG_ARG) + _pam_log(LOG_DEBUG,"can not get the username"); + return PAM_SERVICE_ERR; + } + + /* Verify the username */ + retval = valid_user(username); + switch (retval) { + case -1: + /* some sort of system error. The log was already printed */ + return PAM_SERVICE_ERR; + case 1: + /* the user does not exist */ + if (ctrl & WINBIND_DEBUG_ARG) + _pam_log(LOG_NOTICE, "user `%s' not found", + username); + if (ctrl & WINBIND_UNKNOWN_OK_ARG) + return PAM_IGNORE; + return PAM_USER_UNKNOWN; + case 0: + /* Otherwise, the authentication looked good */ + _pam_log(LOG_NOTICE, "user '%s' granted acces", username); + return PAM_SUCCESS; + default: + /* we don't know anything about this return value */ + _pam_log(LOG_ERR, "internal module error (retval = %d, user = `%s'", + retval, username); + return PAM_SERVICE_ERR; + } + + /* should not be reached */ + return PAM_IGNORE; +} +PAM_EXTERN +int pam_sm_open_session(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + /* parse arguments */ + int ctrl = _pam_parse(argc, argv); + if (ctrl & WINBIND_DEBUG_ARG) + _pam_log(LOG_DEBUG,"libpam_winbind:pam_sm_open_session handler"); + return PAM_SUCCESS; +} +PAM_EXTERN +int pam_sm_close_session(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + /* parse arguments */ + int ctrl = _pam_parse(argc, argv); + if (ctrl & WINBIND_DEBUG_ARG) + _pam_log(LOG_DEBUG,"libpam_winbind:pam_sm_close_session handler"); + return PAM_SUCCESS; +} + + + +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, + int argc, const char **argv) +{ + unsigned int lctrl; + int retval; + unsigned int ctrl = _pam_parse(argc, argv); + + /* <DO NOT free() THESE> */ + const char *user; + char *pass_old, *pass_new; + /* </DO NOT free() THESE> */ + + char *Announce; + + int retry = 0; + + /* + * First get the name of a user + */ + retval = pam_get_user(pamh, &user, "Username: "); + if (retval == PAM_SUCCESS) { + if (user == NULL) { + _pam_log(LOG_ERR, "username was NULL!"); + return PAM_USER_UNKNOWN; + } + if (retval == PAM_SUCCESS && on(WINBIND_DEBUG_ARG, ctrl)) + _pam_log(LOG_DEBUG, "username [%s] obtained", + user); + } else { + if (on(WINBIND_DEBUG_ARG, ctrl)) + _pam_log(LOG_DEBUG, + "password - could not identify user"); + return retval; + } + + /* + * obtain and verify the current password (OLDAUTHTOK) for + * the user. + */ + + if (flags & PAM_PRELIM_CHECK) { + + /* instruct user what is happening */ +#define greeting "Changing password for " + Announce = (char *) malloc(sizeof(greeting) + strlen(user)); + if (Announce == NULL) { + _pam_log(LOG_CRIT, + "password - out of memory"); + return PAM_BUF_ERR; + } + (void) strcpy(Announce, greeting); + (void) strcpy(Announce + sizeof(greeting) - 1, user); +#undef greeting + + lctrl = ctrl | WINBIND__OLD_PASSWORD; + retval = _winbind_read_password(pamh, lctrl + ,Announce + ,"(current) NT password: " + ,NULL + ,(const char **) &pass_old); + free(Announce); + + if (retval != PAM_SUCCESS) { + _pam_log(LOG_NOTICE + ,"password - (old) token not obtained"); + return retval; + } + /* verify that this is the password for this user */ + + retval = winbind_auth_request(user, pass_old, ctrl); + + if (retval != PAM_ACCT_EXPIRED + && retval != PAM_AUTHTOK_EXPIRED + && retval != PAM_NEW_AUTHTOK_REQD + && retval != PAM_SUCCESS) { + pass_old = NULL; + return retval; + } + + retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old); + pass_old = NULL; + if (retval != PAM_SUCCESS) { + _pam_log(LOG_CRIT, + "failed to set PAM_OLDAUTHTOK"); + } + } else if (flags & PAM_UPDATE_AUTHTOK) { + + /* + * obtain the proposed password + */ + + /* + * get the old token back. + */ + + retval = pam_get_item(pamh, PAM_OLDAUTHTOK + ,(const void **) &pass_old); + + if (retval != PAM_SUCCESS) { + _pam_log(LOG_NOTICE, "user not authenticated"); + return retval; + } + + lctrl = ctrl; + + if (on(WINBIND_USE_AUTHTOK_ARG, lctrl)) { + ctrl = WINBIND_USE_FIRST_PASS_ARG | lctrl; + } + retry = 0; + retval = PAM_AUTHTOK_ERR; + while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) { + /* + * use_authtok is to force the use of a previously entered + * password -- needed for pluggable password strength checking + */ + + retval = _winbind_read_password(pamh, lctrl + ,NULL + ,"Enter new NT password: " + ,"Retype new NT password: " + ,(const char **) &pass_new); + + if (retval != PAM_SUCCESS) { + if (on(WINBIND_DEBUG_ARG, ctrl)) { + _pam_log(LOG_ALERT + ,"password - new password not obtained"); + } + pass_old = NULL;/* tidy up */ + return retval; + } + + /* + * At this point we know who the user is and what they + * propose as their new password. Verify that the new + * password is acceptable. + */ + + if (pass_new[0] == '\0') {/* "\0" password = NULL */ + pass_new = NULL; + } + } + + /* + * By reaching here we have approved the passwords and must now + * rebuild the password database file. + */ + + retval = winbind_chauthtok_request(user, pass_old, pass_new, ctrl); + _pam_overwrite(pass_new); + _pam_overwrite(pass_old); + pass_old = pass_new = NULL; + } else { + retval = PAM_SERVICE_ERR; + } + + return retval; +} + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_winbind_modstruct = { + MODULE_NAME, + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok +}; + +#endif + +/* + * Copyright (c) Andrew Tridgell <tridge@samba.org> 2000 + * Copyright (c) Tim Potter <tpot@samba.org> 2000 + * Copyright (c) Andrew Bartlettt <abartlet@samba.org> 2002 + * Copyright (c) Jan Rêkorajski 1999. + * Copyright (c) Andrew G. Morgan 1996-8. + * Copyright (c) Alex O. Yuriev, 1996. + * Copyright (c) Cristian Gafton 1996. + * Copyright (C) Elliot Lee <sopwith@redhat.com> 1996, Red Hat Software. + * + * 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, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 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 author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `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 AUTHOR 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. + */ diff --git a/source4/nsswitch/pam_winbind.h b/source4/nsswitch/pam_winbind.h new file mode 100644 index 0000000000..fae635d806 --- /dev/null +++ b/source4/nsswitch/pam_winbind.h @@ -0,0 +1,93 @@ +/* pam_winbind header file + (Solaris needs some macros from Linux for common PAM code) + + Shirish Kalele 2000 +*/ + +#ifdef HAVE_FEATURES_H +#include <features.h> +#endif + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <syslog.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> + +#include <config.h> + +#define MODULE_NAME "pam_winbind" +#define PAM_SM_AUTH +#define PAM_SM_ACCOUNT +#define PAM_SM_PASSWORD + +#if defined(SUNOS5) || defined(SUNOS4) || defined(HPUX) + +/* Solaris always uses dynamic pam modules */ +#define PAM_EXTERN extern +#include <security/pam_appl.h> + +#define PAM_AUTHTOK_RECOVER_ERR PAM_AUTHTOK_RECOVERY_ERR +#endif + +#ifdef HAVE_SECURITY_PAM_MODULES_H +#include <security/pam_modules.h> +#endif + +#ifdef HAVE_SECURITY__PAM_MACROS_H +#include <security/_pam_macros.h> +#else +/* Define required macros from (Linux PAM 0.68) security/_pam_macros.h */ +#define _pam_drop_reply(/* struct pam_response * */ reply, /* int */ replies) \ +do { \ + int reply_i; \ + \ + for (reply_i=0; reply_i<replies; ++reply_i) { \ + if (reply[reply_i].resp) { \ + _pam_overwrite(reply[reply_i].resp); \ + free(reply[reply_i].resp); \ + } \ + } \ + if (reply) \ + free(reply); \ +} while (0) + +#define _pam_overwrite(x) \ +do { \ + register char *__xx__; \ + if ((__xx__=(x))) \ + while (*__xx__) \ + *__xx__++ = '\0'; \ +} while (0) + +/* + * Don't just free it, forget it too. + */ + +#define _pam_drop(X) SAFE_FREE(X) + +#define x_strdup(s) ( (s) ? strdup(s):NULL ) +#endif + +#define WINBIND_DEBUG_ARG (1<<0) +#define WINBIND_USE_AUTHTOK_ARG (1<<1) +#define WINBIND_UNKNOWN_OK_ARG (1<<2) +#define WINBIND_TRY_FIRST_PASS_ARG (1<<3) +#define WINBIND_USE_FIRST_PASS_ARG (1<<4) +#define WINBIND__OLD_PASSWORD (1<<5) + +/* + * here is the string to inform the user that the new passwords they + * typed were not the same. + */ + +#define MISTYPED_PASS "Sorry, passwords do not match" + +#define on(x, y) (x & y) +#define off(x, y) (!(x & y)) + +#include "winbind_client.h" diff --git a/source4/nsswitch/wb_client.c b/source4/nsswitch/wb_client.c new file mode 100644 index 0000000000..62c9686960 --- /dev/null +++ b/source4/nsswitch/wb_client.c @@ -0,0 +1,307 @@ +/* + Unix SMB/CIFS implementation. + + winbind client code + + Copyright (C) Tim Potter 2000 + Copyright (C) Andrew Tridgell 2000 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include "includes.h" +#include "nsswitch/nss.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +extern DOM_SID global_sid_NULL; /* NULL sid */ + +NSS_STATUS winbindd_request(int req_type, + struct winbindd_request *request, + struct winbindd_response *response); + +/* Call winbindd to convert a name to a sid */ + +BOOL winbind_lookup_name(const char *dom_name, const char *name, DOM_SID *sid, + enum SID_NAME_USE *name_type) +{ + struct winbindd_request request; + struct winbindd_response response; + NSS_STATUS result; + + if (!sid || !name_type) + return False; + + /* Send off request */ + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + fstrcpy(request.data.name.dom_name, dom_name); + fstrcpy(request.data.name.name, name); + + if ((result = winbindd_request(WINBINDD_LOOKUPNAME, &request, + &response)) == NSS_STATUS_SUCCESS) { + if (!string_to_sid(sid, response.data.sid.sid)) + return False; + *name_type = (enum SID_NAME_USE)response.data.sid.type; + } + + return result == NSS_STATUS_SUCCESS; +} + +/* Call winbindd to convert sid to name */ + +BOOL winbind_lookup_sid(const DOM_SID *sid, + fstring dom_name, fstring name, + enum SID_NAME_USE *name_type) +{ + struct winbindd_request request; + struct winbindd_response response; + NSS_STATUS result; + fstring sid_str; + + /* Initialise request */ + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + sid_to_string(sid_str, sid); + fstrcpy(request.data.sid, sid_str); + + /* Make request */ + + result = winbindd_request(WINBINDD_LOOKUPSID, &request, &response); + + /* Copy out result */ + + if (result == NSS_STATUS_SUCCESS) { + fstrcpy(dom_name, response.data.name.dom_name); + fstrcpy(name, response.data.name.name); + *name_type = (enum SID_NAME_USE)response.data.name.type; + + DEBUG(10, ("winbind_lookup_sid: SUCCESS: SID %s -> %s %s\n", + sid_str, dom_name, name)); + } + + return (result == NSS_STATUS_SUCCESS); +} + +/* Call winbindd to convert SID to uid */ + +BOOL winbind_sid_to_uid(uid_t *puid, const DOM_SID *sid) +{ + struct winbindd_request request; + struct winbindd_response response; + int result; + fstring sid_str; + + if (!puid) + return False; + + /* Initialise request */ + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + sid_to_string(sid_str, sid); + fstrcpy(request.data.sid, sid_str); + + /* Make request */ + + result = winbindd_request(WINBINDD_SID_TO_UID, &request, &response); + + /* Copy out result */ + + if (result == NSS_STATUS_SUCCESS) { + *puid = response.data.uid; + } + + return (result == NSS_STATUS_SUCCESS); +} + +/* Call winbindd to convert uid to sid */ + +BOOL winbind_uid_to_sid(DOM_SID *sid, uid_t uid) +{ + struct winbindd_request request; + struct winbindd_response response; + int result; + + if (!sid) + return False; + + /* Initialise request */ + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + request.data.uid = uid; + + /* Make request */ + + result = winbindd_request(WINBINDD_UID_TO_SID, &request, &response); + + /* Copy out result */ + + if (result == NSS_STATUS_SUCCESS) { + if (!string_to_sid(sid, response.data.sid.sid)) + return False; + } else { + sid_copy(sid, &global_sid_NULL); + } + + return (result == NSS_STATUS_SUCCESS); +} + +/* Call winbindd to convert SID to gid */ + +BOOL winbind_sid_to_gid(gid_t *pgid, const DOM_SID *sid) +{ + struct winbindd_request request; + struct winbindd_response response; + int result; + fstring sid_str; + + if (!pgid) + return False; + + /* Initialise request */ + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + sid_to_string(sid_str, sid); + fstrcpy(request.data.sid, sid_str); + + /* Make request */ + + result = winbindd_request(WINBINDD_SID_TO_GID, &request, &response); + + /* Copy out result */ + + if (result == NSS_STATUS_SUCCESS) { + *pgid = response.data.gid; + } + + return (result == NSS_STATUS_SUCCESS); +} + +/* Call winbindd to convert gid to sid */ + +BOOL winbind_gid_to_sid(DOM_SID *sid, gid_t gid) +{ + struct winbindd_request request; + struct winbindd_response response; + int result; + + if (!sid) + return False; + + /* Initialise request */ + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + request.data.gid = gid; + + /* Make request */ + + result = winbindd_request(WINBINDD_GID_TO_SID, &request, &response); + + /* Copy out result */ + + if (result == NSS_STATUS_SUCCESS) { + if (!string_to_sid(sid, response.data.sid.sid)) + return False; + } else { + sid_copy(sid, &global_sid_NULL); + } + + return (result == NSS_STATUS_SUCCESS); +} + +/* Fetch the list of groups a user is a member of from winbindd. This is + used by winbind_getgroups. */ + +static int wb_getgroups(const char *user, gid_t **groups) +{ + struct winbindd_request request; + struct winbindd_response response; + int result; + + /* Call winbindd */ + + fstrcpy(request.data.username, user); + + ZERO_STRUCT(response); + + result = winbindd_request(WINBINDD_GETGROUPS, &request, &response); + + if (result == NSS_STATUS_SUCCESS) { + + /* Return group list. Don't forget to free the group list + when finished. */ + + *groups = (gid_t *)response.extra_data; + return response.data.num_entries; + } + + return -1; +} + +/* Return a list of groups the user is a member of. This function is + useful for large systems where inverting the group database would be too + time consuming. If size is zero, list is not modified and the total + number of groups for the user is returned. */ + +int winbind_getgroups(const char *user, int size, gid_t *list) +{ + gid_t *groups = NULL; + int result, i; + + /* + * Don't do the lookup if the name has no separator _and_ we are not in + * 'winbind use default domain' mode. + */ + + if (!(strchr(user, *lp_winbind_separator()) || lp_winbind_use_default_domain())) + return -1; + + /* Fetch list of groups */ + + result = wb_getgroups(user, &groups); + + if (size == 0) + goto done; + + if (result > size) { + result = -1; + errno = EINVAL; /* This is what getgroups() does */ + goto done; + } + + /* Copy list of groups across */ + + for (i = 0; i < result; i++) { + list[i] = groups[i]; + } + + done: + SAFE_FREE(groups); + return result; +} diff --git a/source4/nsswitch/wb_common.c b/source4/nsswitch/wb_common.c new file mode 100644 index 0000000000..89c751a4ef --- /dev/null +++ b/source4/nsswitch/wb_common.c @@ -0,0 +1,433 @@ +/* + Unix SMB/CIFS implementation. + + winbind client common code + + Copyright (C) Tim Potter 2000 + Copyright (C) Andrew Tridgell 2000 + Copyright (C) Andrew Bartlett 2002 + + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include "winbind_client.h" + +/* Global variables. These are effectively the client state information */ + +int winbindd_fd = -1; /* fd for winbindd socket */ + +/* Free a response structure */ + +void free_response(struct winbindd_response *response) +{ + /* Free any allocated extra_data */ + + if (response) + SAFE_FREE(response->extra_data); +} + +/* Initialise a request structure */ + +void init_request(struct winbindd_request *request, int request_type) +{ + request->length = sizeof(struct winbindd_request); + + request->cmd = (enum winbindd_cmd)request_type; + request->pid = getpid(); + +} + +/* Initialise a response structure */ + +void init_response(struct winbindd_response *response) +{ + /* Initialise return value */ + + response->result = WINBINDD_ERROR; +} + +/* Close established socket */ + +void close_sock(void) +{ + if (winbindd_fd != -1) { + close(winbindd_fd); + winbindd_fd = -1; + } +} + +/* Make sure socket handle isn't stdin, stdout or stderr */ +#define RECURSION_LIMIT 3 + +static int make_nonstd_fd_internals(int fd, int limit /* Recursion limiter */) +{ + int new_fd; + if (fd >= 0 && fd <= 2) { +#ifdef F_DUPFD + if ((new_fd = fcntl(fd, F_DUPFD, 3)) == -1) { + return -1; + } + /* Parinoia */ + if (new_fd < 3) { + close(new_fd); + return -1; + } + close(fd); + return new_fd; +#else + if (limit <= 0) + return -1; + + new_fd = dup(fd); + if (new_fd == -1) + return -1; + + /* use the program stack to hold our list of FDs to close */ + new_fd = make_nonstd_fd_internals(new_fd, limit - 1); + close(fd); + return new_fd; +#endif + } + return fd; +} + +static int make_safe_fd(int fd) +{ + int result, flags; + int new_fd = make_nonstd_fd_internals(fd, RECURSION_LIMIT); + if (new_fd == -1) { + close(fd); + return -1; + } + /* Socket should be closed on exec() */ + +#ifdef FD_CLOEXEC + result = flags = fcntl(new_fd, F_GETFD, 0); + if (flags >= 0) { + flags |= FD_CLOEXEC; + result = fcntl( new_fd, F_SETFD, flags ); + } + if (result < 0) { + close(new_fd); + return -1; + } +#endif + return new_fd; +} + +/* Connect to winbindd socket */ + +int winbind_open_pipe_sock(void) +{ +#ifdef HAVE_UNIXSOCKET + struct sockaddr_un sunaddr; + static pid_t our_pid; + struct stat st; + pstring path; + int fd; + + if (our_pid != getpid()) { + close_sock(); + our_pid = getpid(); + } + + if (winbindd_fd != -1) { + return winbindd_fd; + } + + /* Check permissions on unix socket directory */ + + if (lstat(WINBINDD_SOCKET_DIR, &st) == -1) { + return -1; + } + + if (!S_ISDIR(st.st_mode) || + (st.st_uid != 0 && st.st_uid != geteuid())) { + return -1; + } + + /* Connect to socket */ + + strncpy(path, WINBINDD_SOCKET_DIR, sizeof(path) - 1); + path[sizeof(path) - 1] = '\0'; + + strncat(path, "/", sizeof(path) - 1); + path[sizeof(path) - 1] = '\0'; + + strncat(path, WINBINDD_SOCKET_NAME, sizeof(path) - 1); + path[sizeof(path) - 1] = '\0'; + + ZERO_STRUCT(sunaddr); + sunaddr.sun_family = AF_UNIX; + strncpy(sunaddr.sun_path, path, sizeof(sunaddr.sun_path) - 1); + + /* If socket file doesn't exist, don't bother trying to connect + with retry. This is an attempt to make the system usable when + the winbindd daemon is not running. */ + + if (lstat(path, &st) == -1) { + return -1; + } + + /* Check permissions on unix socket file */ + + if (!S_ISSOCK(st.st_mode) || + (st.st_uid != 0 && st.st_uid != geteuid())) { + return -1; + } + + /* Connect to socket */ + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + return -1; + } + + if ((winbindd_fd = make_safe_fd( fd)) == -1) { + return winbindd_fd; + } + + if (connect(winbindd_fd, (struct sockaddr *)&sunaddr, + sizeof(sunaddr)) == -1) { + close_sock(); + return -1; + } + + return winbindd_fd; +#else + return -1; +#endif /* HAVE_UNIXSOCKET */ +} + +/* Write data to winbindd socket */ + +int write_sock(void *buffer, int count) +{ + int result, nwritten; + + /* Open connection to winbind daemon */ + + restart: + + if (winbind_open_pipe_sock() == -1) { + return -1; + } + + /* Write data to socket */ + + nwritten = 0; + + while(nwritten < count) { + struct timeval tv; + fd_set r_fds; + + /* Catch pipe close on other end by checking if a read() + call would not block by calling select(). */ + + FD_ZERO(&r_fds); + FD_SET(winbindd_fd, &r_fds); + ZERO_STRUCT(tv); + + if (select(winbindd_fd + 1, &r_fds, NULL, NULL, &tv) == -1) { + close_sock(); + return -1; /* Select error */ + } + + /* Write should be OK if fd not available for reading */ + + if (!FD_ISSET(winbindd_fd, &r_fds)) { + + /* Do the write */ + + result = write(winbindd_fd, + (char *)buffer + nwritten, + count - nwritten); + + if ((result == -1) || (result == 0)) { + + /* Write failed */ + + close_sock(); + return -1; + } + + nwritten += result; + + } else { + + /* Pipe has closed on remote end */ + + close_sock(); + goto restart; + } + } + + return nwritten; +} + +/* Read data from winbindd socket */ + +static int read_sock(void *buffer, int count) +{ + int result = 0, nread = 0; + + /* Read data from socket */ + + while(nread < count) { + + result = read(winbindd_fd, (char *)buffer + nread, + count - nread); + + if ((result == -1) || (result == 0)) { + + /* Read failed. I think the only useful thing we + can do here is just return -1 and fail since the + transaction has failed half way through. */ + + close_sock(); + return -1; + } + + nread += result; + } + + return result; +} + +/* Read reply */ + +int read_reply(struct winbindd_response *response) +{ + int result1, result2 = 0; + + if (!response) { + return -1; + } + + /* Read fixed length response */ + + if ((result1 = read_sock(response, sizeof(struct winbindd_response))) + == -1) { + + return -1; + } + + /* We actually send the pointer value of the extra_data field from + the server. This has no meaning in the client's address space + so we clear it out. */ + + response->extra_data = NULL; + + /* Read variable length response */ + + if (response->length > sizeof(struct winbindd_response)) { + int extra_data_len = response->length - + sizeof(struct winbindd_response); + + /* Mallocate memory for extra data */ + + if (!(response->extra_data = malloc(extra_data_len))) { + return -1; + } + + if ((result2 = read_sock(response->extra_data, extra_data_len)) + == -1) { + free_response(response); + return -1; + } + } + + /* Return total amount of data read */ + + return result1 + result2; +} + +/* + * send simple types of requests + */ + +NSS_STATUS winbindd_send_request(int req_type, struct winbindd_request *request) +{ + struct winbindd_request lrequest; + + /* Check for our tricky environment variable */ + + if (getenv(WINBINDD_DONT_ENV)) { + return NSS_STATUS_NOTFOUND; + } + + if (!request) { + ZERO_STRUCT(lrequest); + request = &lrequest; + } + + /* Fill in request and send down pipe */ + + init_request(request, req_type); + + if (write_sock(request, sizeof(*request)) == -1) { + return NSS_STATUS_UNAVAIL; + } + + return NSS_STATUS_SUCCESS; +} + +/* + * Get results from winbindd request + */ + +NSS_STATUS winbindd_get_response(struct winbindd_response *response) +{ + struct winbindd_response lresponse; + + if (!response) { + ZERO_STRUCT(lresponse); + response = &lresponse; + } + + init_response(response); + + /* Wait for reply */ + if (read_reply(response) == -1) { + return NSS_STATUS_UNAVAIL; + } + + /* Throw away extra data if client didn't request it */ + if (response == &lresponse) { + free_response(response); + } + + /* Copy reply data from socket */ + if (response->result != WINBINDD_OK) { + return NSS_STATUS_NOTFOUND; + } + + return NSS_STATUS_SUCCESS; +} + +/* Handle simple types of requests */ + +NSS_STATUS winbindd_request(int req_type, + struct winbindd_request *request, + struct winbindd_response *response) +{ + NSS_STATUS status; + + status = winbindd_send_request(req_type, request); + if (status != NSS_STATUS_SUCCESS) + return(status); + return winbindd_get_response(response); +} diff --git a/source4/nsswitch/wbinfo.c b/source4/nsswitch/wbinfo.c new file mode 100644 index 0000000000..68dc178bcd --- /dev/null +++ b/source4/nsswitch/wbinfo.c @@ -0,0 +1,891 @@ +/* + Unix SMB/CIFS implementation. + + Winbind status program. + + Copyright (C) Tim Potter 2000-2002 + Copyright (C) Andrew Bartlett 2002 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "winbindd.h" +#include "debug.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +extern int winbindd_fd; + +static char winbind_separator(void) +{ + struct winbindd_response response; + static BOOL got_sep; + static char sep; + + if (got_sep) + return sep; + + ZERO_STRUCT(response); + + /* Send off request */ + + if (winbindd_request(WINBINDD_INFO, NULL, &response) != + NSS_STATUS_SUCCESS) { + d_printf("could not obtain winbind separator!\n"); + /* HACK: (this module should not call lp_ funtions) */ + return *lp_winbind_separator(); + } + + sep = response.data.info.winbind_separator; + got_sep = True; + + if (!sep) { + d_printf("winbind separator was NULL!\n"); + /* HACK: (this module should not call lp_ funtions) */ + sep = *lp_winbind_separator(); + } + + return sep; +} + +static const char *get_winbind_domain(void) +{ + struct winbindd_response response; + static fstring winbind_domain; + + ZERO_STRUCT(response); + + /* Send off request */ + + if (winbindd_request(WINBINDD_DOMAIN_NAME, NULL, &response) != + NSS_STATUS_SUCCESS) { + d_printf("could not obtain winbind domain name!\n"); + + /* HACK: (this module should not call lp_ funtions) */ + return lp_workgroup(); + } + + fstrcpy(winbind_domain, response.data.domain_name); + + return winbind_domain; + +} + +/* Copy of parse_domain_user from winbindd_util.c. Parse a string of the + form DOMAIN/user into a domain and a user */ + +static BOOL parse_wbinfo_domain_user(const char *domuser, fstring domain, + fstring user) +{ + + char *p = strchr(domuser,winbind_separator()); + + if (!p) { + fstrcpy(user, domuser); + fstrcpy(domain, get_winbind_domain()); + return True; + } + + fstrcpy(user, p+1); + fstrcpy(domain, domuser); + domain[PTR_DIFF(p, domuser)] = 0; + strupper(domain); + + return True; +} + +/* List groups a user is a member of */ + +static BOOL wbinfo_get_usergroups(char *user) +{ + struct winbindd_request request; + struct winbindd_response response; + NSS_STATUS result; + int i; + + ZERO_STRUCT(response); + + /* Send request */ + + fstrcpy(request.data.username, user); + + result = winbindd_request(WINBINDD_GETGROUPS, &request, &response); + + if (result != NSS_STATUS_SUCCESS) + return False; + + for (i = 0; i < response.data.num_entries; i++) + d_printf("%d\n", (int)((gid_t *)response.extra_data)[i]); + + SAFE_FREE(response.extra_data); + + return True; +} + +/* Convert NetBIOS name to IP */ + +static BOOL wbinfo_wins_byname(char *name) +{ + struct winbindd_request request; + struct winbindd_response response; + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + /* Send request */ + + fstrcpy(request.data.winsreq, name); + + if (winbindd_request(WINBINDD_WINS_BYNAME, &request, &response) != + NSS_STATUS_SUCCESS) { + return False; + } + + /* Display response */ + + printf("%s\n", response.data.winsresp); + + return True; +} + +/* Convert IP to NetBIOS name */ + +static BOOL wbinfo_wins_byip(char *ip) +{ + struct winbindd_request request; + struct winbindd_response response; + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + /* Send request */ + + fstrcpy(request.data.winsreq, ip); + + if (winbindd_request(WINBINDD_WINS_BYIP, &request, &response) != + NSS_STATUS_SUCCESS) { + return False; + } + + /* Display response */ + + printf("%s\n", response.data.winsresp); + + return True; +} + +/* List trusted domains */ + +static BOOL wbinfo_list_domains(void) +{ + struct winbindd_response response; + fstring name; + + ZERO_STRUCT(response); + + /* Send request */ + + if (winbindd_request(WINBINDD_LIST_TRUSTDOM, NULL, &response) != + NSS_STATUS_SUCCESS) + return False; + + /* Display response */ + + if (response.extra_data) { + const char *extra_data = (char *)response.extra_data; + + while(next_token(&extra_data, name, ",", sizeof(fstring))) + d_printf("%s\n", name); + + SAFE_FREE(response.extra_data); + } + + return True; +} + + +/* show sequence numbers */ +static BOOL wbinfo_show_sequence(void) +{ + struct winbindd_response response; + + ZERO_STRUCT(response); + + /* Send request */ + + if (winbindd_request(WINBINDD_SHOW_SEQUENCE, NULL, &response) != + NSS_STATUS_SUCCESS) + return False; + + /* Display response */ + + if (response.extra_data) { + char *extra_data = (char *)response.extra_data; + d_printf("%s", extra_data); + SAFE_FREE(response.extra_data); + } + + return True; +} + +/* Check trust account password */ + +static BOOL wbinfo_check_secret(void) +{ + struct winbindd_response response; + NSS_STATUS result; + + ZERO_STRUCT(response); + + result = winbindd_request(WINBINDD_CHECK_MACHACC, NULL, &response); + + d_printf("checking the trust secret via RPC calls %s\n", + (result == NSS_STATUS_SUCCESS) ? "succeeded" : "failed"); + + if (result != NSS_STATUS_SUCCESS) + d_printf("error code was %s (0x%x)\n", + response.data.auth.nt_status_string, + response.data.auth.nt_status); + + return result == NSS_STATUS_SUCCESS; +} + +/* Convert uid to sid */ + +static BOOL wbinfo_uid_to_sid(uid_t uid) +{ + struct winbindd_request request; + struct winbindd_response response; + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + /* Send request */ + + request.data.uid = uid; + + if (winbindd_request(WINBINDD_UID_TO_SID, &request, &response) != + NSS_STATUS_SUCCESS) + return False; + + /* Display response */ + + d_printf("%s\n", response.data.sid.sid); + + return True; +} + +/* Convert gid to sid */ + +static BOOL wbinfo_gid_to_sid(gid_t gid) +{ + struct winbindd_request request; + struct winbindd_response response; + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + /* Send request */ + + request.data.gid = gid; + + if (winbindd_request(WINBINDD_GID_TO_SID, &request, &response) != + NSS_STATUS_SUCCESS) + return False; + + /* Display response */ + + d_printf("%s\n", response.data.sid.sid); + + return True; +} + +/* Convert sid to uid */ + +static BOOL wbinfo_sid_to_uid(char *sid) +{ + struct winbindd_request request; + struct winbindd_response response; + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + /* Send request */ + + fstrcpy(request.data.sid, sid); + + if (winbindd_request(WINBINDD_SID_TO_UID, &request, &response) != + NSS_STATUS_SUCCESS) + return False; + + /* Display response */ + + d_printf("%d\n", (int)response.data.uid); + + return True; +} + +static BOOL wbinfo_sid_to_gid(char *sid) +{ + struct winbindd_request request; + struct winbindd_response response; + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + /* Send request */ + + fstrcpy(request.data.sid, sid); + + if (winbindd_request(WINBINDD_SID_TO_GID, &request, &response) != + NSS_STATUS_SUCCESS) + return False; + + /* Display response */ + + d_printf("%d\n", (int)response.data.gid); + + return True; +} + +/* Convert sid to string */ + +static BOOL wbinfo_lookupsid(char *sid) +{ + struct winbindd_request request; + struct winbindd_response response; + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + /* Send off request */ + + fstrcpy(request.data.sid, sid); + + if (winbindd_request(WINBINDD_LOOKUPSID, &request, &response) != + NSS_STATUS_SUCCESS) + return False; + + /* Display response */ + + d_printf("%s%c%s %d\n", response.data.name.dom_name, + winbind_separator(), response.data.name.name, + response.data.name.type); + + return True; +} + +/* Convert string to sid */ + +static BOOL wbinfo_lookupname(char *name) +{ + struct winbindd_request request; + struct winbindd_response response; + + /* Send off request */ + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + parse_wbinfo_domain_user(name, request.data.name.dom_name, + request.data.name.name); + + if (winbindd_request(WINBINDD_LOOKUPNAME, &request, &response) != + NSS_STATUS_SUCCESS) + return False; + + /* Display response */ + + d_printf("%s %d\n", response.data.sid.sid, response.data.sid.type); + + return True; +} + +/* Authenticate a user with a plaintext password */ + +static BOOL wbinfo_auth(char *username) +{ + struct winbindd_request request; + struct winbindd_response response; + NSS_STATUS result; + char *p; + + /* Send off request */ + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + p = strchr(username, '%'); + + if (p) { + *p = 0; + fstrcpy(request.data.auth.user, username); + fstrcpy(request.data.auth.pass, p + 1); + *p = '%'; + } else + fstrcpy(request.data.auth.user, username); + + result = winbindd_request(WINBINDD_PAM_AUTH, &request, &response); + + /* Display response */ + + d_printf("plaintext password authentication %s\n", + (result == NSS_STATUS_SUCCESS) ? "succeeded" : "failed"); + + if (response.data.auth.nt_status) + d_printf("error code was %s (0x%x)\n", + response.data.auth.nt_status_string, + response.data.auth.nt_status); + + return result == NSS_STATUS_SUCCESS; +} + +/* Authenticate a user with a challenge/response */ + +static BOOL wbinfo_auth_crap(char *username) +{ + struct winbindd_request request; + struct winbindd_response response; + NSS_STATUS result; + fstring name_user; + fstring name_domain; + fstring pass; + char *p; + + /* Send off request */ + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + p = strchr(username, '%'); + + if (p) { + *p = 0; + fstrcpy(pass, p + 1); + } + + parse_wbinfo_domain_user(username, name_domain, name_user); + + fstrcpy(request.data.auth_crap.user, name_user); + + fstrcpy(request.data.auth_crap.domain, name_domain); + + generate_random_buffer(request.data.auth_crap.chal, 8, False); + + SMBencrypt(pass, request.data.auth_crap.chal, + (uchar *)request.data.auth_crap.lm_resp); + SMBNTencrypt(pass, request.data.auth_crap.chal, + (uchar *)request.data.auth_crap.nt_resp); + + request.data.auth_crap.lm_resp_len = 24; + request.data.auth_crap.nt_resp_len = 24; + + result = winbindd_request(WINBINDD_PAM_AUTH_CRAP, &request, &response); + + /* Display response */ + + d_printf("challenge/response password authentication %s\n", + (result == NSS_STATUS_SUCCESS) ? "succeeded" : "failed"); + + if (response.data.auth.nt_status) + d_printf("error code was %s (0x%x)\n", + response.data.auth.nt_status_string, + response.data.auth.nt_status); + + return result == NSS_STATUS_SUCCESS; +} + +/* Print domain users */ + +static BOOL print_domain_users(void) +{ + struct winbindd_response response; + const char *extra_data; + fstring name; + + /* Send request to winbind daemon */ + + ZERO_STRUCT(response); + + if (winbindd_request(WINBINDD_LIST_USERS, NULL, &response) != + NSS_STATUS_SUCCESS) + return False; + + /* Look through extra data */ + + if (!response.extra_data) + return False; + + extra_data = (const char *)response.extra_data; + + while(next_token(&extra_data, name, ",", sizeof(fstring))) + d_printf("%s\n", name); + + SAFE_FREE(response.extra_data); + + return True; +} + +/* Print domain groups */ + +static BOOL print_domain_groups(void) +{ + struct winbindd_response response; + const char *extra_data; + fstring name; + + ZERO_STRUCT(response); + + if (winbindd_request(WINBINDD_LIST_GROUPS, NULL, &response) != + NSS_STATUS_SUCCESS) + return False; + + /* Look through extra data */ + + if (!response.extra_data) + return False; + + extra_data = (const char *)response.extra_data; + + while(next_token(&extra_data, name, ",", sizeof(fstring))) + d_printf("%s\n", name); + + SAFE_FREE(response.extra_data); + + return True; +} + +/* Set the authorised user for winbindd access in secrets.tdb */ + +static BOOL wbinfo_set_auth_user(char *username) +{ + char *password; + fstring user, domain; + + /* Separate into user and password */ + + parse_wbinfo_domain_user(username, domain, user); + + password = strchr(user, '%'); + + if (password) { + *password = 0; + password++; + } else + password = ""; + + /* Store or remove DOMAIN\username%password in secrets.tdb */ + + secrets_init(); + + if (user[0]) { + + if (!secrets_store(SECRETS_AUTH_USER, user, + strlen(user) + 1)) { + d_fprintf(stderr, "error storing username\n"); + return False; + } + + /* We always have a domain name added by the + parse_wbinfo_domain_user() function. */ + + if (!secrets_store(SECRETS_AUTH_DOMAIN, domain, + strlen(domain) + 1)) { + d_fprintf(stderr, "error storing domain name\n"); + return False; + } + + } else { + secrets_delete(SECRETS_AUTH_USER); + secrets_delete(SECRETS_AUTH_DOMAIN); + } + + if (password[0]) { + + if (!secrets_store(SECRETS_AUTH_PASSWORD, password, + strlen(password) + 1)) { + d_fprintf(stderr, "error storing password\n"); + return False; + } + + } else + secrets_delete(SECRETS_AUTH_PASSWORD); + + return True; +} + +static void wbinfo_get_auth_user(void) +{ + char *user, *domain, *password; + + /* Lift data from secrets file */ + + secrets_init(); + + user = secrets_fetch(SECRETS_AUTH_USER, NULL); + domain = secrets_fetch(SECRETS_AUTH_DOMAIN, NULL); + password = secrets_fetch(SECRETS_AUTH_PASSWORD, NULL); + + if (!user && !domain && !password) { + d_printf("No authorised user configured\n"); + return; + } + + /* Pretty print authorised user info */ + + d_printf("%s%s%s%s%s\n", domain ? domain : "", domain ? "\\" : "", + user, password ? "%" : "", password ? password : ""); + + SAFE_FREE(user); + SAFE_FREE(domain); + SAFE_FREE(password); +} + +static BOOL wbinfo_ping(void) +{ + NSS_STATUS result; + + result = winbindd_request(WINBINDD_PING, NULL, NULL); + + /* Display response */ + + d_printf("'ping' to winbindd %s on fd %d\n", + (result == NSS_STATUS_SUCCESS) ? "succeeded" : "failed", winbindd_fd); + + return result == NSS_STATUS_SUCCESS; +} + +/* Main program */ + +enum { + OPT_SET_AUTH_USER = 1000, + OPT_GET_AUTH_USER, + OPT_SEQUENCE +}; + +int main(int argc, char **argv) +{ + int opt; + + poptContext pc; + static char *string_arg; + static int int_arg; + BOOL got_command = False; + int result = 1; + + struct poptOption long_options[] = { + POPT_AUTOHELP + + /* longName, shortName, argInfo, argPtr, value, descrip, + argDesc */ + + { "domain-users", 'u', POPT_ARG_NONE, 0, 'u', "Lists all domain users"}, + { "domain-groups", 'g', POPT_ARG_NONE, 0, 'g', "Lists all domain groups" }, + { "WINS-by-name", 'N', POPT_ARG_STRING, &string_arg, 'N', "Converts NetBIOS name to IP (WINS)", "NETBIOS-NAME" }, + { "WINS-by-ip", 'I', POPT_ARG_STRING, &string_arg, 'I', "Converts IP address to NetBIOS name (WINS)", "IP" }, + { "name-to-sid", 'n', POPT_ARG_STRING, &string_arg, 'n', "Converts name to sid", "NAME" }, + { "sid-to-name", 's', POPT_ARG_STRING, &string_arg, 's', "Converts sid to name", "SID" }, + { "uid-to-sid", 'U', POPT_ARG_INT, &int_arg, 'U', "Converts uid to sid" , "UID" }, + { "gid-to-sid", 'G', POPT_ARG_INT, &int_arg, 'G', "Converts gid to sid", "GID" }, + { "sid-to-uid", 'S', POPT_ARG_STRING, &string_arg, 'S', "Converts sid to uid", "SID" }, + { "sid-to-gid", 'Y', POPT_ARG_STRING, &string_arg, 'Y', "Converts sid to gid", "SID" }, + { "check-secret", 't', POPT_ARG_NONE, 0, 't', "Check shared secret" }, + { "trusted-domains", 'm', POPT_ARG_NONE, 0, 'm', "List trusted domains" }, + { "sequence", 0, POPT_ARG_NONE, 0, OPT_SEQUENCE, "show sequence numbers of all domains" }, + { "user-groups", 'r', POPT_ARG_STRING, &string_arg, 'r', "Get user groups", "USER" }, + { "authenticate", 'a', POPT_ARG_STRING, &string_arg, 'a', "authenticate user", "user%password" }, + { "set-auth-user", 'A', POPT_ARG_STRING, &string_arg, OPT_SET_AUTH_USER, "Store user and password used by winbindd (root only)", "user%password" }, + { "get-auth-user", 0, POPT_ARG_NONE, NULL, OPT_GET_AUTH_USER, "Retrieve user and password used by winbindd (root only)", NULL }, + { "ping", 'p', POPT_ARG_NONE, 0, 'p', "'ping' winbindd to see if it is alive" }, + { NULL, 0, POPT_ARG_INCLUDE_TABLE, popt_common_version}, + { 0, 0, 0, 0 } + }; + + /* Samba client initialisation */ + + if (!lp_load(dyn_CONFIGFILE, True, False, False)) { + d_fprintf(stderr, "wbinfo: error opening config file %s. Error was %s\n", + dyn_CONFIGFILE, strerror(errno)); + exit(1); + } + + if (!init_names()) + return 1; + + load_interfaces(); + + /* Parse options */ + + pc = poptGetContext("wbinfo", argc, (const char **)argv, long_options, 0); + + /* Parse command line options */ + + if (argc == 1) { + poptPrintHelp(pc, stderr, 0); + return 1; + } + + while((opt = poptGetNextOpt(pc)) != -1) { + if (got_command) { + d_fprintf(stderr, "No more than one command may be specified at once.\n"); + exit(1); + } + got_command = True; + } + + poptFreeContext(pc); + + pc = poptGetContext(NULL, argc, (const char **)argv, long_options, + POPT_CONTEXT_KEEP_FIRST); + + while((opt = poptGetNextOpt(pc)) != -1) { + switch (opt) { + case 'u': + if (!print_domain_users()) { + d_printf("Error looking up domain users\n"); + goto done; + } + break; + case 'g': + if (!print_domain_groups()) { + d_printf("Error looking up domain groups\n"); + goto done; + } + break; + case 's': + if (!wbinfo_lookupsid(string_arg)) { + d_printf("Could not lookup sid %s\n", string_arg); + goto done; + } + break; + case 'n': + if (!wbinfo_lookupname(string_arg)) { + d_printf("Could not lookup name %s\n", string_arg); + goto done; + } + break; + case 'N': + if (!wbinfo_wins_byname(string_arg)) { + d_printf("Could not lookup WINS by name %s\n", string_arg); + goto done; + } + break; + case 'I': + if (!wbinfo_wins_byip(string_arg)) { + d_printf("Could not lookup WINS by IP %s\n", string_arg); + goto done; + } + break; + case 'U': + if (!wbinfo_uid_to_sid(int_arg)) { + d_printf("Could not convert uid %d to sid\n", int_arg); + goto done; + } + break; + case 'G': + if (!wbinfo_gid_to_sid(int_arg)) { + d_printf("Could not convert gid %d to sid\n", + int_arg); + goto done; + } + break; + case 'S': + if (!wbinfo_sid_to_uid(string_arg)) { + d_printf("Could not convert sid %s to uid\n", + string_arg); + goto done; + } + break; + case 'Y': + if (!wbinfo_sid_to_gid(string_arg)) { + d_printf("Could not convert sid %s to gid\n", + string_arg); + goto done; + } + break; + case 't': + if (!wbinfo_check_secret()) { + d_printf("Could not check secret\n"); + goto done; + } + break; + case 'm': + if (!wbinfo_list_domains()) { + d_printf("Could not list trusted domains\n"); + goto done; + } + break; + case OPT_SEQUENCE: + if (!wbinfo_show_sequence()) { + d_printf("Could not show sequence numbers\n"); + goto done; + } + break; + case 'r': + if (!wbinfo_get_usergroups(string_arg)) { + d_printf("Could not get groups for user %s\n", + string_arg); + goto done; + } + break; + case 'a': { + BOOL got_error = False; + + if (!wbinfo_auth(string_arg)) { + d_printf("Could not authenticate user %s with " + "plaintext password\n", string_arg); + got_error = True; + } + + if (!wbinfo_auth_crap(string_arg)) { + d_printf("Could not authenticate user %s with " + "challenge/response\n", string_arg); + got_error = True; + } + + if (got_error) + goto done; + break; + } + case 'p': { + if (!wbinfo_ping()) { + d_printf("could not ping winbindd!\n"); + goto done; + } + break; + } + case OPT_SET_AUTH_USER: + wbinfo_set_auth_user(string_arg); + break; + case OPT_GET_AUTH_USER: + wbinfo_get_auth_user(); + break; + default: + d_fprintf(stderr, "Invalid option\n"); + poptPrintHelp(pc, stderr, 0); + goto done; + } + } + + result = 0; + + /* Exit code */ + + done: + poptFreeContext(pc); + return result; +} diff --git a/source4/nsswitch/winbind_client.h b/source4/nsswitch/winbind_client.h new file mode 100644 index 0000000000..4de2d57cc7 --- /dev/null +++ b/source4/nsswitch/winbind_client.h @@ -0,0 +1,16 @@ +#include "winbind_nss_config.h" +#include "winbindd_nss.h" + +void init_request(struct winbindd_request *req,int rq_type); +NSS_STATUS winbindd_send_request(int req_type, + struct winbindd_request *request); +NSS_STATUS winbindd_get_response(struct winbindd_response *response); +NSS_STATUS winbindd_request(int req_type, + struct winbindd_request *request, + struct winbindd_response *response); +int winbind_open_pipe_sock(void); +int write_sock(void *buffer, int count); +int read_reply(struct winbindd_response *response); +void close_sock(void); +void free_response(struct winbindd_response *response); + diff --git a/source4/nsswitch/winbind_nss.c b/source4/nsswitch/winbind_nss.c new file mode 100644 index 0000000000..0b4c0ce1d0 --- /dev/null +++ b/source4/nsswitch/winbind_nss.c @@ -0,0 +1,1341 @@ +/* + Unix SMB/CIFS implementation. + + Windows NT Domain nsswitch module + + Copyright (C) Tim Potter 2000 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include "winbind_client.h" + +#ifdef HAVE_NS_API_H +#undef VOLATILE + +#include <ns_daemon.h> +#endif + +#define MAX_GETPWENT_USERS 250 +#define MAX_GETGRENT_USERS 250 + +/* Prototypes from wb_common.c */ + +extern int winbindd_fd; + + +#ifdef HAVE_NS_API_H +/* IRIX version */ + +static int send_next_request(nsd_file_t *, struct winbindd_request *); +static int do_list(int state, nsd_file_t *rq); + +static nsd_file_t *current_rq = NULL; +static int current_winbind_xid = 0; +static int next_winbind_xid = 0; + +typedef struct winbind_xid { + int xid; + nsd_file_t *rq; + struct winbindd_request *request; + struct winbind_xid *next; +} winbind_xid_t; + +static winbind_xid_t *winbind_xids = (winbind_xid_t *)0; + +static int +winbind_xid_new(int xid, nsd_file_t *rq, struct winbindd_request *request) +{ + winbind_xid_t *new; + + nsd_logprintf(NSD_LOG_LOW, + "entering winbind_xid_new xid = %d rq = 0x%x, request = 0x%x\n", + xid, rq, request); + new = (winbind_xid_t *)nsd_calloc(1,sizeof(winbind_xid_t)); + if (!new) { + nsd_logprintf(NSD_LOG_RESOURCE,"winbind_xid_new: failed malloc\n"); + return NSD_ERROR; + } + + new->xid = xid; + new->rq = rq; + new->request = request; + new->next = winbind_xids; + winbind_xids = new; + + return NSD_CONTINUE; +} + +/* +** This routine will look down the xid list and return the request +** associated with an xid. We remove the record if it is found. +*/ +nsd_file_t * +winbind_xid_lookup(int xid, struct winbindd_request **requestp) +{ + winbind_xid_t **last, *dx; + nsd_file_t *result=0; + + for (last = &winbind_xids, dx = winbind_xids; dx && (dx->xid != xid); + last = &dx->next, dx = dx->next); + if (dx) { + *last = dx->next; + result = dx->rq; + *requestp = dx->request; + SAFE_FREE(dx); + } + nsd_logprintf(NSD_LOG_LOW, + "entering winbind_xid_lookup xid = %d rq = 0x%x, request = 0x%x\n", + xid, result, dx->request); + + return result; +} + +static int +winbind_startnext_timeout(nsd_file_t **rqp, nsd_times_t *to) +{ + nsd_file_t *rq; + struct winbindd_request *request; + + nsd_logprintf(NSD_LOG_MIN, "timeout (winbind startnext)\n"); + rq = to->t_file; + *rqp = rq; + nsd_timeout_remove(rq); + request = to->t_clientdata; + return(send_next_request(rq, request)); +} + +static void +dequeue_request() +{ + nsd_file_t *rq; + struct winbindd_request *request; + + /* + * Check for queued requests + */ + if (winbind_xids) { + nsd_logprintf(NSD_LOG_MIN, "timeout (winbind) unqueue xid %d\n", + current_winbind_xid); + rq = winbind_xid_lookup(current_winbind_xid++, &request); + /* cause a timeout on the queued request so we can send it */ + nsd_timeout_new(rq,1,winbind_startnext_timeout,request); + } +} + +static int +do_request(nsd_file_t *rq, struct winbindd_request *request) +{ + if (winbind_xids == NULL) { + /* + * No outstanding requests. + * Send off the request to winbindd + */ + nsd_logprintf(NSD_LOG_MIN, "lookup (winbind) sending request\n"); + return(send_next_request(rq, request)); + } else { + /* + * Just queue it up for now - previous callout or timout + * will start it up + */ + nsd_logprintf(NSD_LOG_MIN, + "lookup (winbind): queue request xid = %d\n", + next_winbind_xid); + return(winbind_xid_new(next_winbind_xid++, rq, request)); + } +} + +static int +winbind_callback(nsd_file_t **rqp, int fd) +{ + struct winbindd_response response; + struct winbindd_pw *pw = &response.data.pw; + struct winbindd_gr *gr = &response.data.gr; + nsd_file_t *rq; + NSS_STATUS status; + fstring result; + char *members; + int i, maxlen; + + dequeue_request(); + + nsd_logprintf(NSD_LOG_MIN, "entering callback (winbind)\n"); + + rq = current_rq; + *rqp = rq; + + nsd_timeout_remove(rq); + nsd_callback_remove(fd); + + ZERO_STRUCT(response); + status = winbindd_get_response(&response); + + if (status != NSS_STATUS_SUCCESS) { + /* free any extra data area in response structure */ + free_response(&response); + nsd_logprintf(NSD_LOG_MIN, + "callback (winbind) returning not found, status = %d\n", + status); + rq->f_status = NS_NOTFOUND; + return NSD_NEXT; + } + + maxlen = sizeof(result) - 1; + + switch ((int)rq->f_cmd_data) { + case WINBINDD_WINS_BYNAME: + case WINBINDD_WINS_BYIP: + snprintf(result,maxlen,"%s\n",response.data.winsresp); + break; + case WINBINDD_GETPWUID: + case WINBINDD_GETPWNAM: + snprintf(result,maxlen,"%s:%s:%d:%d:%s:%s:%s\n", + pw->pw_name, + pw->pw_passwd, + pw->pw_uid, + pw->pw_gid, + pw->pw_gecos, + pw->pw_dir, + pw->pw_shell); + break; + case WINBINDD_GETGRNAM: + case WINBINDD_GETGRGID: + if (gr->num_gr_mem && response.extra_data) + members = response.extra_data; + else + members = ""; + snprintf(result,maxlen,"%s:%s:%d:%s\n", + gr->gr_name, gr->gr_passwd, gr->gr_gid, members); + break; + case WINBINDD_SETGRENT: + case WINBINDD_SETPWENT: + nsd_logprintf(NSD_LOG_MIN, "callback (winbind) - SETPWENT/SETGRENT\n"); + free_response(&response); + return(do_list(1,rq)); + case WINBINDD_GETGRENT: + case WINBINDD_GETGRLST: + nsd_logprintf(NSD_LOG_MIN, + "callback (winbind) - %d GETGRENT responses\n", + response.data.num_entries); + if (response.data.num_entries) { + gr = (struct winbindd_gr *)response.extra_data; + if (! gr ) { + nsd_logprintf(NSD_LOG_MIN, " no extra_data\n"); + free_response(&response); + return NSD_ERROR; + } + members = (char *)response.extra_data + + (response.data.num_entries * sizeof(struct winbindd_gr)); + for (i = 0; i < response.data.num_entries; i++) { + snprintf(result,maxlen,"%s:%s:%d:%s\n", + gr->gr_name, gr->gr_passwd, gr->gr_gid, + &members[gr->gr_mem_ofs]); + nsd_logprintf(NSD_LOG_MIN, " GETGRENT %s\n",result); + nsd_append_element(rq,NS_SUCCESS,result,strlen(result)); + gr++; + } + } + i = response.data.num_entries; + free_response(&response); + if (i < MAX_GETPWENT_USERS) + return(do_list(2,rq)); + else + return(do_list(1,rq)); + case WINBINDD_GETPWENT: + nsd_logprintf(NSD_LOG_MIN, + "callback (winbind) - %d GETPWENT responses\n", + response.data.num_entries); + if (response.data.num_entries) { + pw = (struct winbindd_pw *)response.extra_data; + if (! pw ) { + nsd_logprintf(NSD_LOG_MIN, " no extra_data\n"); + free_response(&response); + return NSD_ERROR; + } + for (i = 0; i < response.data.num_entries; i++) { + snprintf(result,maxlen,"%s:%s:%d:%d:%s:%s:%s", + pw->pw_name, + pw->pw_passwd, + pw->pw_uid, + pw->pw_gid, + pw->pw_gecos, + pw->pw_dir, + pw->pw_shell); + nsd_logprintf(NSD_LOG_MIN, " GETPWENT %s\n",result); + nsd_append_element(rq,NS_SUCCESS,result,strlen(result)); + pw++; + } + } + i = response.data.num_entries; + free_response(&response); + if (i < MAX_GETPWENT_USERS) + return(do_list(2,rq)); + else + return(do_list(1,rq)); + case WINBINDD_ENDGRENT: + case WINBINDD_ENDPWENT: + nsd_logprintf(NSD_LOG_MIN, "callback (winbind) - ENDPWENT/ENDGRENT\n"); + nsd_append_element(rq,NS_SUCCESS,"\n",1); + free_response(&response); + return NSD_NEXT; + default: + free_response(&response); + nsd_logprintf(NSD_LOG_MIN, "callback (winbind) - no valid command\n"); + return NSD_NEXT; + } + nsd_logprintf(NSD_LOG_MIN, "callback (winbind) %s\n", result); + /* free any extra data area in response structure */ + free_response(&response); + nsd_set_result(rq,NS_SUCCESS,result,strlen(result),VOLATILE); + return NSD_OK; +} + +static int +winbind_timeout(nsd_file_t **rqp, nsd_times_t *to) +{ + nsd_file_t *rq; + + dequeue_request(); + + nsd_logprintf(NSD_LOG_MIN, "timeout (winbind)\n"); + + rq = to->t_file; + *rqp = rq; + + /* Remove the callback and timeout */ + nsd_callback_remove(winbindd_fd); + nsd_timeout_remove(rq); + + rq->f_status = NS_NOTFOUND; + return NSD_NEXT; +} + +static int +send_next_request(nsd_file_t *rq, struct winbindd_request *request) +{ + NSS_STATUS status; + long timeout; + + timeout = 1000; + + nsd_logprintf(NSD_LOG_MIN, "send_next_request (winbind) %d to = %d\n", + rq->f_cmd_data, timeout); + status = winbindd_send_request((int)rq->f_cmd_data,request); + SAFE_FREE(request); + + if (status != NSS_STATUS_SUCCESS) { + nsd_logprintf(NSD_LOG_MIN, + "send_next_request (winbind) error status = %d\n",status); + rq->f_status = status; + return NSD_NEXT; + } + + current_rq = rq; + + /* + * Set up callback and timeouts + */ + nsd_logprintf(NSD_LOG_MIN, "send_next_request (winbind) fd = %d\n",winbindd_fd); + nsd_callback_new(winbindd_fd,winbind_callback,NSD_READ); + nsd_timeout_new(rq,timeout,winbind_timeout,(void *)0); + return NSD_CONTINUE; +} + +int init(void) +{ + nsd_logprintf(NSD_LOG_MIN, "entering init (winbind)\n"); + return(NSD_OK); +} + +int lookup(nsd_file_t *rq) +{ + char *map; + char *key; + struct winbindd_request *request; + + nsd_logprintf(NSD_LOG_MIN, "entering lookup (winbind)\n"); + if (! rq) + return NSD_ERROR; + + map = nsd_attr_fetch_string(rq->f_attrs, "table", (char*)0); + key = nsd_attr_fetch_string(rq->f_attrs, "key", (char*)0); + if (! map || ! key) { + nsd_logprintf(NSD_LOG_MIN, "lookup (winbind) table or key not defined\n"); + rq->f_status = NS_BADREQ; + return NSD_ERROR; + } + + nsd_logprintf(NSD_LOG_MIN, "lookup (winbind %s)\n",map); + + request = (struct winbindd_request *)nsd_calloc(1,sizeof(struct winbindd_request)); + if (! request) { + nsd_logprintf(NSD_LOG_RESOURCE, + "lookup (winbind): failed malloc\n"); + return NSD_ERROR; + } + + if (strcasecmp(map,"passwd.byuid") == 0) { + request->data.uid = atoi(key); + rq->f_cmd_data = (void *)WINBINDD_GETPWUID; + } else if (strcasecmp(map,"passwd.byname") == 0) { + strncpy(request->data.username, key, + sizeof(request->data.username) - 1); + request->data.username[sizeof(request->data.username) - 1] = '\0'; + rq->f_cmd_data = (void *)WINBINDD_GETPWNAM; + } else if (strcasecmp(map,"group.byname") == 0) { + strncpy(request->data.groupname, key, + sizeof(request->data.groupname) - 1); + request->data.groupname[sizeof(request->data.groupname) - 1] = '\0'; + rq->f_cmd_data = (void *)WINBINDD_GETGRNAM; + } else if (strcasecmp(map,"group.bygid") == 0) { + request->data.gid = atoi(key); + rq->f_cmd_data = (void *)WINBINDD_GETGRGID; + } else if (strcasecmp(map,"hosts.byname") == 0) { + strncpy(request->data.winsreq, key, sizeof(request->data.winsreq) - 1); + request->data.winsreq[sizeof(request->data.winsreq) - 1] = '\0'; + rq->f_cmd_data = (void *)WINBINDD_WINS_BYNAME; + } else if (strcasecmp(map,"hosts.byaddr") == 0) { + strncpy(request->data.winsreq, key, sizeof(request->data.winsreq) - 1); + request->data.winsreq[sizeof(request->data.winsreq) - 1] = '\0'; + rq->f_cmd_data = (void *)WINBINDD_WINS_BYIP; + } else { + /* + * Don't understand this map - just return not found + */ + nsd_logprintf(NSD_LOG_MIN, "lookup (winbind) unknown table\n"); + SAFE_FREE(request); + rq->f_status = NS_NOTFOUND; + return NSD_NEXT; + } + + return(do_request(rq, request)); +} + +int list(nsd_file_t *rq) +{ + char *map; + + nsd_logprintf(NSD_LOG_MIN, "entering list (winbind)\n"); + if (! rq) + return NSD_ERROR; + + map = nsd_attr_fetch_string(rq->f_attrs, "table", (char*)0); + if (! map ) { + nsd_logprintf(NSD_LOG_MIN, "list (winbind) table not defined\n"); + rq->f_status = NS_BADREQ; + return NSD_ERROR; + } + + nsd_logprintf(NSD_LOG_MIN, "list (winbind %s)\n",map); + + return (do_list(0,rq)); +} + +static int +do_list(int state, nsd_file_t *rq) +{ + char *map; + struct winbindd_request *request; + + nsd_logprintf(NSD_LOG_MIN, "entering do_list (winbind) state = %d\n",state); + + map = nsd_attr_fetch_string(rq->f_attrs, "table", (char*)0); + request = (struct winbindd_request *)nsd_calloc(1,sizeof(struct winbindd_request)); + if (! request) { + nsd_logprintf(NSD_LOG_RESOURCE, + "do_list (winbind): failed malloc\n"); + return NSD_ERROR; + } + + if (strcasecmp(map,"passwd.byname") == 0) { + switch (state) { + case 0: + rq->f_cmd_data = (void *)WINBINDD_SETPWENT; + break; + case 1: + request->data.num_entries = MAX_GETPWENT_USERS; + rq->f_cmd_data = (void *)WINBINDD_GETPWENT; + break; + case 2: + rq->f_cmd_data = (void *)WINBINDD_ENDPWENT; + break; + default: + nsd_logprintf(NSD_LOG_MIN, "do_list (winbind) unknown state\n"); + SAFE_FREE(request); + rq->f_status = NS_NOTFOUND; + return NSD_NEXT; + } + } else if (strcasecmp(map,"group.byname") == 0) { + switch (state) { + case 0: + rq->f_cmd_data = (void *)WINBINDD_SETGRENT; + break; + case 1: + request->data.num_entries = MAX_GETGRENT_USERS; + rq->f_cmd_data = (void *)WINBINDD_GETGRENT; + break; + case 2: + rq->f_cmd_data = (void *)WINBINDD_ENDGRENT; + break; + default: + nsd_logprintf(NSD_LOG_MIN, "do_list (winbind) unknown state\n"); + SAFE_FREE(request); + rq->f_status = NS_NOTFOUND; + return NSD_NEXT; + } + } else { + /* + * Don't understand this map - just return not found + */ + nsd_logprintf(NSD_LOG_MIN, "do_list (winbind) unknown table\n"); + SAFE_FREE(request); + rq->f_status = NS_NOTFOUND; + return NSD_NEXT; + } + + return(do_request(rq, request)); +} + +#else + +/* Allocate some space from the nss static buffer. The buffer and buflen + are the pointers passed in by the C library to the _nss_ntdom_* + functions. */ + +static char *get_static(char **buffer, int *buflen, int len) +{ + char *result; + + /* Error check. We return false if things aren't set up right, or + there isn't enough buffer space left. */ + + if ((buffer == NULL) || (buflen == NULL) || (*buflen < len)) { + return NULL; + } + + /* Return an index into the static buffer */ + + result = *buffer; + *buffer += len; + *buflen -= len; + + return result; +} + +/* I've copied the strtok() replacement function next_token() from + lib/util_str.c as I really don't want to have to link in any other + objects if I can possibly avoid it. */ + +BOOL next_token(char **ptr,char *buff,char *sep, size_t bufsize) +{ + char *s; + BOOL quoted; + size_t len=1; + + if (!ptr) return(False); + + s = *ptr; + + /* default to simple separators */ + if (!sep) sep = " \t\n\r"; + + /* find the first non sep char */ + while (*s && strchr(sep,*s)) s++; + + /* nothing left? */ + if (! *s) return(False); + + /* copy over the token */ + for (quoted = False; len < bufsize && *s && (quoted || !strchr(sep,*s)); s++) { + if (*s == '\"') { + quoted = !quoted; + } else { + len++; + *buff++ = *s; + } + } + + *ptr = (*s) ? s+1 : s; + *buff = 0; + + return(True); +} + + +/* Fill a pwent structure from a winbindd_response structure. We use + the static data passed to us by libc to put strings and stuff in. + Return NSS_STATUS_TRYAGAIN if we run out of memory. */ + +static NSS_STATUS fill_pwent(struct passwd *result, + struct winbindd_pw *pw, + char **buffer, size_t *buflen) +{ + /* User name */ + + if ((result->pw_name = + get_static(buffer, buflen, strlen(pw->pw_name) + 1)) == NULL) { + + /* Out of memory */ + + return NSS_STATUS_TRYAGAIN; + } + + strcpy(result->pw_name, pw->pw_name); + + /* Password */ + + if ((result->pw_passwd = + get_static(buffer, buflen, strlen(pw->pw_passwd) + 1)) == NULL) { + + /* Out of memory */ + + return NSS_STATUS_TRYAGAIN; + } + + strcpy(result->pw_passwd, pw->pw_passwd); + + /* [ug]id */ + + result->pw_uid = pw->pw_uid; + result->pw_gid = pw->pw_gid; + + /* GECOS */ + + if ((result->pw_gecos = + get_static(buffer, buflen, strlen(pw->pw_gecos) + 1)) == NULL) { + + /* Out of memory */ + + return NSS_STATUS_TRYAGAIN; + } + + strcpy(result->pw_gecos, pw->pw_gecos); + + /* Home directory */ + + if ((result->pw_dir = + get_static(buffer, buflen, strlen(pw->pw_dir) + 1)) == NULL) { + + /* Out of memory */ + + return NSS_STATUS_TRYAGAIN; + } + + strcpy(result->pw_dir, pw->pw_dir); + + /* Logon shell */ + + if ((result->pw_shell = + get_static(buffer, buflen, strlen(pw->pw_shell) + 1)) == NULL) { + + /* Out of memory */ + + return NSS_STATUS_TRYAGAIN; + } + + strcpy(result->pw_shell, pw->pw_shell); + + /* The struct passwd for Solaris has some extra fields which must + be initialised or nscd crashes. */ + +#if HAVE_PASSWD_PW_COMMENT + result->pw_comment = ""; +#endif + +#if HAVE_PASSWD_PW_AGE + result->pw_age = ""; +#endif + + return NSS_STATUS_SUCCESS; +} + +/* Fill a grent structure from a winbindd_response structure. We use + the static data passed to us by libc to put strings and stuff in. + Return NSS_STATUS_TRYAGAIN if we run out of memory. */ + +static NSS_STATUS fill_grent(struct group *result, struct winbindd_gr *gr, + char *gr_mem, char **buffer, size_t *buflen) +{ + fstring name; + int i; + char *tst; + + /* Group name */ + + if ((result->gr_name = + get_static(buffer, buflen, strlen(gr->gr_name) + 1)) == NULL) { + + /* Out of memory */ + + return NSS_STATUS_TRYAGAIN; + } + + strcpy(result->gr_name, gr->gr_name); + + /* Password */ + + if ((result->gr_passwd = + get_static(buffer, buflen, strlen(gr->gr_passwd) + 1)) == NULL) { + + /* Out of memory */ + + return NSS_STATUS_TRYAGAIN; + } + + strcpy(result->gr_passwd, gr->gr_passwd); + + /* gid */ + + result->gr_gid = gr->gr_gid; + + /* Group membership */ + + if ((gr->num_gr_mem < 0) || !gr_mem) { + gr->num_gr_mem = 0; + } + + /* this next value is a pointer to a pointer so let's align it */ + + /* Calculate number of extra bytes needed to align on pointer size boundry */ + if ((i = (unsigned long)(*buffer) % sizeof(char*)) != 0) + i = sizeof(char*) - i; + + if ((tst = get_static(buffer, buflen, ((gr->num_gr_mem + 1) * + sizeof(char *)+i))) == NULL) { + + /* Out of memory */ + + return NSS_STATUS_TRYAGAIN; + } + result->gr_mem = (char **)(tst + i); + + if (gr->num_gr_mem == 0) { + + /* Group is empty */ + + *(result->gr_mem) = NULL; + return NSS_STATUS_SUCCESS; + } + + /* Start looking at extra data */ + + i = 0; + + while(next_token((char **)&gr_mem, name, ",", sizeof(fstring))) { + + /* Allocate space for member */ + + if (((result->gr_mem)[i] = + get_static(buffer, buflen, strlen(name) + 1)) == NULL) { + + /* Out of memory */ + + return NSS_STATUS_TRYAGAIN; + } + + strcpy((result->gr_mem)[i], name); + i++; + } + + /* Terminate list */ + + (result->gr_mem)[i] = NULL; + + return NSS_STATUS_SUCCESS; +} + +/* + * NSS user functions + */ + +static struct winbindd_response getpwent_response; + +static int ndx_pw_cache; /* Current index into pwd cache */ +static int num_pw_cache; /* Current size of pwd cache */ + +/* Rewind "file pointer" to start of ntdom password database */ + +NSS_STATUS +_nss_winbind_setpwent(void) +{ +#ifdef DEBUG_NSS + fprintf(stderr, "[%5d]: setpwent\n", getpid()); +#endif + + if (num_pw_cache > 0) { + ndx_pw_cache = num_pw_cache = 0; + free_response(&getpwent_response); + } + + return winbindd_request(WINBINDD_SETPWENT, NULL, NULL); +} + +/* Close ntdom password database "file pointer" */ + +NSS_STATUS +_nss_winbind_endpwent(void) +{ +#ifdef DEBUG_NSS + fprintf(stderr, "[%5d]: endpwent\n", getpid()); +#endif + + if (num_pw_cache > 0) { + ndx_pw_cache = num_pw_cache = 0; + free_response(&getpwent_response); + } + + return winbindd_request(WINBINDD_ENDPWENT, NULL, NULL); +} + +/* Fetch the next password entry from ntdom password database */ + +NSS_STATUS +_nss_winbind_getpwent_r(struct passwd *result, char *buffer, + size_t buflen, int *errnop) +{ + NSS_STATUS ret; + struct winbindd_request request; + static int called_again; + +#ifdef DEBUG_NSS + fprintf(stderr, "[%5d]: getpwent\n", getpid()); +#endif + + /* Return an entry from the cache if we have one, or if we are + called again because we exceeded our static buffer. */ + + if ((ndx_pw_cache < num_pw_cache) || called_again) { + goto return_result; + } + + /* Else call winbindd to get a bunch of entries */ + + if (num_pw_cache > 0) { + free_response(&getpwent_response); + } + + ZERO_STRUCT(request); + ZERO_STRUCT(getpwent_response); + + request.data.num_entries = MAX_GETPWENT_USERS; + + ret = winbindd_request(WINBINDD_GETPWENT, &request, + &getpwent_response); + + if (ret == NSS_STATUS_SUCCESS) { + struct winbindd_pw *pw_cache; + + /* Fill cache */ + + ndx_pw_cache = 0; + num_pw_cache = getpwent_response.data.num_entries; + + /* Return a result */ + + return_result: + + pw_cache = getpwent_response.extra_data; + + /* Check data is valid */ + + if (pw_cache == NULL) { + return NSS_STATUS_NOTFOUND; + } + + ret = fill_pwent(result, &pw_cache[ndx_pw_cache], + &buffer, &buflen); + + /* Out of memory - try again */ + + if (ret == NSS_STATUS_TRYAGAIN) { + called_again = True; + *errnop = errno = ERANGE; + return ret; + } + + *errnop = errno = 0; + called_again = False; + ndx_pw_cache++; + + /* If we've finished with this lot of results free cache */ + + if (ndx_pw_cache == num_pw_cache) { + ndx_pw_cache = num_pw_cache = 0; + free_response(&getpwent_response); + } + } + + return ret; +} + +/* Return passwd struct from uid */ + +NSS_STATUS +_nss_winbind_getpwuid_r(uid_t uid, struct passwd *result, char *buffer, + size_t buflen, int *errnop) +{ + NSS_STATUS ret; + static struct winbindd_response response; + struct winbindd_request request; + static int keep_response=0; + + /* If our static buffer needs to be expanded we are called again */ + if (!keep_response) { + + /* Call for the first time */ + + ZERO_STRUCT(response); + ZERO_STRUCT(request); + + request.data.uid = uid; + + ret = winbindd_request(WINBINDD_GETPWUID, &request, &response); + + if (ret == NSS_STATUS_SUCCESS) { + ret = fill_pwent(result, &response.data.pw, + &buffer, &buflen); + + if (ret == NSS_STATUS_TRYAGAIN) { + keep_response = True; + *errnop = errno = ERANGE; + return ret; + } + } + + } else { + + /* We've been called again */ + + ret = fill_pwent(result, &response.data.pw, &buffer, &buflen); + + if (ret == NSS_STATUS_TRYAGAIN) { + keep_response = True; + *errnop = errno = ERANGE; + return ret; + } + + keep_response = False; + *errnop = errno = 0; + } + + free_response(&response); + return ret; +} + +/* Return passwd struct from username */ + +NSS_STATUS +_nss_winbind_getpwnam_r(const char *name, struct passwd *result, char *buffer, + size_t buflen, int *errnop) +{ + NSS_STATUS ret; + static struct winbindd_response response; + struct winbindd_request request; + static int keep_response; + +#ifdef DEBUG_NSS + fprintf(stderr, "[%5d]: getpwnam %s\n", getpid(), name); +#endif + + /* If our static buffer needs to be expanded we are called again */ + + if (!keep_response) { + + /* Call for the first time */ + + ZERO_STRUCT(response); + ZERO_STRUCT(request); + + strncpy(request.data.username, name, + sizeof(request.data.username) - 1); + request.data.username + [sizeof(request.data.username) - 1] = '\0'; + + ret = winbindd_request(WINBINDD_GETPWNAM, &request, &response); + + if (ret == NSS_STATUS_SUCCESS) { + ret = fill_pwent(result, &response.data.pw, &buffer, + &buflen); + + if (ret == NSS_STATUS_TRYAGAIN) { + keep_response = True; + *errnop = errno = ERANGE; + return ret; + } + } + + } else { + + /* We've been called again */ + + ret = fill_pwent(result, &response.data.pw, &buffer, &buflen); + + if (ret == NSS_STATUS_TRYAGAIN) { + keep_response = True; + *errnop = errno = ERANGE; + return ret; + } + + keep_response = False; + *errnop = errno = 0; + } + + free_response(&response); + return ret; +} + +/* + * NSS group functions + */ + +static struct winbindd_response getgrent_response; + +static int ndx_gr_cache; /* Current index into grp cache */ +static int num_gr_cache; /* Current size of grp cache */ + +/* Rewind "file pointer" to start of ntdom group database */ + +NSS_STATUS +_nss_winbind_setgrent(void) +{ +#ifdef DEBUG_NSS + fprintf(stderr, "[%5d]: setgrent\n", getpid()); +#endif + + if (num_gr_cache > 0) { + ndx_gr_cache = num_gr_cache = 0; + free_response(&getgrent_response); + } + + return winbindd_request(WINBINDD_SETGRENT, NULL, NULL); +} + +/* Close "file pointer" for ntdom group database */ + +NSS_STATUS +_nss_winbind_endgrent(void) +{ +#ifdef DEBUG_NSS + fprintf(stderr, "[%5d]: endgrent\n", getpid()); +#endif + + if (num_gr_cache > 0) { + ndx_gr_cache = num_gr_cache = 0; + free_response(&getgrent_response); + } + + return winbindd_request(WINBINDD_ENDGRENT, NULL, NULL); +} + +/* Get next entry from ntdom group database */ + +static NSS_STATUS +winbind_getgrent(enum winbindd_cmd cmd, + struct group *result, + char *buffer, size_t buflen, int *errnop) +{ + NSS_STATUS ret; + static struct winbindd_request request; + static int called_again; + + +#ifdef DEBUG_NSS + fprintf(stderr, "[%5d]: getgrent\n", getpid()); +#endif + + /* Return an entry from the cache if we have one, or if we are + called again because we exceeded our static buffer. */ + + if ((ndx_gr_cache < num_gr_cache) || called_again) { + goto return_result; + } + + /* Else call winbindd to get a bunch of entries */ + + if (num_gr_cache > 0) { + free_response(&getgrent_response); + } + + ZERO_STRUCT(request); + ZERO_STRUCT(getgrent_response); + + request.data.num_entries = MAX_GETGRENT_USERS; + + ret = winbindd_request(cmd, &request, + &getgrent_response); + + if (ret == NSS_STATUS_SUCCESS) { + struct winbindd_gr *gr_cache; + int mem_ofs; + + /* Fill cache */ + + ndx_gr_cache = 0; + num_gr_cache = getgrent_response.data.num_entries; + + /* Return a result */ + + return_result: + + gr_cache = getgrent_response.extra_data; + + /* Check data is valid */ + + if (gr_cache == NULL) { + return NSS_STATUS_NOTFOUND; + } + + /* Fill group membership. The offset into the extra data + for the group membership is the reported offset plus the + size of all the winbindd_gr records returned. */ + + mem_ofs = gr_cache[ndx_gr_cache].gr_mem_ofs + + num_gr_cache * sizeof(struct winbindd_gr); + + ret = fill_grent(result, &gr_cache[ndx_gr_cache], + ((char *)getgrent_response.extra_data)+mem_ofs, + &buffer, &buflen); + + /* Out of memory - try again */ + + if (ret == NSS_STATUS_TRYAGAIN) { + called_again = True; + *errnop = errno = ERANGE; + return ret; + } + + *errnop = 0; + called_again = False; + ndx_gr_cache++; + + /* If we've finished with this lot of results free cache */ + + if (ndx_gr_cache == num_gr_cache) { + ndx_gr_cache = num_gr_cache = 0; + free_response(&getgrent_response); + } + } + + return ret; +} + + +NSS_STATUS +_nss_winbind_getgrent_r(struct group *result, + char *buffer, size_t buflen, int *errnop) +{ + return winbind_getgrent(WINBINDD_GETGRENT, result, buffer, buflen, errnop); +} + +NSS_STATUS +_nss_winbind_getgrlst_r(struct group *result, + char *buffer, size_t buflen, int *errnop) +{ + return winbind_getgrent(WINBINDD_GETGRLST, result, buffer, buflen, errnop); +} + +/* Return group struct from group name */ + +NSS_STATUS +_nss_winbind_getgrnam_r(const char *name, + struct group *result, char *buffer, + size_t buflen, int *errnop) +{ + NSS_STATUS ret; + static struct winbindd_response response; + struct winbindd_request request; + static int keep_response; + +#ifdef DEBUG_NSS + fprintf(stderr, "[%5d]: getgrnam %s\n", getpid(), name); +#endif + + /* If our static buffer needs to be expanded we are called again */ + + if (!keep_response) { + + /* Call for the first time */ + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + strncpy(request.data.groupname, name, + sizeof(request.data.groupname)); + request.data.groupname + [sizeof(request.data.groupname) - 1] = '\0'; + + ret = winbindd_request(WINBINDD_GETGRNAM, &request, &response); + + if (ret == NSS_STATUS_SUCCESS) { + ret = fill_grent(result, &response.data.gr, + response.extra_data, + &buffer, &buflen); + + if (ret == NSS_STATUS_TRYAGAIN) { + keep_response = True; + *errnop = errno = ERANGE; + return ret; + } + } + + } else { + + /* We've been called again */ + + ret = fill_grent(result, &response.data.gr, + response.extra_data, &buffer, &buflen); + + if (ret == NSS_STATUS_TRYAGAIN) { + keep_response = True; + *errnop = errno = ERANGE; + return ret; + } + + keep_response = False; + *errnop = 0; + } + + free_response(&response); + return ret; +} + +/* Return group struct from gid */ + +NSS_STATUS +_nss_winbind_getgrgid_r(gid_t gid, + struct group *result, char *buffer, + size_t buflen, int *errnop) +{ + NSS_STATUS ret; + static struct winbindd_response response; + struct winbindd_request request; + static int keep_response; + +#ifdef DEBUG_NSS + fprintf(stderr, "[%5d]: getgrgid %d\n", getpid(), gid); +#endif + + /* If our static buffer needs to be expanded we are called again */ + + if (!keep_response) { + + /* Call for the first time */ + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + request.data.gid = gid; + + ret = winbindd_request(WINBINDD_GETGRGID, &request, &response); + + if (ret == NSS_STATUS_SUCCESS) { + + ret = fill_grent(result, &response.data.gr, + response.extra_data, + &buffer, &buflen); + + if (ret == NSS_STATUS_TRYAGAIN) { + keep_response = True; + *errnop = errno = ERANGE; + return ret; + } + } + + } else { + + /* We've been called again */ + + ret = fill_grent(result, &response.data.gr, + response.extra_data, &buffer, &buflen); + + if (ret == NSS_STATUS_TRYAGAIN) { + keep_response = True; + *errnop = errno = ERANGE; + return ret; + } + + keep_response = False; + *errnop = 0; + } + + free_response(&response); + return ret; +} + +/* Initialise supplementary groups */ + +NSS_STATUS +_nss_winbind_initgroups_dyn(char *user, gid_t group, long int *start, + long int *size, gid_t **groups, long int limit, + int *errnop) +{ + NSS_STATUS ret; + struct winbindd_request request; + struct winbindd_response response; + int i; + +#ifdef DEBUG_NSS + fprintf(stderr, "[%5d]: initgroups %s (%d)\n", getpid(), + user, group); +#endif + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + strncpy(request.data.username, user, + sizeof(request.data.username) - 1); + + ret = winbindd_request(WINBINDD_GETGROUPS, &request, &response); + + if (ret == NSS_STATUS_SUCCESS) { + int num_gids = response.data.num_entries; + gid_t *gid_list = (gid_t *)response.extra_data; + + /* Copy group list to client */ + + for (i = 0; i < num_gids; i++) { + + /* Skip primary group */ + + if (gid_list[i] == group) continue; + + /* Add to buffer */ + + if (*start == *size && limit <= 0) { + (*groups) = realloc( + (*groups), (2 * (*size) + 1) * sizeof(**groups)); + if (! *groups) goto done; + *size = 2 * (*size) + 1; + } + + if (*start == *size) goto done; + + (*groups)[*start] = gid_list[i]; + *start += 1; + + /* Filled buffer? */ + + if (*start == limit) goto done; + } + } + + /* Back to your regularly scheduled programming */ + + done: + return ret; +} + +#endif diff --git a/source4/nsswitch/winbind_nss_config.h b/source4/nsswitch/winbind_nss_config.h new file mode 100644 index 0000000000..2faaa30d1b --- /dev/null +++ b/source4/nsswitch/winbind_nss_config.h @@ -0,0 +1,165 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon for ntdom nss module + + Copyright (C) Tim Potter 2000 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef _WINBIND_NSS_CONFIG_H +#define _WINBIND_NSS_CONFIG_H + +/* Include header files from data in config.h file */ + +#ifndef NO_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#ifdef HAVE_UNIXSOCKET +#include <sys/un.h> +#endif + +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#ifdef HAVE_GRP_H +#include <grp.h> +#endif + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#else +#ifdef HAVE_SYS_FCNTL_H +#include <sys/fcntl.h> +#endif +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <pwd.h> +#include "nsswitch/nss.h" + +/* Declarations for functions in winbind_nss.c + needed in winbind_nss_solaris.c (solaris wrapper to nss) */ + +NSS_STATUS _nss_winbind_setpwent(void); +NSS_STATUS _nss_winbind_endpwent(void); +NSS_STATUS _nss_winbind_getpwent_r(struct passwd* result, char* buffer, + size_t buflen, int* errnop); +NSS_STATUS _nss_winbind_getpwuid_r(uid_t, struct passwd*, char* buffer, + size_t buflen, int* errnop); +NSS_STATUS _nss_winbind_getpwnam_r(const char* name, struct passwd* result, + char* buffer, size_t buflen, int* errnop); + +NSS_STATUS _nss_winbind_setgrent(void); +NSS_STATUS _nss_winbind_endgrent(void); +NSS_STATUS _nss_winbind_getgrent_r(struct group* result, char* buffer, + size_t buflen, int* errnop); +NSS_STATUS _nss_winbind_getgrnam_r(const char *name, + struct group *result, char *buffer, + size_t buflen, int *errnop); +NSS_STATUS _nss_winbind_getgrgid_r(gid_t gid, + struct group *result, char *buffer, + size_t buflen, int *errnop); + +/* I'm trying really hard not to include anything from smb.h with the + result of some silly looking redeclaration of structures. */ + +#ifndef _PSTRING +#define _PSTRING +#define PSTRING_LEN 1024 +#define FSTRING_LEN 256 +typedef char pstring[PSTRING_LEN]; +typedef char fstring[FSTRING_LEN]; +#endif + +#ifndef _BOOL +#define _BOOL /* So we don't typedef BOOL again in vfs.h */ +#define False (0) +#define True (1) +#define Auto (2) +typedef int BOOL; +#endif + +#if !defined(uint32) +#if (SIZEOF_INT == 4) +#define uint32 unsigned int +#elif (SIZEOF_LONG == 4) +#define uint32 unsigned long +#elif (SIZEOF_SHORT == 4) +#define uint32 unsigned short +#endif +#endif + +#if !defined(uint16) +#if (SIZEOF_SHORT == 4) +#define uint16 __ERROR___CANNOT_DETERMINE_TYPE_FOR_INT16; +#else /* SIZEOF_SHORT != 4 */ +#define uint16 unsigned short +#endif /* SIZEOF_SHORT != 4 */ +#endif + +#ifndef uint8 +#define uint8 unsigned char +#endif + +/* zero a structure */ +#ifndef ZERO_STRUCT +#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) +#endif + +/* zero a structure given a pointer to the structure */ +#ifndef ZERO_STRUCTP +#define ZERO_STRUCTP(x) { if ((x) != NULL) memset((char *)(x), 0, sizeof(*(x))); } +#endif + +/* Some systems (SCO) treat UNIX domain sockets as FIFOs */ + +#ifndef S_IFSOCK +#define S_IFSOCK S_IFIFO +#endif + +#ifndef S_ISSOCK +#define S_ISSOCK(mode) ((mode & S_IFSOCK) == S_IFSOCK) +#endif + +#endif diff --git a/source4/nsswitch/winbind_nss_solaris.c b/source4/nsswitch/winbind_nss_solaris.c new file mode 100644 index 0000000000..f3bd05b77a --- /dev/null +++ b/source4/nsswitch/winbind_nss_solaris.c @@ -0,0 +1,301 @@ +/* + Solaris NSS wrapper for winbind + - Shirish Kalele 2000 + + Based on Luke Howard's ldap_nss module for Solaris + */ + +/* + Copyright (C) 1997-2003 Luke Howard. + This file is part of the nss_ldap library. + + The nss_ldap library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The nss_ldap library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the nss_ldap library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include <stdlib.h> +#include <sys/types.h> +#include <sys/param.h> +#include <string.h> +#include <pwd.h> +#include "includes.h" +#include <syslog.h> +#if !defined(HPUX) +#include <sys/syslog.h> +#endif /*hpux*/ +#include "winbind_nss_config.h" + +#if defined(HAVE_NSS_COMMON_H) || defined(HPUX) + +#undef NSS_DEBUG + +#ifdef NSS_DEBUG +#define NSS_DEBUG(str) syslog(LOG_DEBUG, "nss_winbind: %s", str); +#else +#define NSS_DEBUG(str) ; +#endif + +#define NSS_ARGS(args) ((nss_XbyY_args_t *)args) + +#define make_pwent_str(dest, src) \ +{ \ + if((dest = get_static(buffer, buflen, strlen(src)+1)) == NULL) \ + { \ + *errnop = ERANGE; \ + NSS_DEBUG("ERANGE error"); \ + return NSS_STATUS_TRYAGAIN; \ + } \ + strcpy(dest, src); \ +} + +static NSS_STATUS _nss_winbind_setpwent_solwrap (nss_backend_t* be, void* args) +{ + NSS_DEBUG("_nss_winbind_setpwent_solwrap"); + return _nss_winbind_setpwent(); +} + +static NSS_STATUS +_nss_winbind_endpwent_solwrap (nss_backend_t * be, void *args) +{ + NSS_DEBUG("_nss_winbind_endpwent_solwrap"); + return _nss_winbind_endpwent(); +} + +static NSS_STATUS +_nss_winbind_getpwent_solwrap (nss_backend_t* be, void *args) +{ + NSS_STATUS ret; + char* buffer = NSS_ARGS(args)->buf.buffer; + int buflen = NSS_ARGS(args)->buf.buflen; + struct passwd* result = (struct passwd*) NSS_ARGS(args)->buf.result; + int* errnop = &NSS_ARGS(args)->erange; + char logmsg[80]; + + ret = _nss_winbind_getpwent_r(result, buffer, + buflen, errnop); + + if(ret == NSS_STATUS_SUCCESS) + { + snprintf(logmsg, 79, "_nss_winbind_getpwent_solwrap: Returning user: %s\n", + result->pw_name); + NSS_DEBUG(logmsg); + NSS_ARGS(args)->returnval = (void*) result; + } else { + snprintf(logmsg, 79, "_nss_winbind_getpwent_solwrap: Returning error: %d.\n",ret); + NSS_DEBUG(logmsg); + } + + return ret; +} + +static NSS_STATUS +_nss_winbind_getpwnam_solwrap (nss_backend_t* be, void* args) +{ + NSS_STATUS ret; + struct passwd* result = (struct passwd*) NSS_ARGS(args)->buf.result; + + NSS_DEBUG("_nss_winbind_getpwnam_solwrap"); + + ret = _nss_winbind_getpwnam_r (NSS_ARGS(args)->key.name, + result, + NSS_ARGS(args)->buf.buffer, + NSS_ARGS(args)->buf.buflen, + &NSS_ARGS(args)->erange); + if(ret == NSS_STATUS_SUCCESS) + NSS_ARGS(args)->returnval = (void*) result; + + return ret; +} + +static NSS_STATUS +_nss_winbind_getpwuid_solwrap(nss_backend_t* be, void* args) +{ + NSS_STATUS ret; + struct passwd* result = (struct passwd*) NSS_ARGS(args)->buf.result; + + NSS_DEBUG("_nss_winbind_getpwuid_solwrap"); + ret = _nss_winbind_getpwuid_r (NSS_ARGS(args)->key.uid, + result, + NSS_ARGS(args)->buf.buffer, + NSS_ARGS(args)->buf.buflen, + &NSS_ARGS(args)->erange); + if(ret == NSS_STATUS_SUCCESS) + NSS_ARGS(args)->returnval = (void*) result; + + return ret; +} + +static NSS_STATUS _nss_winbind_passwd_destr (nss_backend_t * be, void *args) +{ + SAFE_FREE(be); + NSS_DEBUG("_nss_winbind_passwd_destr"); + return NSS_STATUS_SUCCESS; +} + +static nss_backend_op_t passwd_ops[] = +{ + _nss_winbind_passwd_destr, + _nss_winbind_endpwent_solwrap, /* NSS_DBOP_ENDENT */ + _nss_winbind_setpwent_solwrap, /* NSS_DBOP_SETENT */ + _nss_winbind_getpwent_solwrap, /* NSS_DBOP_GETENT */ + _nss_winbind_getpwnam_solwrap, /* NSS_DBOP_PASSWD_BYNAME */ + _nss_winbind_getpwuid_solwrap /* NSS_DBOP_PASSWD_BYUID */ +}; + +nss_backend_t* +_nss_winbind_passwd_constr (const char* db_name, + const char* src_name, + const char* cfg_args) +{ + nss_backend_t *be; + + if(!(be = (nss_backend_t*) malloc(sizeof(nss_backend_t))) ) + return NULL; + + be->ops = passwd_ops; + be->n_ops = sizeof(passwd_ops) / sizeof(nss_backend_op_t); + + NSS_DEBUG("Initialized nss_winbind passwd backend"); + return be; +} + +/***************************************************************** + GROUP database backend + *****************************************************************/ + +static NSS_STATUS _nss_winbind_setgrent_solwrap (nss_backend_t* be, void* args) +{ + NSS_DEBUG("_nss_winbind_setgrent_solwrap"); + return _nss_winbind_setgrent(); +} + +static NSS_STATUS +_nss_winbind_endgrent_solwrap (nss_backend_t * be, void *args) +{ + NSS_DEBUG("_nss_winbind_endgrent_solwrap"); + return _nss_winbind_endgrent(); +} + +static NSS_STATUS +_nss_winbind_getgrent_solwrap(nss_backend_t* be, void* args) +{ + NSS_STATUS ret; + char* buffer = NSS_ARGS(args)->buf.buffer; + int buflen = NSS_ARGS(args)->buf.buflen; + struct group* result = (struct group*) NSS_ARGS(args)->buf.result; + int* errnop = &NSS_ARGS(args)->erange; + char logmsg[80]; + + ret = _nss_winbind_getgrent_r(result, buffer, + buflen, errnop); + + if(ret == NSS_STATUS_SUCCESS) + { + snprintf(logmsg, 79, "_nss_winbind_getgrent_solwrap: Returning group: %s\n", result->gr_name); + NSS_DEBUG(logmsg); + NSS_ARGS(args)->returnval = (void*) result; + } else { + snprintf(logmsg, 79, "_nss_winbind_getgrent_solwrap: Returning error: %d.\n", ret); + NSS_DEBUG(logmsg); + } + + return ret; + +} + +static NSS_STATUS +_nss_winbind_getgrnam_solwrap(nss_backend_t* be, void* args) +{ + NSS_STATUS ret; + struct group* result = (struct group*) NSS_ARGS(args)->buf.result; + + NSS_DEBUG("_nss_winbind_getgrnam_solwrap"); + ret = _nss_winbind_getgrnam_r(NSS_ARGS(args)->key.name, + result, + NSS_ARGS(args)->buf.buffer, + NSS_ARGS(args)->buf.buflen, + &NSS_ARGS(args)->erange); + + if(ret == NSS_STATUS_SUCCESS) + NSS_ARGS(args)->returnval = (void*) result; + + return ret; +} + +static NSS_STATUS +_nss_winbind_getgrgid_solwrap(nss_backend_t* be, void* args) +{ + NSS_STATUS ret; + struct group* result = (struct group*) NSS_ARGS(args)->buf.result; + + NSS_DEBUG("_nss_winbind_getgrgid_solwrap"); + ret = _nss_winbind_getgrgid_r (NSS_ARGS(args)->key.gid, + result, + NSS_ARGS(args)->buf.buffer, + NSS_ARGS(args)->buf.buflen, + &NSS_ARGS(args)->erange); + + if(ret == NSS_STATUS_SUCCESS) + NSS_ARGS(args)->returnval = (void*) result; + + return ret; +} + +static NSS_STATUS +_nss_winbind_getgroupsbymember_solwrap(nss_backend_t* be, void* args) +{ + NSS_DEBUG("_nss_winbind_getgroupsbymember"); + return NSS_STATUS_NOTFOUND; +} + +static NSS_STATUS +_nss_winbind_group_destr (nss_backend_t* be, void* args) +{ + SAFE_FREE(be); + NSS_DEBUG("_nss_winbind_group_destr"); + return NSS_STATUS_SUCCESS; +} + +static nss_backend_op_t group_ops[] = +{ + _nss_winbind_group_destr, + _nss_winbind_endgrent_solwrap, + _nss_winbind_setgrent_solwrap, + _nss_winbind_getgrent_solwrap, + _nss_winbind_getgrnam_solwrap, + _nss_winbind_getgrgid_solwrap, + _nss_winbind_getgroupsbymember_solwrap +}; + +nss_backend_t* +_nss_winbind_group_constr (const char* db_name, + const char* src_name, + const char* cfg_args) +{ + nss_backend_t* be; + + if(!(be = (nss_backend_t*) malloc(sizeof(nss_backend_t))) ) + return NULL; + + be->ops = group_ops; + be->n_ops = sizeof(group_ops) / sizeof(nss_backend_op_t); + + NSS_DEBUG("Initialized nss_winbind group backend"); + return be; +} + +#endif /* SUN_NSS */ + + diff --git a/source4/nsswitch/winbindd.c b/source4/nsswitch/winbindd.c new file mode 100644 index 0000000000..ad37768c09 --- /dev/null +++ b/source4/nsswitch/winbindd.c @@ -0,0 +1,951 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon for ntdom nss module + + Copyright (C) by Tim Potter 2000-2002 + Copyright (C) Andrew Tridgell 2002 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "winbindd.h" + +BOOL opt_nocache = False; +BOOL opt_dual_daemon = False; + +/* Reload configuration */ + +static BOOL reload_services_file(BOOL test) +{ + BOOL ret; + pstring logfile; + + if (lp_loaded()) { + pstring fname; + + pstrcpy(fname,lp_configfile()); + if (file_exist(fname,NULL) && !strcsequal(fname,dyn_CONFIGFILE)) { + pstrcpy(dyn_CONFIGFILE,fname); + test = False; + } + } + + snprintf(logfile, sizeof(logfile), "%s/log.winbindd", dyn_LOGFILEBASE); + lp_set_logfile(logfile); + + reopen_logs(); + ret = lp_load(dyn_CONFIGFILE,False,False,True); + + snprintf(logfile, sizeof(logfile), "%s/log.winbindd", dyn_LOGFILEBASE); + lp_set_logfile(logfile); + + reopen_logs(); + load_interfaces(); + + return(ret); +} + +/******************************************************************* + Print out all talloc memory info. +********************************************************************/ + +void return_all_talloc_info(int msg_type, pid_t src_pid, void *buf, size_t len) +{ + TALLOC_CTX *ctx = talloc_init("info context"); + char *info = NULL; + + if (!ctx) + return; + + info = talloc_describe_all(ctx); + if (info) + DEBUG(10,(info)); + message_send_pid(src_pid, MSG_TALLOC_USAGE, info, info ? strlen(info) + 1: 0, True); + talloc_destroy(ctx); +} + +#if DUMP_CORE + +/**************************************************************************** ** + Prepare to dump a core file - carefully! + **************************************************************************** */ + +static BOOL dump_core(void) +{ + char *p; + pstring dname; + pstrcpy( dname, lp_logfile() ); + if ((p=strrchr(dname,'/'))) + *p=0; + pstrcat( dname, "/corefiles" ); + mkdir( dname, 0700 ); + sys_chown( dname, getuid(), getgid() ); + chmod( dname, 0700 ); + if ( chdir(dname) ) + return( False ); + umask( ~(0700) ); + +#ifdef HAVE_GETRLIMIT +#ifdef RLIMIT_CORE + { + struct rlimit rlp; + getrlimit( RLIMIT_CORE, &rlp ); + rlp.rlim_cur = MAX( 4*1024*1024, rlp.rlim_cur ); + setrlimit( RLIMIT_CORE, &rlp ); + getrlimit( RLIMIT_CORE, &rlp ); + DEBUG( 3, ( "Core limits now %d %d\n", (int)rlp.rlim_cur, (int)rlp.rlim_max ) ); + } +#endif +#endif + + DEBUG(0,("Dumping core in %s\n",dname)); + abort(); + return( True ); +} /* dump_core */ +#endif + +/**************************************************************************** ** + Handle a fault.. + **************************************************************************** */ + +static void fault_quit(void) +{ +#if DUMP_CORE + dump_core(); +#endif +} + +static void winbindd_status(void) +{ + struct winbindd_cli_state *tmp; + + DEBUG(0, ("winbindd status:\n")); + + /* Print client state information */ + + DEBUG(0, ("\t%d clients currently active\n", winbindd_num_clients())); + + if (DEBUGLEVEL >= 2 && winbindd_num_clients()) { + DEBUG(2, ("\tclient list:\n")); + for(tmp = winbindd_client_list(); tmp; tmp = tmp->next) { + DEBUG(2, ("\t\tpid %d, sock %d, rbl %d, wbl %d\n", + tmp->pid, tmp->sock, tmp->read_buf_len, + tmp->write_buf_len)); + } + } +} + +/* Print winbindd status to log file */ + +static void print_winbindd_status(void) +{ + winbindd_status(); + winbindd_idmap_status(); + winbindd_cm_status(); +} + +/* Flush client cache */ + +static void flush_caches(void) +{ + /* Clear cached user and group enumation info */ + wcache_flush_cache(); +} + +/* Handle the signal by unlinking socket and exiting */ + +static void terminate(void) +{ + pstring path; + + winbindd_idmap_close(); + + /* Remove socket file */ + snprintf(path, sizeof(path), "%s/%s", + WINBINDD_SOCKET_DIR, WINBINDD_SOCKET_NAME); + unlink(path); + exit(0); +} + +static BOOL do_sigterm; + +static void termination_handler(int signum) +{ + do_sigterm = True; + sys_select_signal(); +} + +static BOOL do_sigusr2; + +static void sigusr2_handler(int signum) +{ + do_sigusr2 = True; + sys_select_signal(); +} + +static BOOL do_sighup; + +static void sighup_handler(int signum) +{ + do_sighup = True; + sys_select_signal(); +} + +struct dispatch_table { + enum winbindd_cmd cmd; + enum winbindd_result (*fn)(struct winbindd_cli_state *state); + const char *winbindd_cmd_name; +}; + +static struct dispatch_table dispatch_table[] = { + + /* User functions */ + + { WINBINDD_GETPWNAM, winbindd_getpwnam, "GETPWNAM" }, + { WINBINDD_GETPWUID, winbindd_getpwuid, "GETPWUID" }, + + { WINBINDD_SETPWENT, winbindd_setpwent, "SETPWENT" }, + { WINBINDD_ENDPWENT, winbindd_endpwent, "ENDPWENT" }, + { WINBINDD_GETPWENT, winbindd_getpwent, "GETPWENT" }, + + { WINBINDD_GETGROUPS, winbindd_getgroups, "GETGROUPS" }, + + /* Group functions */ + + { WINBINDD_GETGRNAM, winbindd_getgrnam, "GETGRNAM" }, + { WINBINDD_GETGRGID, winbindd_getgrgid, "GETGRGID" }, + { WINBINDD_SETGRENT, winbindd_setgrent, "SETGRENT" }, + { WINBINDD_ENDGRENT, winbindd_endgrent, "ENDGRENT" }, + { WINBINDD_GETGRENT, winbindd_getgrent, "GETGRENT" }, + { WINBINDD_GETGRLST, winbindd_getgrent, "GETGRLST" }, + + /* PAM auth functions */ + + { WINBINDD_PAM_AUTH, winbindd_pam_auth, "PAM_AUTH" }, + { WINBINDD_PAM_AUTH_CRAP, winbindd_pam_auth_crap, "AUTH_CRAP" }, + { WINBINDD_PAM_CHAUTHTOK, winbindd_pam_chauthtok, "CHAUTHTOK" }, + + /* Enumeration functions */ + + { WINBINDD_LIST_USERS, winbindd_list_users, "LIST_USERS" }, + { WINBINDD_LIST_GROUPS, winbindd_list_groups, "LIST_GROUPS" }, + { WINBINDD_LIST_TRUSTDOM, winbindd_list_trusted_domains, "LIST_TRUSTDOM" }, + { WINBINDD_SHOW_SEQUENCE, winbindd_show_sequence, "SHOW_SEQUENCE" }, + + /* SID related functions */ + + { WINBINDD_LOOKUPSID, winbindd_lookupsid, "LOOKUPSID" }, + { WINBINDD_LOOKUPNAME, winbindd_lookupname, "LOOKUPNAME" }, + + /* Lookup related functions */ + + { WINBINDD_SID_TO_UID, winbindd_sid_to_uid, "SID_TO_UID" }, + { WINBINDD_SID_TO_GID, winbindd_sid_to_gid, "SID_TO_GID" }, + { WINBINDD_GID_TO_SID, winbindd_gid_to_sid, "GID_TO_SID" }, + { WINBINDD_UID_TO_SID, winbindd_uid_to_sid, "UID_TO_SID" }, + + /* Miscellaneous */ + + { WINBINDD_CHECK_MACHACC, winbindd_check_machine_acct, "CHECK_MACHACC" }, + { WINBINDD_PING, winbindd_ping, "PING" }, + { WINBINDD_INFO, winbindd_info, "INFO" }, + { WINBINDD_INTERFACE_VERSION, winbindd_interface_version, "INTERFACE_VERSION" }, + { WINBINDD_DOMAIN_NAME, winbindd_domain_name, "DOMAIN_NAME" }, + { WINBINDD_NETBIOS_NAME, winbindd_netbios_name, "NETBIOS_NAME" }, + + /* WINS functions */ + + { WINBINDD_WINS_BYNAME, winbindd_wins_byname, "WINS_BYNAME" }, + { WINBINDD_WINS_BYIP, winbindd_wins_byip, "WINS_BYIP" }, + + /* End of list */ + + { WINBINDD_NUM_CMDS, NULL, "NONE" } +}; + +static void process_request(struct winbindd_cli_state *state) +{ + struct dispatch_table *table = dispatch_table; + + /* Free response data - we may be interrupted and receive another + command before being able to send this data off. */ + + SAFE_FREE(state->response.extra_data); + + ZERO_STRUCT(state->response); + + state->response.result = WINBINDD_ERROR; + state->response.length = sizeof(struct winbindd_response); + + /* Process command */ + + for (table = dispatch_table; table->fn; table++) { + if (state->request.cmd == table->cmd) { + DEBUG(10,("process_request: request fn %s\n", table->winbindd_cmd_name )); + state->response.result = table->fn(state); + break; + } + } + + if (!table->fn) + DEBUG(10,("process_request: unknown request fn number %d\n", (int)state->request.cmd )); + + /* In case extra data pointer is NULL */ + + if (!state->response.extra_data) + state->response.length = sizeof(struct winbindd_response); +} + +/* Process a new connection by adding it to the client connection list */ + +static void new_connection(int listen_sock) +{ + struct sockaddr_un sunaddr; + struct winbindd_cli_state *state; + socklen_t len; + int sock; + + /* Accept connection */ + + len = sizeof(sunaddr); + + do { + sock = accept(listen_sock, (struct sockaddr *)&sunaddr, &len); + } while (sock == -1 && errno == EINTR); + + if (sock == -1) + return; + + DEBUG(6,("accepted socket %d\n", sock)); + + /* Create new connection structure */ + + if ((state = (struct winbindd_cli_state *) + malloc(sizeof(*state))) == NULL) + return; + + ZERO_STRUCTP(state); + state->sock = sock; + + state->last_access = time(NULL); + + /* Add to connection list */ + + winbindd_add_client(state); +} + +/* Remove a client connection from client connection list */ + +static void remove_client(struct winbindd_cli_state *state) +{ + /* It's a dead client - hold a funeral */ + + if (state != NULL) { + + /* Close socket */ + + close(state->sock); + + /* Free any getent state */ + + free_getent_state(state->getpwent_state); + free_getent_state(state->getgrent_state); + + /* We may have some extra data that was not freed if the + client was killed unexpectedly */ + + SAFE_FREE(state->response.extra_data); + + /* Remove from list and free */ + + winbindd_remove_client(state); + SAFE_FREE(state); + } +} + + +/* Shutdown client connection which has been idle for the longest time */ + +static BOOL remove_idle_client(void) +{ + struct winbindd_cli_state *state, *remove_state = NULL; + time_t last_access = 0; + int nidle = 0; + + for (state = winbindd_client_list(); state; state = state->next) { + if (state->read_buf_len == 0 && state->write_buf_len == 0 && + !state->getpwent_state && !state->getgrent_state) { + nidle++; + if (!last_access || state->last_access < last_access) { + last_access = state->last_access; + remove_state = state; + } + } + } + + if (remove_state) { + DEBUG(5,("Found %d idle client connections, shutting down sock %d, pid %u\n", + nidle, remove_state->sock, (unsigned int)remove_state->pid)); + remove_client(remove_state); + return True; + } + + return False; +} + +/* Process a complete received packet from a client */ + +void winbind_process_packet(struct winbindd_cli_state *state) +{ + /* Process request */ + + /* Ensure null termination of entire request */ + state->request.null_term = '\0'; + + state->pid = state->request.pid; + + process_request(state); + + /* Update client state */ + + state->read_buf_len = 0; + state->write_buf_len = sizeof(struct winbindd_response); + + /* we might need to send it to the dual daemon */ + if (opt_dual_daemon) { + dual_send_request(state); + } +} + +/* Read some data from a client connection */ + +void winbind_client_read(struct winbindd_cli_state *state) +{ + int n; + + /* Read data */ + + n = sys_read(state->sock, state->read_buf_len + + (char *)&state->request, + sizeof(state->request) - state->read_buf_len); + + DEBUG(10,("client_read: read %d bytes. Need %d more for a full request.\n", n, sizeof(state->request) - n - state->read_buf_len )); + + /* Read failed, kill client */ + + if (n == -1 || n == 0) { + DEBUG(5,("read failed on sock %d, pid %d: %s\n", + state->sock, state->pid, + (n == -1) ? strerror(errno) : "EOF")); + + state->finished = True; + return; + } + + /* Update client state */ + + state->read_buf_len += n; + state->last_access = time(NULL); +} + +/* Write some data to a client connection */ + +static void client_write(struct winbindd_cli_state *state) +{ + char *data; + int num_written; + + /* Write some data */ + + if (!state->write_extra_data) { + + /* Write response structure */ + + data = (char *)&state->response + sizeof(state->response) - + state->write_buf_len; + + } else { + + /* Write extra data */ + + data = (char *)state->response.extra_data + + state->response.length - + sizeof(struct winbindd_response) - + state->write_buf_len; + } + + num_written = sys_write(state->sock, data, state->write_buf_len); + + DEBUG(10,("client_write: wrote %d bytes.\n", num_written )); + + /* Write failed, kill cilent */ + + if (num_written == -1 || num_written == 0) { + + DEBUG(3,("write failed on sock %d, pid %d: %s\n", + state->sock, state->pid, + (num_written == -1) ? strerror(errno) : "EOF")); + + state->finished = True; + + SAFE_FREE(state->response.extra_data); + + return; + } + + /* Update client state */ + + state->write_buf_len -= num_written; + state->last_access = time(NULL); + + /* Have we written all data? */ + + if (state->write_buf_len == 0) { + + /* Take care of extra data */ + + if (state->write_extra_data) { + + SAFE_FREE(state->response.extra_data); + + state->write_extra_data = False; + + DEBUG(10,("client_write: client_write: complete response written.\n")); + + } else if (state->response.length > + sizeof(struct winbindd_response)) { + + /* Start writing extra data */ + + state->write_buf_len = + state->response.length - + sizeof(struct winbindd_response); + + DEBUG(10,("client_write: need to write %d extra data bytes.\n", (int)state->write_buf_len)); + + state->write_extra_data = True; + } + } +} + +/* Process incoming clients on listen_sock. We use a tricky non-blocking, + non-forking, non-threaded model which allows us to handle many + simultaneous connections while remaining impervious to many denial of + service attacks. */ + +static void process_loop(void) +{ + /* We'll be doing this a lot */ + + while (1) { + struct winbindd_cli_state *state; + fd_set r_fds, w_fds; + int maxfd, listen_sock, selret; + struct timeval timeout; + + /* Handle messages */ + + message_dispatch(); + + /* rescan the trusted domains list. This must be done + regularly to cope with transitive trusts */ + rescan_trusted_domains(False); + + /* Free up temporary memory */ + + lp_talloc_free(); + main_loop_talloc_free(); + + /* Initialise fd lists for select() */ + + listen_sock = open_winbindd_socket(); + + if (listen_sock == -1) { + perror("open_winbind_socket"); + exit(1); + } + + maxfd = listen_sock; + + FD_ZERO(&r_fds); + FD_ZERO(&w_fds); + FD_SET(listen_sock, &r_fds); + + timeout.tv_sec = WINBINDD_ESTABLISH_LOOP; + timeout.tv_usec = 0; + + if (opt_dual_daemon) { + maxfd = dual_select_setup(&w_fds, maxfd); + } + + /* Set up client readers and writers */ + + state = winbindd_client_list(); + + while (state) { + + /* Dispose of client connection if it is marked as + finished */ + + if (state->finished) { + struct winbindd_cli_state *next = state->next; + + remove_client(state); + state = next; + continue; + } + + /* Select requires we know the highest fd used */ + + if (state->sock > maxfd) + maxfd = state->sock; + + /* Add fd for reading */ + + if (state->read_buf_len != sizeof(state->request)) + FD_SET(state->sock, &r_fds); + + /* Add fd for writing */ + + if (state->write_buf_len) + FD_SET(state->sock, &w_fds); + + state = state->next; + } + + /* Call select */ + + selret = sys_select(maxfd + 1, &r_fds, &w_fds, NULL, &timeout); + + if (selret == 0) + continue; + + if ((selret == -1 && errno != EINTR) || selret == 0) { + + /* Select error, something is badly wrong */ + + perror("select"); + exit(1); + } + + /* Create a new connection if listen_sock readable */ + + if (selret > 0) { + + if (opt_dual_daemon) { + dual_select(&w_fds); + } + + if (FD_ISSET(listen_sock, &r_fds)) { + while (winbindd_num_clients() > WINBINDD_MAX_SIMULTANEOUS_CLIENTS - 1) { + DEBUG(5,("winbindd: Exceeding %d client connections, removing idle connection.\n", + WINBINDD_MAX_SIMULTANEOUS_CLIENTS)); + if (!remove_idle_client()) { + DEBUG(0,("winbindd: Exceeding %d client connections, no idle connection found\n", + WINBINDD_MAX_SIMULTANEOUS_CLIENTS)); + break; + } + } + new_connection(listen_sock); + } + + /* Process activity on client connections */ + + for (state = winbindd_client_list(); state; + state = state->next) { + + /* Data available for reading */ + + if (FD_ISSET(state->sock, &r_fds)) { + + /* Read data */ + + winbind_client_read(state); + + /* + * If we have the start of a + * packet, then check the + * length field to make sure + * the client's not talking + * Mock Swedish. + */ + + if (state->read_buf_len >= sizeof(uint32) + && *(uint32 *) &state->request != sizeof(state->request)) { + DEBUG(0,("process_loop: Invalid request size from pid %d: %d bytes sent, should be %d\n", + state->request.pid, *(uint32 *) &state->request, sizeof(state->request))); + + remove_client(state); + break; + } + + /* A request packet might be + complete */ + + if (state->read_buf_len == + sizeof(state->request)) { + winbind_process_packet(state); + } + } + + /* Data available for writing */ + + if (FD_ISSET(state->sock, &w_fds)) + client_write(state); + } + } + +#if 0 + winbindd_check_cache_size(time(NULL)); +#endif + + /* Check signal handling things */ + + if (do_sigterm) + terminate(); + + if (do_sighup) { + + DEBUG(3, ("got SIGHUP\n")); + + /* Flush various caches */ + + flush_caches(); + reload_services_file(True); + do_sighup = False; + } + + if (do_sigusr2) { + print_winbindd_status(); + do_sigusr2 = False; + } + } +} + + +/* + these are split out from the main winbindd for use by the background daemon + */ +BOOL winbind_setup_common(void) +{ + load_interfaces(); + + if (!secrets_init()) { + + DEBUG(0,("Could not initialize domain trust account secrets. Giving up\n")); + return False; + } + + namecache_enable(); /* Enable netbios namecache */ + + /* Check winbindd parameters are valid */ + + ZERO_STRUCT(server_state); + + if (!winbindd_param_init()) + return False; + + /* Winbind daemon initialisation */ + + if (!winbindd_idmap_init()) + return False; + + /* Unblock all signals we are interested in as they may have been + blocked by the parent process. */ + + BlockSignals(False, SIGINT); + BlockSignals(False, SIGQUIT); + BlockSignals(False, SIGTERM); + BlockSignals(False, SIGUSR1); + BlockSignals(False, SIGUSR2); + BlockSignals(False, SIGHUP); + + /* Setup signal handlers */ + + CatchSignal(SIGINT, termination_handler); /* Exit on these sigs */ + CatchSignal(SIGQUIT, termination_handler); + CatchSignal(SIGTERM, termination_handler); + + CatchSignal(SIGPIPE, SIG_IGN); /* Ignore sigpipe */ + + CatchSignal(SIGUSR2, sigusr2_handler); /* Debugging sigs */ + CatchSignal(SIGHUP, sighup_handler); + + return True; +} + + +/* Main function */ + +struct winbindd_state server_state; /* Server state information */ + + +static void usage(void) +{ + printf("Usage: winbindd [options]\n"); + printf("\t-F daemon in foreground mode\n"); + printf("\t-S log to stdout\n"); + printf("\t-i interactive mode\n"); + printf("\t-B dual daemon mode\n"); + printf("\t-n disable cacheing\n"); + printf("\t-d level set debug level\n"); + printf("\t-s configfile choose smb.conf location\n"); + printf("\t-h show this help message\n"); +} + + int main(int argc, char **argv) +{ + extern BOOL AllowDebugChange; + pstring logfile; + BOOL interactive = False; + BOOL Fork = True; + BOOL log_stdout = False; + int opt; + + /* glibc (?) likes to print "User defined signal 1" and exit if a + SIGUSR[12] is received before a handler is installed */ + + CatchSignal(SIGUSR1, SIG_IGN); + CatchSignal(SIGUSR2, SIG_IGN); + + fault_setup((void (*)(void *))fault_quit ); + + snprintf(logfile, sizeof(logfile), "%s/log.winbindd", dyn_LOGFILEBASE); + lp_set_logfile(logfile); + + /* Initialise for running in non-root mode */ + + sec_init(); + + /* Set environment variable so we don't recursively call ourselves. + This may also be useful interactively. */ + + setenv(WINBINDD_DONT_ENV, "1", 1); + + /* Initialise samba/rpc client stuff */ + + while ((opt = getopt(argc, argv, "FSid:s:nhB")) != EOF) { + switch (opt) { + + case 'F': + Fork = False; + break; + case 'S': + log_stdout = True; + break; + /* Don't become a daemon */ + case 'i': + interactive = True; + log_stdout = True; + Fork = False; + break; + + /* dual daemon system */ + case 'B': + opt_dual_daemon = True; + break; + + /* disable cacheing */ + case 'n': + opt_nocache = True; + break; + + /* Run with specified debug level */ + case 'd': + DEBUGLEVEL = atoi(optarg); + AllowDebugChange = False; + break; + + /* Load a different smb.conf file */ + case 's': + pstrcpy(dyn_CONFIGFILE,optarg); + break; + + case 'h': + usage(); + exit(0); + + default: + printf("Unknown option %c\n", (char)opt); + exit(1); + } + } + + if (log_stdout && Fork) { + printf("Can't log to stdout (-S) unless daemon is in foreground +(-F) or interactive (-i)\n"); + usage(); + exit(1); + } + + snprintf(logfile, sizeof(logfile), "%s/log.winbindd", dyn_LOGFILEBASE); + lp_set_logfile(logfile); + setup_logging("winbindd", log_stdout); + reopen_logs(); + + DEBUG(1, ("winbindd version %s started.\n", VERSION ) ); + DEBUGADD( 1, ( "Copyright The Samba Team 2000-2001\n" ) ); + + if (!reload_services_file(False)) { + DEBUG(0, ("error opening config file\n")); + exit(1); + } + + /* Setup names. */ + + if (!init_names()) + exit(1); + + if (!interactive) { + become_daemon(Fork); + pidfile_create("winbindd"); + } + + +#if HAVE_SETPGID + /* + * If we're interactive we want to set our own process group for + * signal management. + */ + if (interactive) + setpgid( (pid_t)0, (pid_t)0); +#endif + + if (!winbind_setup_common()) { + return 1; + } + + if (opt_dual_daemon) { + do_dual_daemon(); + } + + /* Initialise messaging system */ + + if (!message_init()) { + DEBUG(0, ("unable to initialise messaging system\n")); + exit(1); + } + + register_msg_pool_usage(); + message_register(MSG_REQ_TALLOC_USAGE, return_all_talloc_info); + + /* Loop waiting for requests */ + + process_loop(); + + trustdom_cache_shutdown(); + uni_group_cache_shutdown(); + return 0; +} diff --git a/source4/nsswitch/winbindd.h b/source4/nsswitch/winbindd.h new file mode 100644 index 0000000000..42ef209faf --- /dev/null +++ b/source4/nsswitch/winbindd.h @@ -0,0 +1,230 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon for ntdom nss module + + Copyright (C) Tim Potter 2000 + Copyright (C) Anthony Liguori 2003 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef _WINBINDD_H +#define _WINBINDD_H + +#include "includes.h" +#include "nterr.h" + +#include "winbindd_nss.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/* Client state structure */ + +struct winbindd_cli_state { + struct winbindd_cli_state *prev, *next; /* Linked list pointers */ + int sock; /* Open socket from client */ + pid_t pid; /* pid of client */ + int read_buf_len, write_buf_len; /* Indexes in request/response */ + BOOL finished; /* Can delete from list */ + BOOL write_extra_data; /* Write extra_data field */ + time_t last_access; /* Time of last access (read or write) */ + struct winbindd_request request; /* Request from client */ + struct winbindd_response response; /* Respose to client */ + struct getent_state *getpwent_state; /* State for getpwent() */ + struct getent_state *getgrent_state; /* State for getgrent() */ +}; + +/* State between get{pw,gr}ent() calls */ + +struct getent_state { + struct getent_state *prev, *next; + void *sam_entries; + uint32 sam_entry_index, num_sam_entries; + BOOL got_sam_entries; + fstring domain_name; +}; + +/* Storage for cached getpwent() user entries */ + +struct getpwent_user { + fstring name; /* Account name */ + fstring gecos; /* User information */ + DOM_SID user_sid; /* NT user and primary group SIDs */ + DOM_SID group_sid; +}; + +/* Server state structure */ + +struct winbindd_state { + + /* User and group id pool */ + + uid_t uid_low, uid_high; /* Range of uids to allocate */ + gid_t gid_low, gid_high; /* Range of gids to allocate */ +}; + +extern struct winbindd_state server_state; /* Server information */ + +typedef struct { + char *acct_name; + char *full_name; + DOM_SID *user_sid; /* NT user and primary group SIDs */ + DOM_SID *group_sid; +} WINBIND_USERINFO; + +/* Structures to hold per domain information */ + +struct winbindd_domain { + fstring name; /* Domain name */ + fstring alt_name; /* alt Domain name (if any) */ + DOM_SID sid; /* SID for this domain */ + BOOL native_mode; /* is this a win2k domain in native mode ? */ + + /* Lookup methods for this domain (LDAP or RPC) */ + + struct winbindd_methods *methods; + + /* Private data for the backends (used for connection cache) */ + + void *private; + + /* Sequence number stuff */ + + time_t last_seq_check; + uint32 sequence_number; + + /* Linked list info */ + + struct winbindd_domain *prev, *next; +}; + +/* per-domain methods. This is how LDAP vs RPC is selected + */ +struct winbindd_methods { + /* does this backend provide a consistent view of the data? (ie. is the primary group + always correct) */ + BOOL consistent; + + /* get a list of users, returning a WINBIND_USERINFO for each one */ + NTSTATUS (*query_user_list)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_entries, + WINBIND_USERINFO **info); + + /* get a list of domain groups */ + NTSTATUS (*enum_dom_groups)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_entries, + struct acct_info **info); + + /* get a list of domain local groups */ + NTSTATUS (*enum_local_groups)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_entries, + struct acct_info **info); + + /* convert one user or group name to a sid */ + NTSTATUS (*name_to_sid)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *name, + DOM_SID *sid, + enum SID_NAME_USE *type); + + /* convert a sid to a user or group name */ + NTSTATUS (*sid_to_name)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *sid, + char **name, + enum SID_NAME_USE *type); + + /* lookup user info for a given SID */ + NTSTATUS (*query_user)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *user_sid, + WINBIND_USERINFO *user_info); + + /* lookup all groups that a user is a member of. The backend + can also choose to lookup by username or rid for this + function */ + NTSTATUS (*lookup_usergroups)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *user_sid, + uint32 *num_groups, DOM_SID ***user_gids); + + /* find all members of the group with the specified group_rid */ + NTSTATUS (*lookup_groupmem)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *group_sid, + uint32 *num_names, + DOM_SID ***sid_mem, char ***names, + uint32 **name_types); + + /* return the current global sequence number */ + NTSTATUS (*sequence_number)(struct winbindd_domain *domain, uint32 *seq); + + /* enumerate trusted domains */ + NTSTATUS (*trusted_domains)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_domains, + char ***names, + char ***alt_names, + DOM_SID **dom_sids); + + /* find the domain sid */ + NTSTATUS (*domain_sid)(struct winbindd_domain *domain, + DOM_SID *sid); + + /* setup the list of alternate names for the domain, if any */ + NTSTATUS (*alternate_name)(struct winbindd_domain *domain); +}; + +/* Used to glue a policy handle and cli_state together */ + +typedef struct { + struct cli_state *cli; + POLICY_HND pol; +} CLI_POLICY_HND; + +/* Filled out by IDMAP backends */ +struct idmap_methods { + /* Called when backend is first loaded */ + BOOL (*init)(void); + + BOOL (*get_sid_from_uid)(uid_t uid, DOM_SID *sid); + BOOL (*get_sid_from_gid)(gid_t gid, DOM_SID *sid); + + BOOL (*get_uid_from_sid)(DOM_SID *sid, uid_t *uid); + BOOL (*get_gid_from_sid)(DOM_SID *sid, gid_t *gid); + + /* Called when backend is unloaded */ + BOOL (*close)(void); + /* Called to dump backend status */ + void (*status)(void); +}; + +#include "winbindd_proto.h" + +#include "rpc_parse.h" +#include "rpc_client.h" + +#define WINBINDD_ESTABLISH_LOOP 30 +#define WINBINDD_RESCAN_FREQ 300 + +#define DOM_SEQUENCE_NONE ((uint32)-1) + +#endif /* _WINBINDD_H */ diff --git a/source4/nsswitch/winbindd_ads.c b/source4/nsswitch/winbindd_ads.c new file mode 100644 index 0000000000..de3757aa44 --- /dev/null +++ b/source4/nsswitch/winbindd_ads.c @@ -0,0 +1,837 @@ +/* + Unix SMB/CIFS implementation. + + Winbind ADS backend functions + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "winbindd.h" + +#ifdef HAVE_ADS + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/* the realm of our primary LDAP server */ +static char *primary_realm; + + +/* + return our ads connections structure for a domain. We keep the connection + open to make things faster +*/ +static ADS_STRUCT *ads_cached_connection(struct winbindd_domain *domain) +{ + ADS_STRUCT *ads; + ADS_STATUS status; + + if (domain->private) { + return (ADS_STRUCT *)domain->private; + } + + /* we don't want this to affect the users ccache */ + setenv("KRB5CCNAME", "MEMORY:winbind_ccache", 1); + + ads = ads_init(domain->alt_name, domain->name, NULL); + if (!ads) { + DEBUG(1,("ads_init for domain %s failed\n", domain->name)); + return NULL; + } + + /* the machine acct password might have change - fetch it every time */ + SAFE_FREE(ads->auth.password); + ads->auth.password = secrets_fetch_machine_password(); + + if (primary_realm) { + SAFE_FREE(ads->auth.realm); + ads->auth.realm = strdup(primary_realm); + } + + status = ads_connect(ads); + if (!ADS_ERR_OK(status) || !ads->config.realm) { + extern struct winbindd_methods msrpc_methods; + DEBUG(1,("ads_connect for domain %s failed: %s\n", + domain->name, ads_errstr(status))); + ads_destroy(&ads); + + /* if we get ECONNREFUSED then it might be a NT4 + server, fall back to MSRPC */ + if (status.error_type == ADS_ERROR_SYSTEM && + status.err.rc == ECONNREFUSED) { + DEBUG(1,("Trying MSRPC methods\n")); + domain->methods = &msrpc_methods; + } + return NULL; + } + + /* remember our primary realm for trusted domain support */ + if (!primary_realm) { + primary_realm = strdup(ads->config.realm); + } + + domain->private = (void *)ads; + return ads; +} + + +/* Query display info for a realm. This is the basic user list fn */ +static NTSTATUS query_user_list(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_entries, + WINBIND_USERINFO **info) +{ + ADS_STRUCT *ads = NULL; + const char *attrs[] = {"userPrincipalName", + "sAMAccountName", + "name", "objectSid", "primaryGroupID", + "sAMAccountType", NULL}; + int i, count; + ADS_STATUS rc; + void *res = NULL; + void *msg = NULL; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + *num_entries = 0; + + DEBUG(3,("ads: query_user_list\n")); + + ads = ads_cached_connection(domain); + if (!ads) goto done; + + rc = ads_search_retry(ads, &res, "(objectCategory=user)", attrs); + if (!ADS_ERR_OK(rc)) { + DEBUG(1,("query_user_list ads_search: %s\n", ads_errstr(rc))); + goto done; + } + + count = ads_count_replies(ads, res); + if (count == 0) { + DEBUG(1,("query_user_list: No users found\n")); + goto done; + } + + (*info) = talloc_zero(mem_ctx, count * sizeof(**info)); + if (!*info) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + i = 0; + + for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) { + char *name, *gecos; + DOM_SID sid; + DOM_SID *sid2; + DOM_SID *group_sid; + uint32 group; + uint32 atype; + + if (!ads_pull_uint32(ads, msg, "sAMAccountType", &atype) || + ads_atype_map(atype) != SID_NAME_USER) { + DEBUG(1,("Not a user account? atype=0x%x\n", atype)); + continue; + } + + name = ads_pull_username(ads, mem_ctx, msg); + gecos = ads_pull_string(ads, mem_ctx, msg, "name"); + if (!ads_pull_sid(ads, msg, "objectSid", &sid)) { + DEBUG(1,("No sid for %s !?\n", name)); + continue; + } + if (!ads_pull_uint32(ads, msg, "primaryGroupID", &group)) { + DEBUG(1,("No primary group for %s !?\n", name)); + continue; + } + + sid2 = talloc(mem_ctx, sizeof(*sid2)); + if (!sid2) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + sid_copy(sid2, &sid); + + group_sid = rid_to_talloced_sid(domain, mem_ctx, group); + + (*info)[i].acct_name = name; + (*info)[i].full_name = gecos; + (*info)[i].user_sid = sid2; + (*info)[i].group_sid = group_sid; + i++; + } + + (*num_entries) = i; + status = NT_STATUS_OK; + + DEBUG(3,("ads query_user_list gave %d entries\n", (*num_entries))); + +done: + if (res) ads_msgfree(ads, res); + + return status; +} + +/* list all domain groups */ +static NTSTATUS enum_dom_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_entries, + struct acct_info **info) +{ + ADS_STRUCT *ads = NULL; + const char *attrs[] = {"userPrincipalName", "sAMAccountName", + "name", "objectSid", + "sAMAccountType", NULL}; + int i, count; + ADS_STATUS rc; + void *res = NULL; + void *msg = NULL; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + uint32 group_flags; + + *num_entries = 0; + + DEBUG(3,("ads: enum_dom_groups\n")); + + ads = ads_cached_connection(domain); + if (!ads) goto done; + + rc = ads_search_retry(ads, &res, "(objectCategory=group)", attrs); + if (!ADS_ERR_OK(rc)) { + DEBUG(1,("enum_dom_groups ads_search: %s\n", ads_errstr(rc))); + goto done; + } + + count = ads_count_replies(ads, res); + if (count == 0) { + DEBUG(1,("enum_dom_groups: No groups found\n")); + goto done; + } + + (*info) = talloc_zero(mem_ctx, count * sizeof(**info)); + if (!*info) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + i = 0; + + group_flags = ATYPE_GLOBAL_GROUP; + if ( domain->native_mode ) + group_flags |= ATYPE_LOCAL_GROUP; + + for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) { + char *name, *gecos; + DOM_SID sid; + uint32 rid; + uint32 account_type; + + if (!ads_pull_uint32(ads, msg, "sAMAccountType", &account_type) || !(account_type & group_flags) ) + continue; + + name = ads_pull_username(ads, mem_ctx, msg); + gecos = ads_pull_string(ads, mem_ctx, msg, "name"); + if (!ads_pull_sid(ads, msg, "objectSid", &sid)) { + DEBUG(1,("No sid for %s !?\n", name)); + continue; + } + + if (!sid_peek_check_rid(&domain->sid, &sid, &rid)) { + DEBUG(1,("No rid for %s !?\n", name)); + continue; + } + + fstrcpy((*info)[i].acct_name, name); + fstrcpy((*info)[i].acct_desc, gecos); + (*info)[i].rid = rid; + i++; + } + + (*num_entries) = i; + + status = NT_STATUS_OK; + + DEBUG(3,("ads enum_dom_groups gave %d entries\n", (*num_entries))); + +done: + if (res) ads_msgfree(ads, res); + + return status; +} + +/* list all domain local groups */ +static NTSTATUS enum_local_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_entries, + struct acct_info **info) +{ + /* + * This is a stub function only as we returned the domain + * ocal groups in enum_dom_groups() if the domain->native field + * was true. This is a simple performance optimization when + * using LDAP. + * + * if we ever need to enumerate domain local groups separately, + * then this the optimization in enum_dom_groups() will need + * to be split out + */ + *num_entries = 0; + + return NT_STATUS_OK; +} + +/* convert a single name to a sid in a domain */ +static NTSTATUS name_to_sid(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *name, + DOM_SID *sid, + enum SID_NAME_USE *type) +{ + ADS_STRUCT *ads; + + DEBUG(3,("ads: name_to_sid\n")); + + ads = ads_cached_connection(domain); + if (!ads) + return NT_STATUS_UNSUCCESSFUL; + + return ads_name_to_sid(ads, name, sid, type); +} + +/* convert a sid to a user or group name */ +static NTSTATUS sid_to_name(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *sid, + char **name, + enum SID_NAME_USE *type) +{ + ADS_STRUCT *ads = NULL; + DEBUG(3,("ads: sid_to_name\n")); + ads = ads_cached_connection(domain); + if (!ads) + return NT_STATUS_UNSUCCESSFUL; + + return ads_sid_to_name(ads, mem_ctx, sid, name, type); +} + + +/* convert a DN to a name, SID and name type + this might become a major speed bottleneck if groups have + lots of users, in which case we could cache the results +*/ +static BOOL dn_lookup(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, + const char *dn, + char **name, uint32 *name_type, DOM_SID *sid) +{ + char *exp; + void *res = NULL; + const char *attrs[] = {"userPrincipalName", "sAMAccountName", + "objectSid", "sAMAccountType", NULL}; + ADS_STATUS rc; + uint32 atype; + char *escaped_dn = escape_ldap_string_alloc(dn); + + if (!escaped_dn) { + return False; + } + + asprintf(&exp, "(distinguishedName=%s)", dn); + rc = ads_search_retry(ads, &res, exp, attrs); + SAFE_FREE(exp); + SAFE_FREE(escaped_dn); + + if (!ADS_ERR_OK(rc)) { + goto failed; + } + + (*name) = ads_pull_username(ads, mem_ctx, res); + + if (!ads_pull_uint32(ads, res, "sAMAccountType", &atype)) { + goto failed; + } + (*name_type) = ads_atype_map(atype); + + if (!ads_pull_sid(ads, res, "objectSid", sid)) { + goto failed; + } + + if (res) ads_msgfree(ads, res); + return True; + +failed: + if (res) ads_msgfree(ads, res); + return False; +} + +/* Lookup user information from a rid */ +static NTSTATUS query_user(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *sid, + WINBIND_USERINFO *info) +{ + ADS_STRUCT *ads = NULL; + const char *attrs[] = {"userPrincipalName", + "sAMAccountName", + "name", + "primaryGroupID", NULL}; + ADS_STATUS rc; + int count; + void *msg = NULL; + char *exp; + char *sidstr; + uint32 group_rid; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + DOM_SID *sid2; + fstring sid_string; + + DEBUG(3,("ads: query_user\n")); + + ads = ads_cached_connection(domain); + if (!ads) goto done; + + sidstr = sid_binstring(sid); + asprintf(&exp, "(objectSid=%s)", sidstr); + rc = ads_search_retry(ads, &msg, exp, attrs); + free(exp); + free(sidstr); + if (!ADS_ERR_OK(rc)) { + DEBUG(1,("query_user(sid=%s) ads_search: %s\n", sid_to_string(sid_string, sid), ads_errstr(rc))); + goto done; + } + + count = ads_count_replies(ads, msg); + if (count != 1) { + DEBUG(1,("query_user(sid=%s): Not found\n", sid_to_string(sid_string, sid))); + goto done; + } + + info->acct_name = ads_pull_username(ads, mem_ctx, msg); + info->full_name = ads_pull_string(ads, mem_ctx, msg, "name"); + + if (!ads_pull_uint32(ads, msg, "primaryGroupID", &group_rid)) { + DEBUG(1,("No primary group for %s !?\n", sid_to_string(sid_string, sid))); + goto done; + } + + sid2 = talloc(mem_ctx, sizeof(*sid2)); + if (!sid2) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + sid_copy(sid2, sid); + + info->user_sid = sid2; + + info->group_sid = rid_to_talloced_sid(domain, mem_ctx, group_rid); + + status = NT_STATUS_OK; + + DEBUG(3,("ads query_user gave %s\n", info->acct_name)); +done: + if (msg) ads_msgfree(ads, msg); + + return status; +} + +/* Lookup groups a user is a member of - alternate method, for when + tokenGroups are not available. */ +static NTSTATUS lookup_usergroups_alt(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *user_dn, + DOM_SID *primary_group, + uint32 *num_groups, DOM_SID ***user_gids) +{ + ADS_STATUS rc; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + int count; + void *res = NULL; + void *msg = NULL; + char *exp; + ADS_STRUCT *ads; + const char *group_attrs[] = {"objectSid", NULL}; + + ads = ads_cached_connection(domain); + if (!ads) goto done; + + /* buggy server, no tokenGroups. Instead lookup what groups this user + is a member of by DN search on member*/ + if (asprintf(&exp, "(&(member=%s)(objectClass=group))", user_dn) == -1) { + DEBUG(1,("lookup_usergroups(dn=%s) asprintf failed!\n", user_dn)); + return NT_STATUS_NO_MEMORY; + } + + rc = ads_search_retry(ads, &res, exp, group_attrs); + free(exp); + + if (!ADS_ERR_OK(rc)) { + DEBUG(1,("lookup_usergroups ads_search member=%s: %s\n", user_dn, ads_errstr(rc))); + return ads_ntstatus(rc); + } + + count = ads_count_replies(ads, res); + if (count == 0) { + DEBUG(5,("lookup_usergroups: No supp groups found\n")); + + status = ads_ntstatus(rc); + goto done; + } + + (*user_gids) = talloc_zero(mem_ctx, sizeof(**user_gids) * (count + 1)); + (*user_gids)[0] = primary_group; + + *num_groups = 1; + + for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) { + DOM_SID group_sid; + + if (!ads_pull_sid(ads, msg, "objectSid", &group_sid)) { + DEBUG(1,("No sid for this group ?!?\n")); + continue; + } + + if (sid_equal(&group_sid, primary_group)) continue; + + (*user_gids)[*num_groups] = talloc(mem_ctx, sizeof(***user_gids)); + if (!(*user_gids)[*num_groups]) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + sid_copy((*user_gids)[*num_groups], &group_sid); + + (*num_groups)++; + + } + + status = NT_STATUS_OK; + + DEBUG(3,("ads lookup_usergroups (alt) for dn=%s\n", user_dn)); +done: + if (res) ads_msgfree(ads, res); + if (msg) ads_msgfree(ads, msg); + + return status; +} + +/* Lookup groups a user is a member of. */ +static NTSTATUS lookup_usergroups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *sid, + uint32 *num_groups, DOM_SID ***user_gids) +{ + ADS_STRUCT *ads = NULL; + const char *attrs[] = {"distinguishedName", NULL}; + const char *attrs2[] = {"tokenGroups", "primaryGroupID", NULL}; + ADS_STATUS rc; + int count; + void *msg = NULL; + char *exp; + char *user_dn; + DOM_SID *sids; + int i; + DOM_SID *primary_group; + uint32 primary_group_rid; + char *sidstr; + fstring sid_string; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + DEBUG(3,("ads: lookup_usergroups\n")); + *num_groups = 0; + + ads = ads_cached_connection(domain); + if (!ads) goto done; + + if (!(sidstr = sid_binstring(sid))) { + DEBUG(1,("lookup_usergroups(sid=%s) sid_binstring returned NULL\n", sid_to_string(sid_string, sid))); + status = NT_STATUS_NO_MEMORY; + goto done; + } + if (asprintf(&exp, "(objectSid=%s)", sidstr) == -1) { + free(sidstr); + DEBUG(1,("lookup_usergroups(sid=%s) asprintf failed!\n", sid_to_string(sid_string, sid))); + status = NT_STATUS_NO_MEMORY; + goto done; + } + + rc = ads_search_retry(ads, &msg, exp, attrs); + free(exp); + free(sidstr); + + if (!ADS_ERR_OK(rc)) { + DEBUG(1,("lookup_usergroups(sid=%s) ads_search: %s\n", sid_to_string(sid_string, sid), ads_errstr(rc))); + goto done; + } + + user_dn = ads_pull_string(ads, mem_ctx, msg, "distinguishedName"); + if (!user_dn) { + DEBUG(1,("lookup_usergroups(sid=%s) ads_search did not return a a distinguishedName!\n", sid_to_string(sid_string, sid))); + if (msg) ads_msgfree(ads, msg); + goto done; + } + + if (msg) ads_msgfree(ads, msg); + + rc = ads_search_retry_dn(ads, &msg, user_dn, attrs2); + if (!ADS_ERR_OK(rc)) { + DEBUG(1,("lookup_usergroups(sid=%s) ads_search tokenGroups: %s\n", sid_to_string(sid_string, sid), ads_errstr(rc))); + goto done; + } + + if (!ads_pull_uint32(ads, msg, "primaryGroupID", &primary_group_rid)) { + DEBUG(1,("%s: No primary group for sid=%s !?\n", domain->name, sid_to_string(sid_string, sid))); + goto done; + } + + primary_group = rid_to_talloced_sid(domain, mem_ctx, primary_group_rid); + + count = ads_pull_sids(ads, mem_ctx, msg, "tokenGroups", &sids); + + if (msg) ads_msgfree(ads, msg); + + /* there must always be at least one group in the token, + unless we are talking to a buggy Win2k server */ + if (count == 0) { + return lookup_usergroups_alt(domain, mem_ctx, user_dn, + primary_group, + num_groups, user_gids); + } + + (*user_gids) = talloc_zero(mem_ctx, sizeof(**user_gids) * (count + 1)); + (*user_gids)[0] = primary_group; + + *num_groups = 1; + + for (i=0;i<count;i++) { + if (sid_equal(&sids[i], primary_group)) continue; + + (*user_gids)[*num_groups] = talloc(mem_ctx, sizeof(***user_gids)); + if (!(*user_gids)[*num_groups]) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + sid_copy((*user_gids)[*num_groups], &sids[i]); + (*num_groups)++; + } + + status = NT_STATUS_OK; + DEBUG(3,("ads lookup_usergroups for sid=%s\n", sid_to_string(sid_string, sid))); +done: + return status; +} + +/* + find the members of a group, given a group rid and domain + */ +static NTSTATUS lookup_groupmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *group_sid, uint32 *num_names, + DOM_SID ***sid_mem, char ***names, + uint32 **name_types) +{ + ADS_STATUS rc; + int count; + void *res=NULL; + ADS_STRUCT *ads = NULL; + char *exp; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + char *sidstr; + const char *attrs[] = {"member", NULL}; + char **members; + int i, num_members; + fstring sid_string; + + *num_names = 0; + + ads = ads_cached_connection(domain); + if (!ads) goto done; + + sidstr = sid_binstring(group_sid); + + /* search for all members of the group */ + asprintf(&exp, "(objectSid=%s)",sidstr); + rc = ads_search_retry(ads, &res, exp, attrs); + free(exp); + free(sidstr); + + if (!ADS_ERR_OK(rc)) { + DEBUG(1,("query_user_list ads_search: %s\n", ads_errstr(rc))); + goto done; + } + + count = ads_count_replies(ads, res); + if (count == 0) { + status = NT_STATUS_OK; + goto done; + } + + members = ads_pull_strings(ads, mem_ctx, res, "member"); + if (!members) { + /* no members? ok ... */ + status = NT_STATUS_OK; + goto done; + } + + /* now we need to turn a list of members into rids, names and name types + the problem is that the members are in the form of distinguised names + */ + for (i=0;members[i];i++) /* noop */ ; + num_members = i; + + (*sid_mem) = talloc_zero(mem_ctx, sizeof(**sid_mem) * num_members); + (*name_types) = talloc_zero(mem_ctx, sizeof(**name_types) * num_members); + (*names) = talloc_zero(mem_ctx, sizeof(**names) * num_members); + + for (i=0;i<num_members;i++) { + uint32 name_type; + char *name; + DOM_SID sid; + + if (dn_lookup(ads, mem_ctx, members[i], &name, &name_type, &sid)) { + (*names)[*num_names] = name; + (*name_types)[*num_names] = name_type; + (*sid_mem)[*num_names] = talloc(mem_ctx, sizeof(***sid_mem)); + if (!(*sid_mem)[*num_names]) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + sid_copy((*sid_mem)[*num_names], &sid); + (*num_names)++; + } + } + + status = NT_STATUS_OK; + DEBUG(3,("ads lookup_groupmem for sid=%s\n", sid_to_string(sid_string, group_sid))); +done: + if (res) ads_msgfree(ads, res); + + return status; +} + + +/* find the sequence number for a domain */ +static NTSTATUS sequence_number(struct winbindd_domain *domain, uint32 *seq) +{ + ADS_STRUCT *ads = NULL; + ADS_STATUS rc; + + *seq = DOM_SEQUENCE_NONE; + + ads = ads_cached_connection(domain); + if (!ads) return NT_STATUS_UNSUCCESSFUL; + + rc = ads_USN(ads, seq); + if (!ADS_ERR_OK(rc)) { + /* its a dead connection */ + ads_destroy(&ads); + domain->private = NULL; + } + return ads_ntstatus(rc); +} + +/* get a list of trusted domains */ +static NTSTATUS trusted_domains(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_domains, + char ***names, + char ***alt_names, + DOM_SID **dom_sids) +{ + ADS_STRUCT *ads; + ADS_STATUS rc; + + *num_domains = 0; + *names = NULL; + + ads = ads_cached_connection(domain); + if (!ads) return NT_STATUS_UNSUCCESSFUL; + + rc = ads_trusted_domains(ads, mem_ctx, num_domains, names, alt_names, dom_sids); + + return ads_ntstatus(rc); +} + +/* find the domain sid for a domain */ +static NTSTATUS domain_sid(struct winbindd_domain *domain, DOM_SID *sid) +{ + ADS_STRUCT *ads; + ADS_STATUS rc; + + ads = ads_cached_connection(domain); + if (!ads) return NT_STATUS_UNSUCCESSFUL; + + rc = ads_domain_sid(ads, sid); + + if (!ADS_ERR_OK(rc)) { + /* its a dead connection */ + ads_destroy(&ads); + domain->private = NULL; + } + + return ads_ntstatus(rc); +} + + +/* find alternate names list for the domain - for ADS this is the + netbios name */ +static NTSTATUS alternate_name(struct winbindd_domain *domain) +{ + ADS_STRUCT *ads; + ADS_STATUS rc; + TALLOC_CTX *ctx; + char *workgroup; + + ads = ads_cached_connection(domain); + if (!ads) return NT_STATUS_UNSUCCESSFUL; + + if (!(ctx = talloc_init("alternate_name"))) { + return NT_STATUS_NO_MEMORY; + } + + rc = ads_workgroup_name(ads, ctx, &workgroup); + + if (ADS_ERR_OK(rc)) { + fstrcpy(domain->name, workgroup); + fstrcpy(domain->alt_name, ads->config.realm); + strupper(domain->alt_name); + strupper(domain->name); + } + + talloc_destroy(ctx); + + return ads_ntstatus(rc); +} + +/* the ADS backend methods are exposed via this structure */ +struct winbindd_methods ads_methods = { + True, + query_user_list, + enum_dom_groups, + enum_local_groups, + name_to_sid, + sid_to_name, + query_user, + lookup_usergroups, + lookup_groupmem, + sequence_number, + trusted_domains, + domain_sid, + alternate_name +}; + +#endif diff --git a/source4/nsswitch/winbindd_cache.c b/source4/nsswitch/winbindd_cache.c new file mode 100644 index 0000000000..5fb59e7467 --- /dev/null +++ b/source4/nsswitch/winbindd_cache.c @@ -0,0 +1,1016 @@ +/* + Unix SMB/CIFS implementation. + + Winbind cache backend functions + + Copyright (C) Andrew Tridgell 2001 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "winbindd.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +struct winbind_cache { + struct winbindd_methods *backend; + TDB_CONTEXT *tdb; +}; + +struct cache_entry { + NTSTATUS status; + uint32 sequence_number; + uint8 *data; + uint32 len, ofs; +}; + +#define WINBINDD_MAX_CACHE_SIZE (50*1024*1024) + +static struct winbind_cache *wcache; + +/* flush the cache */ +void wcache_flush_cache(void) +{ + extern BOOL opt_nocache; + + if (!wcache) return; + if (wcache->tdb) { + tdb_close(wcache->tdb); + wcache->tdb = NULL; + } + if (opt_nocache) return; + + wcache->tdb = tdb_open_log(lock_path("winbindd_cache.tdb"), 5000, + TDB_CLEAR_IF_FIRST, O_RDWR|O_CREAT, 0600); + + if (!wcache->tdb) { + DEBUG(0,("Failed to open winbindd_cache.tdb!\n")); + } +} + +void winbindd_check_cache_size(time_t t) +{ + static time_t last_check_time; + struct stat st; + + if (last_check_time == (time_t)0) + last_check_time = t; + + if (t - last_check_time < 60 && t - last_check_time > 0) + return; + + if (wcache == NULL || wcache->tdb == NULL) { + DEBUG(0, ("Unable to check size of tdb cache - cache not open !\n")); + return; + } + + if (fstat(wcache->tdb->fd, &st) == -1) { + DEBUG(0, ("Unable to check size of tdb cache %s!\n", strerror(errno) )); + return; + } + + if (st.st_size > WINBINDD_MAX_CACHE_SIZE) { + DEBUG(10,("flushing cache due to size (%lu) > (%lu)\n", + (unsigned long)st.st_size, + (unsigned long)WINBINDD_MAX_CACHE_SIZE)); + wcache_flush_cache(); + } +} + +/* get the winbind_cache structure */ +static struct winbind_cache *get_cache(struct winbindd_domain *domain) +{ + extern struct winbindd_methods msrpc_methods; + struct winbind_cache *ret = wcache; + + if (ret) return ret; + + ret = smb_xmalloc(sizeof(*ret)); + ZERO_STRUCTP(ret); + switch (lp_security()) { +#ifdef HAVE_ADS + case SEC_ADS: { + extern struct winbindd_methods ads_methods; + ret->backend = &ads_methods; + break; + } +#endif + default: + ret->backend = &msrpc_methods; + } + + wcache = ret; + wcache_flush_cache(); + + return ret; +} + +/* + free a centry structure +*/ +static void centry_free(struct cache_entry *centry) +{ + if (!centry) return; + SAFE_FREE(centry->data); + free(centry); +} + + +/* + pull a uint32 from a cache entry +*/ +static uint32 centry_uint32(struct cache_entry *centry) +{ + uint32 ret; + if (centry->len - centry->ofs < 4) { + DEBUG(0,("centry corruption? needed 4 bytes, have %d\n", + centry->len - centry->ofs)); + smb_panic("centry_uint32"); + } + ret = IVAL(centry->data, centry->ofs); + centry->ofs += 4; + return ret; +} + +/* + pull a uint8 from a cache entry +*/ +static uint8 centry_uint8(struct cache_entry *centry) +{ + uint8 ret; + if (centry->len - centry->ofs < 1) { + DEBUG(0,("centry corruption? needed 1 bytes, have %d\n", + centry->len - centry->ofs)); + smb_panic("centry_uint32"); + } + ret = CVAL(centry->data, centry->ofs); + centry->ofs += 1; + return ret; +} + +/* pull a string from a cache entry, using the supplied + talloc context +*/ +static char *centry_string(struct cache_entry *centry, TALLOC_CTX *mem_ctx) +{ + uint32 len; + char *ret; + + len = centry_uint8(centry); + + if (len == 0xFF) { + /* a deliberate NULL string */ + return NULL; + } + + if (centry->len - centry->ofs < len) { + DEBUG(0,("centry corruption? needed %d bytes, have %d\n", + len, centry->len - centry->ofs)); + smb_panic("centry_string"); + } + + ret = talloc(mem_ctx, len+1); + if (!ret) { + smb_panic("centry_string out of memory\n"); + } + memcpy(ret,centry->data + centry->ofs, len); + ret[len] = 0; + centry->ofs += len; + return ret; +} + +/* pull a string from a cache entry, using the supplied + talloc context +*/ +static DOM_SID *centry_sid(struct cache_entry *centry, TALLOC_CTX *mem_ctx) +{ + DOM_SID *sid; + char *sid_string; + sid = talloc(mem_ctx, sizeof(*sid)); + if (!sid) return NULL; + + sid_string = centry_string(centry, mem_ctx); + if (!string_to_sid(sid, sid_string)) { + return NULL; + } + return sid; +} + +/* the server is considered down if it can't give us a sequence number */ +static BOOL wcache_server_down(struct winbindd_domain *domain) +{ + if (!wcache->tdb) return False; + return (domain->sequence_number == DOM_SEQUENCE_NONE); +} + + +/* + refresh the domain sequence number. If force is True + then always refresh it, no matter how recently we fetched it +*/ +static void refresh_sequence_number(struct winbindd_domain *domain, BOOL force) +{ + NTSTATUS status; + unsigned time_diff; + unsigned cache_time = lp_winbind_cache_time(); + + /* trying to reconnect is expensive, don't do it too often */ + if (domain->sequence_number == DOM_SEQUENCE_NONE) { + cache_time *= 8; + } + + time_diff = time(NULL) - domain->last_seq_check; + + /* see if we have to refetch the domain sequence number */ + if (!force && (time_diff < cache_time)) { + return; + } + + status = wcache->backend->sequence_number(domain, &domain->sequence_number); + + if (!NT_STATUS_IS_OK(status)) { + domain->sequence_number = DOM_SEQUENCE_NONE; + } + + domain->last_seq_check = time(NULL); +} + +/* + decide if a cache entry has expired +*/ +static BOOL centry_expired(struct winbindd_domain *domain, struct cache_entry *centry) +{ + /* if the server is OK and our cache entry came from when it was down then + the entry is invalid */ + if (domain->sequence_number != DOM_SEQUENCE_NONE && + centry->sequence_number == DOM_SEQUENCE_NONE) { + return True; + } + + /* if the server is down or the cache entry is not older than the + current sequence number then it is OK */ + if (wcache_server_down(domain) || + centry->sequence_number == domain->sequence_number) { + return False; + } + + /* it's expired */ + return True; +} + +/* + fetch an entry from the cache, with a varargs key. auto-fetch the sequence + number and return status +*/ +static struct cache_entry *wcache_fetch(struct winbind_cache *cache, + struct winbindd_domain *domain, + const char *format, ...) PRINTF_ATTRIBUTE(3,4); +static struct cache_entry *wcache_fetch(struct winbind_cache *cache, + struct winbindd_domain *domain, + const char *format, ...) +{ + va_list ap; + char *kstr; + TDB_DATA data; + struct cache_entry *centry; + TDB_DATA key; + + refresh_sequence_number(domain, False); + + va_start(ap, format); + smb_xvasprintf(&kstr, format, ap); + va_end(ap); + + key.dptr = kstr; + key.dsize = strlen(kstr); + data = tdb_fetch(wcache->tdb, key); + free(kstr); + if (!data.dptr) { + /* a cache miss */ + return NULL; + } + + centry = smb_xmalloc(sizeof(*centry)); + centry->data = data.dptr; + centry->len = data.dsize; + centry->ofs = 0; + + if (centry->len < 8) { + /* huh? corrupt cache? */ + centry_free(centry); + return NULL; + } + + centry->status = NT_STATUS(centry_uint32(centry)); + centry->sequence_number = centry_uint32(centry); + + if (centry_expired(domain, centry)) { + extern BOOL opt_dual_daemon; + + if (opt_dual_daemon) { + extern BOOL background_process; + background_process = True; + } else { + centry_free(centry); + return NULL; + } + } + + return centry; +} + +/* + make sure we have at least len bytes available in a centry +*/ +static void centry_expand(struct cache_entry *centry, uint32 len) +{ + uint8 *p; + if (centry->len - centry->ofs >= len) return; + centry->len *= 2; + p = realloc(centry->data, centry->len); + if (!p) { + DEBUG(0,("out of memory: needed %d bytes in centry_expand\n", centry->len)); + smb_panic("out of memory in centry_expand"); + } + centry->data = p; +} + +/* + push a uint32 into a centry +*/ +static void centry_put_uint32(struct cache_entry *centry, uint32 v) +{ + centry_expand(centry, 4); + SIVAL(centry->data, centry->ofs, v); + centry->ofs += 4; +} + +/* + push a uint8 into a centry +*/ +static void centry_put_uint8(struct cache_entry *centry, uint8 v) +{ + centry_expand(centry, 1); + SCVAL(centry->data, centry->ofs, v); + centry->ofs += 1; +} + +/* + push a string into a centry + */ +static void centry_put_string(struct cache_entry *centry, const char *s) +{ + int len; + + if (!s) { + /* null strings are marked as len 0xFFFF */ + centry_put_uint8(centry, 0xFF); + return; + } + + len = strlen(s); + /* can't handle more than 254 char strings. Truncating is probably best */ + if (len > 254) len = 254; + centry_put_uint8(centry, len); + centry_expand(centry, len); + memcpy(centry->data + centry->ofs, s, len); + centry->ofs += len; +} + +static void centry_put_sid(struct cache_entry *centry, const DOM_SID *sid) +{ + int len; + fstring sid_string; + centry_put_string(centry, sid_to_string(sid_string, sid)); +} + +/* + start a centry for output. When finished, call centry_end() +*/ +struct cache_entry *centry_start(struct winbindd_domain *domain, NTSTATUS status) +{ + struct cache_entry *centry; + + if (!wcache->tdb) return NULL; + + centry = smb_xmalloc(sizeof(*centry)); + + centry->len = 8192; /* reasonable default */ + centry->data = smb_xmalloc(centry->len); + centry->ofs = 0; + centry->sequence_number = domain->sequence_number; + centry_put_uint32(centry, NT_STATUS_V(status)); + centry_put_uint32(centry, centry->sequence_number); + return centry; +} + +/* + finish a centry and write it to the tdb +*/ +static void centry_end(struct cache_entry *centry, const char *format, ...) PRINTF_ATTRIBUTE(2,3); +static void centry_end(struct cache_entry *centry, const char *format, ...) +{ + va_list ap; + char *kstr; + TDB_DATA key, data; + + va_start(ap, format); + smb_xvasprintf(&kstr, format, ap); + va_end(ap); + + key.dptr = kstr; + key.dsize = strlen(kstr); + data.dptr = centry->data; + data.dsize = centry->ofs; + + tdb_store(wcache->tdb, key, data, TDB_REPLACE); + free(kstr); +} + +static void wcache_save_name_to_sid(struct winbindd_domain *domain, + NTSTATUS status, + const char *name, DOM_SID *sid, + enum SID_NAME_USE type) +{ + struct cache_entry *centry; + uint32 len; + fstring uname; + fstring sid_string; + + centry = centry_start(domain, status); + if (!centry) return; + centry_put_sid(centry, sid); + fstrcpy(uname, name); + strupper(uname); + centry_end(centry, "NS/%s", sid_to_string(sid_string, sid)); + centry_free(centry); +} + +static void wcache_save_sid_to_name(struct winbindd_domain *domain, NTSTATUS status, + DOM_SID *sid, const char *name, enum SID_NAME_USE type) +{ + struct cache_entry *centry; + fstring sid_string; + + centry = centry_start(domain, status); + if (!centry) return; + if (NT_STATUS_IS_OK(status)) { + centry_put_uint32(centry, type); + centry_put_string(centry, name); + } + centry_end(centry, "SN/%s", sid_to_string(sid_string, sid)); + centry_free(centry); +} + + +static void wcache_save_user(struct winbindd_domain *domain, NTSTATUS status, WINBIND_USERINFO *info) +{ + struct cache_entry *centry; + fstring sid_string; + + centry = centry_start(domain, status); + if (!centry) return; + centry_put_string(centry, info->acct_name); + centry_put_string(centry, info->full_name); + centry_put_sid(centry, info->user_sid); + centry_put_sid(centry, info->group_sid); + centry_end(centry, "U/%s", sid_to_string(sid_string, info->user_sid)); + centry_free(centry); +} + + +/* Query display info. This is the basic user list fn */ +static NTSTATUS query_user_list(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_entries, + WINBIND_USERINFO **info) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + unsigned int i; + + if (!cache->tdb) goto do_query; + + centry = wcache_fetch(cache, domain, "UL/%s", domain->name); + if (!centry) goto do_query; + + *num_entries = centry_uint32(centry); + + if (*num_entries == 0) goto do_cached; + + (*info) = talloc(mem_ctx, sizeof(**info) * (*num_entries)); + if (! (*info)) smb_panic("query_user_list out of memory"); + for (i=0; i<(*num_entries); i++) { + (*info)[i].acct_name = centry_string(centry, mem_ctx); + (*info)[i].full_name = centry_string(centry, mem_ctx); + (*info)[i].user_sid = centry_sid(centry, mem_ctx); + (*info)[i].group_sid = centry_sid(centry, mem_ctx); + } + +do_cached: + status = centry->status; + centry_free(centry); + return status; + +do_query: + *num_entries = 0; + *info = NULL; + + if (wcache_server_down(domain)) { + return NT_STATUS_SERVER_DISABLED; + } + + status = cache->backend->query_user_list(domain, mem_ctx, num_entries, info); + + /* and save it */ + refresh_sequence_number(domain, True); + centry = centry_start(domain, status); + if (!centry) goto skip_save; + centry_put_uint32(centry, *num_entries); + for (i=0; i<(*num_entries); i++) { + centry_put_string(centry, (*info)[i].acct_name); + centry_put_string(centry, (*info)[i].full_name); + centry_put_sid(centry, (*info)[i].user_sid); + centry_put_sid(centry, (*info)[i].group_sid); + if (cache->backend->consistent) { + /* when the backend is consistent we can pre-prime some mappings */ + wcache_save_name_to_sid(domain, NT_STATUS_OK, + (*info)[i].acct_name, + (*info)[i].user_sid, + SID_NAME_USER); + wcache_save_sid_to_name(domain, NT_STATUS_OK, + (*info)[i].user_sid, + (*info)[i].acct_name, + SID_NAME_USER); + wcache_save_user(domain, NT_STATUS_OK, &(*info)[i]); + } + } + centry_end(centry, "UL/%s", domain->name); + centry_free(centry); + +skip_save: + return status; +} + +/* list all domain groups */ +static NTSTATUS enum_dom_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_entries, + struct acct_info **info) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + unsigned int i; + + if (!cache->tdb) goto do_query; + + centry = wcache_fetch(cache, domain, "GL/%s/domain", domain->name); + if (!centry) goto do_query; + + *num_entries = centry_uint32(centry); + + if (*num_entries == 0) goto do_cached; + + (*info) = talloc(mem_ctx, sizeof(**info) * (*num_entries)); + if (! (*info)) smb_panic("enum_dom_groups out of memory"); + for (i=0; i<(*num_entries); i++) { + fstrcpy((*info)[i].acct_name, centry_string(centry, mem_ctx)); + fstrcpy((*info)[i].acct_desc, centry_string(centry, mem_ctx)); + (*info)[i].rid = centry_uint32(centry); + } + +do_cached: + status = centry->status; + centry_free(centry); + return status; + +do_query: + *num_entries = 0; + *info = NULL; + + if (wcache_server_down(domain)) { + return NT_STATUS_SERVER_DISABLED; + } + + status = cache->backend->enum_dom_groups(domain, mem_ctx, num_entries, info); + + /* and save it */ + refresh_sequence_number(domain, True); + centry = centry_start(domain, status); + if (!centry) goto skip_save; + centry_put_uint32(centry, *num_entries); + for (i=0; i<(*num_entries); i++) { + centry_put_string(centry, (*info)[i].acct_name); + centry_put_string(centry, (*info)[i].acct_desc); + centry_put_uint32(centry, (*info)[i].rid); + } + centry_end(centry, "GL/%s/domain", domain->name); + centry_free(centry); + +skip_save: + return status; +} + +/* list all domain groups */ +static NTSTATUS enum_local_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_entries, + struct acct_info **info) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + unsigned int i; + + if (!cache->tdb) goto do_query; + + centry = wcache_fetch(cache, domain, "GL/%s/local", domain->name); + if (!centry) goto do_query; + + *num_entries = centry_uint32(centry); + + if (*num_entries == 0) goto do_cached; + + (*info) = talloc(mem_ctx, sizeof(**info) * (*num_entries)); + if (! (*info)) smb_panic("enum_dom_groups out of memory"); + for (i=0; i<(*num_entries); i++) { + fstrcpy((*info)[i].acct_name, centry_string(centry, mem_ctx)); + fstrcpy((*info)[i].acct_desc, centry_string(centry, mem_ctx)); + (*info)[i].rid = centry_uint32(centry); + } + +do_cached: + + /* If we are returning cached data and the domain controller + is down then we don't know whether the data is up to date + or not. Return NT_STATUS_MORE_PROCESSING_REQUIRED to + indicate this. */ + + if (wcache_server_down(domain)) { + DEBUG(10, ("query_user_list: returning cached user list and server was down\n")); + status = NT_STATUS_MORE_PROCESSING_REQUIRED; + } else + status = centry->status; + + centry_free(centry); + return status; + +do_query: + *num_entries = 0; + *info = NULL; + + if (wcache_server_down(domain)) { + return NT_STATUS_SERVER_DISABLED; + } + + status = cache->backend->enum_local_groups(domain, mem_ctx, num_entries, info); + + /* and save it */ + refresh_sequence_number(domain, True); + centry = centry_start(domain, status); + if (!centry) goto skip_save; + centry_put_uint32(centry, *num_entries); + for (i=0; i<(*num_entries); i++) { + centry_put_string(centry, (*info)[i].acct_name); + centry_put_string(centry, (*info)[i].acct_desc); + centry_put_uint32(centry, (*info)[i].rid); + } + centry_end(centry, "GL/%s/local", domain->name); + centry_free(centry); + +skip_save: + return status; +} + +/* convert a single name to a sid in a domain */ +static NTSTATUS name_to_sid(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *name, + DOM_SID *sid, + enum SID_NAME_USE *type) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + fstring uname; + DOM_SID *sid2; + + if (!cache->tdb) goto do_query; + + fstrcpy(uname, name); + strupper(uname); + centry = wcache_fetch(cache, domain, "NS/%s/%s", domain->name, uname); + if (!centry) goto do_query; + *type = centry_uint32(centry); + sid2 = centry_sid(centry, mem_ctx); + if (!sid2) { + ZERO_STRUCTP(sid); + } else { + sid_copy(sid, sid2); + } + + status = centry->status; + centry_free(centry); + return status; + +do_query: + ZERO_STRUCTP(sid); + + if (wcache_server_down(domain)) { + return NT_STATUS_SERVER_DISABLED; + } + status = cache->backend->name_to_sid(domain, mem_ctx, name, sid, type); + + /* and save it */ + wcache_save_name_to_sid(domain, status, name, sid, *type); + + /* We can't save the sid to name mapping as we don't know the + correct case of the name without looking it up */ + + return status; +} + +/* convert a sid to a user or group name. The sid is guaranteed to be in the domain + given */ +static NTSTATUS sid_to_name(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *sid, + char **name, + enum SID_NAME_USE *type) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + fstring sid_string; + + if (!cache->tdb) goto do_query; + + centry = wcache_fetch(cache, domain, "SN/%s", sid_to_string(sid_string, sid)); + if (!centry) goto do_query; + if (NT_STATUS_IS_OK(centry->status)) { + *type = centry_uint32(centry); + *name = centry_string(centry, mem_ctx); + } + status = centry->status; + centry_free(centry); + return status; + +do_query: + *name = NULL; + + if (wcache_server_down(domain)) { + return NT_STATUS_SERVER_DISABLED; + } + status = cache->backend->sid_to_name(domain, mem_ctx, sid, name, type); + + /* and save it */ + refresh_sequence_number(domain, True); + wcache_save_sid_to_name(domain, status, sid, *name, *type); + wcache_save_name_to_sid(domain, status, *name, sid, *type); + + return status; +} + + +/* Lookup user information from a rid */ +static NTSTATUS query_user(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *user_sid, + WINBIND_USERINFO *info) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + fstring sid_string; + + if (!cache->tdb) goto do_query; + + centry = wcache_fetch(cache, domain, "U/%s", sid_to_string(sid_string, user_sid)); + if (!centry) goto do_query; + + info->acct_name = centry_string(centry, mem_ctx); + info->full_name = centry_string(centry, mem_ctx); + info->user_sid = centry_sid(centry, mem_ctx); + info->group_sid = centry_sid(centry, mem_ctx); + status = centry->status; + centry_free(centry); + return status; + +do_query: + ZERO_STRUCTP(info); + + if (wcache_server_down(domain)) { + return NT_STATUS_SERVER_DISABLED; + } + + status = cache->backend->query_user(domain, mem_ctx, user_sid, info); + + /* and save it */ + refresh_sequence_number(domain, True); + wcache_save_user(domain, status, info); + + return status; +} + + +/* Lookup groups a user is a member of. */ +static NTSTATUS lookup_usergroups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *user_sid, + uint32 *num_groups, DOM_SID ***user_gids) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + unsigned int i; + fstring sid_string; + + if (!cache->tdb) goto do_query; + + centry = wcache_fetch(cache, domain, "UG/%s", sid_to_string(sid_string, user_sid)); + if (!centry) goto do_query; + + *num_groups = centry_uint32(centry); + + if (*num_groups == 0) goto do_cached; + + (*user_gids) = talloc(mem_ctx, sizeof(**user_gids) * (*num_groups)); + if (! (*user_gids)) smb_panic("lookup_usergroups out of memory"); + for (i=0; i<(*num_groups); i++) { + (*user_gids)[i] = centry_sid(centry, mem_ctx); + } + +do_cached: + status = centry->status; + centry_free(centry); + return status; + +do_query: + (*num_groups) = 0; + (*user_gids) = NULL; + + if (wcache_server_down(domain)) { + return NT_STATUS_SERVER_DISABLED; + } + status = cache->backend->lookup_usergroups(domain, mem_ctx, user_sid, num_groups, user_gids); + + /* and save it */ + refresh_sequence_number(domain, True); + centry = centry_start(domain, status); + if (!centry) goto skip_save; + centry_put_uint32(centry, *num_groups); + for (i=0; i<(*num_groups); i++) { + centry_put_sid(centry, (*user_gids)[i]); + } + centry_end(centry, "UG/%s", sid_to_string(sid_string, user_sid)); + centry_free(centry); + +skip_save: + return status; +} + + +static NTSTATUS lookup_groupmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *group_sid, uint32 *num_names, + DOM_SID ***sid_mem, char ***names, + uint32 **name_types) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + unsigned int i; + fstring sid_string; + + if (!cache->tdb) goto do_query; + + centry = wcache_fetch(cache, domain, "GM/%s", sid_to_string(sid_string, group_sid)); + if (!centry) goto do_query; + + *num_names = centry_uint32(centry); + + if (*num_names == 0) goto do_cached; + + (*sid_mem) = talloc(mem_ctx, sizeof(**sid_mem) * (*num_names)); + (*names) = talloc(mem_ctx, sizeof(**names) * (*num_names)); + (*name_types) = talloc(mem_ctx, sizeof(**name_types) * (*num_names)); + + if (! (*sid_mem) || ! (*names) || ! (*name_types)) { + smb_panic("lookup_groupmem out of memory"); + } + + for (i=0; i<(*num_names); i++) { + (*sid_mem)[i] = centry_sid(centry, mem_ctx); + (*names)[i] = centry_string(centry, mem_ctx); + (*name_types)[i] = centry_uint32(centry); + } + +do_cached: + status = centry->status; + centry_free(centry); + return status; + +do_query: + (*num_names) = 0; + (*sid_mem) = NULL; + (*names) = NULL; + (*name_types) = NULL; + + + if (wcache_server_down(domain)) { + return NT_STATUS_SERVER_DISABLED; + } + status = cache->backend->lookup_groupmem(domain, mem_ctx, group_sid, num_names, + sid_mem, names, name_types); + + /* and save it */ + refresh_sequence_number(domain, True); + centry = centry_start(domain, status); + if (!centry) goto skip_save; + centry_put_uint32(centry, *num_names); + for (i=0; i<(*num_names); i++) { + centry_put_sid(centry, (*sid_mem)[i]); + centry_put_string(centry, (*names)[i]); + centry_put_uint32(centry, (*name_types)[i]); + } + centry_end(centry, "GM/%s", sid_to_string(sid_string, group_sid)); + centry_free(centry); + +skip_save: + return status; +} + +/* find the sequence number for a domain */ +static NTSTATUS sequence_number(struct winbindd_domain *domain, uint32 *seq) +{ + refresh_sequence_number(domain, False); + + *seq = domain->sequence_number; + + return NT_STATUS_OK; +} + +/* enumerate trusted domains */ +static NTSTATUS trusted_domains(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_domains, + char ***names, + char ***alt_names, + DOM_SID **dom_sids) +{ + struct winbind_cache *cache = get_cache(domain); + + /* we don't cache this call */ + return cache->backend->trusted_domains(domain, mem_ctx, num_domains, + names, alt_names, dom_sids); +} + +/* find the domain sid */ +static NTSTATUS domain_sid(struct winbindd_domain *domain, DOM_SID *sid) +{ + struct winbind_cache *cache = get_cache(domain); + + /* we don't cache this call */ + return cache->backend->domain_sid(domain, sid); +} + +/* find the alternate names for the domain, if any */ +static NTSTATUS alternate_name(struct winbindd_domain *domain) +{ + struct winbind_cache *cache = get_cache(domain); + + /* we don't cache this call */ + return cache->backend->alternate_name(domain); +} + +/* the ADS backend methods are exposed via this structure */ +struct winbindd_methods cache_methods = { + True, + query_user_list, + enum_dom_groups, + enum_local_groups, + name_to_sid, + sid_to_name, + query_user, + lookup_usergroups, + lookup_groupmem, + sequence_number, + trusted_domains, + domain_sid, + alternate_name +}; diff --git a/source4/nsswitch/winbindd_cm.c b/source4/nsswitch/winbindd_cm.c new file mode 100644 index 0000000000..0748a1c534 --- /dev/null +++ b/source4/nsswitch/winbindd_cm.c @@ -0,0 +1,954 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon connection manager + + Copyright (C) Tim Potter 2001 + Copyright (C) Andrew Bartlett 2002 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + We need to manage connections to domain controllers without having to + mess up the main winbindd code with other issues. The aim of the + connection manager is to: + + - make connections to domain controllers and cache them + - re-establish connections when networks or servers go down + - centralise the policy on connection timeouts, domain controller + selection etc + - manage re-entrancy for when winbindd becomes able to handle + multiple outstanding rpc requests + + Why not have connection management as part of the rpc layer like tng? + Good question. This code may morph into libsmb/rpc_cache.c or something + like that but at the moment it's simply staying as part of winbind. I + think the TNG architecture of forcing every user of the rpc layer to use + the connection caching system is a bad idea. It should be an optional + method of using the routines. + + The TNG design is quite good but I disagree with some aspects of the + implementation. -tpot + + */ + +/* + TODO: + + - I'm pretty annoyed by all the make_nmb_name() stuff. It should be + moved down into another function. + + - There needs to be a utility function in libsmb/namequery.c that does + cm_get_dc_name() + + - Take care when destroying cli_structs as they can be shared between + various sam handles. + + */ + +#include "winbindd.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/* Global list of connections. Initially a DLIST but can become a hash + table or whatever later. */ + +struct winbindd_cm_conn { + struct winbindd_cm_conn *prev, *next; + fstring domain; + fstring controller; + fstring pipe_name; + size_t mutex_ref_count; + struct cli_state *cli; + POLICY_HND pol; +}; + +static struct winbindd_cm_conn *cm_conns = NULL; + +/* Get a domain controller name. Cache positive and negative lookups so we + don't go to the network too often when something is badly broken. */ + +#define GET_DC_NAME_CACHE_TIMEOUT 30 /* Seconds between dc lookups */ + +struct get_dc_name_cache { + fstring domain_name; + fstring srv_name; + time_t lookup_time; + struct get_dc_name_cache *prev, *next; +}; + +/* + find the DC for a domain using methods appropriate for a ADS domain +*/ +static BOOL cm_ads_find_dc(const char *domain, struct in_addr *dc_ip, fstring srv_name) +{ + ADS_STRUCT *ads; + const char *realm = domain; + + if (strcasecmp(realm, lp_workgroup()) == 0) + realm = lp_realm(); + + ads = ads_init(realm, domain, NULL); + if (!ads) + return False; + + /* we don't need to bind, just connect */ + ads->auth.flags |= ADS_AUTH_NO_BIND; + + DEBUG(4,("cm_ads_find_dc: domain=%s\n", domain)); + +#ifdef HAVE_ADS + /* a full ads_connect() is actually overkill, as we don't srictly need + to do the SASL auth in order to get the info we need, but libads + doesn't offer a better way right now */ + ads_connect(ads); +#endif + + if (!ads->config.realm) + return False; + + fstrcpy(srv_name, ads->config.ldap_server_name); + strupper(srv_name); + *dc_ip = ads->ldap_ip; + ads_destroy(&ads); + + DEBUG(4,("cm_ads_find_dc: using server='%s' IP=%s\n", + srv_name, inet_ntoa(*dc_ip))); + + return True; +} + + + +static BOOL cm_get_dc_name(const char *domain, fstring srv_name, struct in_addr *ip_out) +{ + static struct get_dc_name_cache *get_dc_name_cache; + struct get_dc_name_cache *dcc; + struct in_addr dc_ip; + BOOL ret; + + /* Check the cache for previous lookups */ + + for (dcc = get_dc_name_cache; dcc; dcc = dcc->next) { + + if (!strequal(domain, dcc->domain_name)) + continue; /* Not our domain */ + + if ((time(NULL) - dcc->lookup_time) > + GET_DC_NAME_CACHE_TIMEOUT) { + + /* Cache entry has expired, delete it */ + + DEBUG(10, ("get_dc_name_cache entry expired for %s\n", domain)); + + DLIST_REMOVE(get_dc_name_cache, dcc); + SAFE_FREE(dcc); + + break; + } + + /* Return a positive or negative lookup for this domain */ + + if (dcc->srv_name[0]) { + DEBUG(10, ("returning positive get_dc_name_cache entry for %s\n", domain)); + fstrcpy(srv_name, dcc->srv_name); + return True; + } else { + DEBUG(10, ("returning negative get_dc_name_cache entry for %s\n", domain)); + return False; + } + } + + /* Add cache entry for this lookup. */ + + DEBUG(10, ("Creating get_dc_name_cache entry for %s\n", domain)); + + if (!(dcc = (struct get_dc_name_cache *) + malloc(sizeof(struct get_dc_name_cache)))) + return False; + + ZERO_STRUCTP(dcc); + + fstrcpy(dcc->domain_name, domain); + dcc->lookup_time = time(NULL); + + DLIST_ADD(get_dc_name_cache, dcc); + + zero_ip(&dc_ip); + + ret = False; + if (lp_security() == SEC_ADS) + ret = cm_ads_find_dc(domain, &dc_ip, srv_name); + + if (!ret) { + /* fall back on rpc methods if the ADS methods fail */ + ret = rpc_find_dc(domain, srv_name, &dc_ip); + } + + if (!ret) + return False; + + /* We have a name so make the cache entry positive now */ + fstrcpy(dcc->srv_name, srv_name); + + DEBUG(3, ("cm_get_dc_name: Returning DC %s (%s) for domain %s\n", srv_name, + inet_ntoa(dc_ip), domain)); + + *ip_out = dc_ip; + + return True; +} + +/* Choose between anonymous or authenticated connections. We need to use + an authenticated connection if DCs have the RestrictAnonymous registry + entry set > 0, or the "Additional restrictions for anonymous + connections" set in the win2k Local Security Policy. + + Caller to free() result in domain, username, password +*/ + +static void cm_get_ipc_userpass(char **username, char **domain, char **password) +{ + *username = secrets_fetch(SECRETS_AUTH_USER, NULL); + *domain = secrets_fetch(SECRETS_AUTH_DOMAIN, NULL); + *password = secrets_fetch(SECRETS_AUTH_PASSWORD, NULL); + + if (*username && **username) { + + if (!*domain || !**domain) + *domain = smb_xstrdup(lp_workgroup()); + + if (!*password || !**password) + *password = smb_xstrdup(""); + + DEBUG(3, ("IPC$ connections done by user %s\\%s\n", + *domain, *username)); + + } else { + DEBUG(3, ("IPC$ connections done anonymously\n")); + *username = smb_xstrdup(""); + *domain = smb_xstrdup(""); + *password = smb_xstrdup(""); + } +} + +/* Open a new smb pipe connection to a DC on a given domain. Cache + negative creation attempts so we don't try and connect to broken + machines too often. */ + +#define FAILED_CONNECTION_CACHE_TIMEOUT 30 /* Seconds between attempts */ + +struct failed_connection_cache { + fstring domain_name; + fstring controller; + time_t lookup_time; + NTSTATUS nt_status; + struct failed_connection_cache *prev, *next; +}; + +static struct failed_connection_cache *failed_connection_cache; + +/* Add an entry to the failed conneciton cache */ + +static void add_failed_connection_entry(struct winbindd_cm_conn *new_conn, + NTSTATUS result) +{ + struct failed_connection_cache *fcc; + + SMB_ASSERT(!NT_STATUS_IS_OK(result)); + + /* Check we already aren't in the cache */ + + for (fcc = failed_connection_cache; fcc; fcc = fcc->next) { + if (strequal(fcc->domain_name, new_conn->domain)) { + DEBUG(10, ("domain %s already tried and failed\n", + fcc->domain_name)); + return; + } + } + + /* Create negative lookup cache entry for this domain and controller */ + + if (!(fcc = (struct failed_connection_cache *) + malloc(sizeof(struct failed_connection_cache)))) { + DEBUG(0, ("malloc failed in add_failed_connection_entry!\n")); + return; + } + + ZERO_STRUCTP(fcc); + + fstrcpy(fcc->domain_name, new_conn->domain); + fstrcpy(fcc->controller, new_conn->controller); + fcc->lookup_time = time(NULL); + fcc->nt_status = result; + + DLIST_ADD(failed_connection_cache, fcc); +} + +/* Open a connction to the remote server, cache failures for 30 seconds */ + +static NTSTATUS cm_open_connection(const char *domain, const int pipe_index, + struct winbindd_cm_conn *new_conn, BOOL keep_mutex) +{ + struct failed_connection_cache *fcc; + NTSTATUS result; + char *ipc_username, *ipc_domain, *ipc_password; + struct in_addr dc_ip; + int i; + BOOL retry = True; + BOOL got_mutex = False; + + ZERO_STRUCT(dc_ip); + + fstrcpy(new_conn->domain, domain); + fstrcpy(new_conn->pipe_name, get_pipe_name_from_index(pipe_index)); + + /* Look for a domain controller for this domain. Negative results + are cached so don't bother applying the caching for this + function just yet. */ + + if (!cm_get_dc_name(domain, new_conn->controller, &dc_ip)) { + result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + add_failed_connection_entry(new_conn, result); + return result; + } + + /* Return false if we have tried to look up this domain and netbios + name before and failed. */ + + for (fcc = failed_connection_cache; fcc; fcc = fcc->next) { + + if (!(strequal(domain, fcc->domain_name) && + strequal(new_conn->controller, fcc->controller))) + continue; /* Not our domain */ + + if ((time(NULL) - fcc->lookup_time) > + FAILED_CONNECTION_CACHE_TIMEOUT) { + + /* Cache entry has expired, delete it */ + + DEBUG(10, ("cm_open_connection cache entry expired for %s, %s\n", domain, new_conn->controller)); + + DLIST_REMOVE(failed_connection_cache, fcc); + free(fcc); + + break; + } + + /* The timeout hasn't expired yet so return false */ + + DEBUG(10, ("returning negative open_connection_cache entry for %s, %s\n", domain, new_conn->controller)); + + result = fcc->nt_status; + SMB_ASSERT(!NT_STATUS_IS_OK(result)); + return result; + } + + /* Initialise SMB connection */ + + cm_get_ipc_userpass(&ipc_username, &ipc_domain, &ipc_password); + + DEBUG(5, ("connecting to %s from %s with username [%s]\\[%s]\n", + new_conn->controller, lp_netbios_name(), ipc_domain, ipc_username)); + + for (i = 0; retry && (i < 3); i++) { + + if (!secrets_named_mutex(new_conn->controller, WINBIND_SERVER_MUTEX_WAIT_TIME, &new_conn->mutex_ref_count)) { + DEBUG(0,("cm_open_connection: mutex grab failed for %s\n", new_conn->controller)); + result = NT_STATUS_POSSIBLE_DEADLOCK; + continue; + } + + got_mutex = True; + + result = cli_full_connection(&new_conn->cli, lp_netbios_name(), new_conn->controller, + &dc_ip, 0, "IPC$", "IPC", ipc_username, ipc_domain, + ipc_password, 0, &retry); + + if (NT_STATUS_IS_OK(result)) + break; + + secrets_named_mutex_release(new_conn->controller, &new_conn->mutex_ref_count); + got_mutex = False; + } + + SAFE_FREE(ipc_username); + SAFE_FREE(ipc_domain); + SAFE_FREE(ipc_password); + + if (!NT_STATUS_IS_OK(result)) { + if (got_mutex) + secrets_named_mutex_release(new_conn->controller, &new_conn->mutex_ref_count); + add_failed_connection_entry(new_conn, result); + return result; + } + + if ( !cli_nt_session_open (new_conn->cli, pipe_index) ) { + result = NT_STATUS_PIPE_NOT_AVAILABLE; + /* + * only cache a failure if we are not trying to open the + * **win2k** specific lsarpc UUID. This could be an NT PDC + * and therefore a failure is normal. This should probably + * be abstracted to a check for 2k specific pipes and wondering + * if the PDC is an NT4 box. but since there is only one 2k + * specific UUID right now, i'm not going to bother. --jerry + */ + if (got_mutex) + secrets_named_mutex_release(new_conn->controller, &new_conn->mutex_ref_count); + if ( !is_win2k_pipe(pipe_index) ) + add_failed_connection_entry(new_conn, result); + cli_shutdown(new_conn->cli); + return result; + } + + if ((got_mutex) && !keep_mutex) + secrets_named_mutex_release(new_conn->controller, &new_conn->mutex_ref_count); + return NT_STATUS_OK; +} + +/* Return true if a connection is still alive */ + +static BOOL connection_ok(struct winbindd_cm_conn *conn) +{ + if (!conn) { + smb_panic("Invalid paramater passed to conneciton_ok(): conn was NULL!\n"); + return False; + } + + if (!conn->cli) { + DEBUG(0, ("Connection to %s for domain %s (pipe %s) has NULL conn->cli!\n", + conn->controller, conn->domain, conn->pipe_name)); + smb_panic("connection_ok: conn->cli was null!"); + return False; + } + + if (!conn->cli->initialised) { + DEBUG(0, ("Connection to %s for domain %s (pipe %s) was never initialised!\n", + conn->controller, conn->domain, conn->pipe_name)); + smb_panic("connection_ok: conn->cli->initialised is False!"); + return False; + } + + if (conn->cli->fd == -1) { + DEBUG(3, ("Connection to %s for domain %s (pipe %s) has died or was never started (fd == -1)\n", + conn->controller, conn->domain, conn->pipe_name)); + return False; + } + + return True; +} + +/* Get a connection to the remote DC and open the pipe. If there is already a connection, use that */ + +static NTSTATUS get_connection_from_cache(const char *domain, const char *pipe_name, + struct winbindd_cm_conn **conn_out, BOOL keep_mutex) +{ + struct winbindd_cm_conn *conn, conn_temp; + NTSTATUS result; + + for (conn = cm_conns; conn; conn = conn->next) { + if (strequal(conn->domain, domain) && + strequal(conn->pipe_name, pipe_name)) { + if (!connection_ok(conn)) { + if (conn->cli) + cli_shutdown(conn->cli); + ZERO_STRUCT(conn_temp); + conn_temp.next = conn->next; + DLIST_REMOVE(cm_conns, conn); + SAFE_FREE(conn); + conn = &conn_temp; /* Just to keep the loop moving */ + } else { + if (keep_mutex) { + if (!secrets_named_mutex(conn->controller, + WINBIND_SERVER_MUTEX_WAIT_TIME, &conn->mutex_ref_count)) + DEBUG(0,("get_connection_from_cache: mutex grab failed for %s\n", + conn->controller)); + } + break; + } + } + } + + if (!conn) { + if (!(conn = malloc(sizeof(*conn)))) + return NT_STATUS_NO_MEMORY; + + ZERO_STRUCTP(conn); + + if (!NT_STATUS_IS_OK(result = cm_open_connection(domain, get_pipe_index(pipe_name), conn, keep_mutex))) { + DEBUG(3, ("Could not open a connection to %s for %s (%s)\n", + domain, pipe_name, nt_errstr(result))); + SAFE_FREE(conn); + return result; + } + DLIST_ADD(cm_conns, conn); + } + + *conn_out = conn; + return NT_STATUS_OK; +} + + +/********************************************************************************** +**********************************************************************************/ + +BOOL cm_check_for_native_mode_win2k( const char *domain ) +{ + NTSTATUS result; + struct winbindd_cm_conn conn; + DS_DOMINFO_CTR ctr; + BOOL ret = False; + + ZERO_STRUCT( conn ); + ZERO_STRUCT( ctr ); + + + if ( !NT_STATUS_IS_OK(result = cm_open_connection(domain, PI_LSARPC_DS, &conn, False)) ) { + DEBUG(5, ("cm_check_for_native_mode_win2k: Could not open a connection to %s for PIPE_LSARPC (%s)\n", + domain, nt_errstr(result))); + return False; + } + + if ( conn.cli ) { + if ( !NT_STATUS_IS_OK(cli_ds_getprimarydominfo( conn.cli, + conn.cli->mem_ctx, DsRolePrimaryDomainInfoBasic, &ctr)) ) { + ret = False; + goto done; + } + } + + if ( (ctr.basic->flags & DSROLE_PRIMARY_DS_RUNNING) + && !(ctr.basic->flags & DSROLE_PRIMARY_DS_MIXED_MODE) ) + ret = True; + +done: + if ( conn.cli ) + cli_shutdown( conn.cli ); + + return ret; +} + + + +/* Return a LSA policy handle on a domain */ + +CLI_POLICY_HND *cm_get_lsa_handle(const char *domain) +{ + struct winbindd_cm_conn *conn; + uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED; + NTSTATUS result; + static CLI_POLICY_HND hnd; + + /* Look for existing connections */ + + if (!NT_STATUS_IS_OK(result = get_connection_from_cache(domain, PIPE_LSARPC, &conn, False))) + return NULL; + + /* This *shitty* code needs scrapping ! JRA */ + if (policy_handle_is_valid(&conn->pol)) { + hnd.pol = conn->pol; + hnd.cli = conn->cli; + return &hnd; + } + + result = cli_lsa_open_policy(conn->cli, conn->cli->mem_ctx, False, + des_access, &conn->pol); + + if (!NT_STATUS_IS_OK(result)) { + /* Hit the cache code again. This cleans out the old connection and gets a new one */ + if (conn->cli->fd == -1) { /* Try again, if the remote host disapeared */ + if (!NT_STATUS_IS_OK(result = get_connection_from_cache(domain, PIPE_LSARPC, &conn, False))) + return NULL; + + result = cli_lsa_open_policy(conn->cli, conn->cli->mem_ctx, False, + des_access, &conn->pol); + } + + if (!NT_STATUS_IS_OK(result)) { + cli_shutdown(conn->cli); + DLIST_REMOVE(cm_conns, conn); + SAFE_FREE(conn); + return NULL; + } + } + + hnd.pol = conn->pol; + hnd.cli = conn->cli; + + return &hnd; +} + +/* Return a SAM policy handle on a domain */ + +CLI_POLICY_HND *cm_get_sam_handle(char *domain) +{ + struct winbindd_cm_conn *conn; + uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED; + NTSTATUS result; + static CLI_POLICY_HND hnd; + + /* Look for existing connections */ + + if (!NT_STATUS_IS_OK(result = get_connection_from_cache(domain, PIPE_SAMR, &conn, False))) + return NULL; + + /* This *shitty* code needs scrapping ! JRA */ + if (policy_handle_is_valid(&conn->pol)) { + hnd.pol = conn->pol; + hnd.cli = conn->cli; + return &hnd; + } + result = cli_samr_connect(conn->cli, conn->cli->mem_ctx, + des_access, &conn->pol); + + if (!NT_STATUS_IS_OK(result)) { + /* Hit the cache code again. This cleans out the old connection and gets a new one */ + if (conn->cli->fd == -1) { /* Try again, if the remote host disapeared */ + if (!NT_STATUS_IS_OK(result = get_connection_from_cache(domain, PIPE_SAMR, &conn, False))) + return NULL; + + result = cli_samr_connect(conn->cli, conn->cli->mem_ctx, + des_access, &conn->pol); + } + + if (!NT_STATUS_IS_OK(result)) { + cli_shutdown(conn->cli); + DLIST_REMOVE(cm_conns, conn); + SAFE_FREE(conn); + return NULL; + } + } + + hnd.pol = conn->pol; + hnd.cli = conn->cli; + + return &hnd; +} + +#if 0 /* This code now *well* out of date */ + +/* Return a SAM domain policy handle on a domain */ + +CLI_POLICY_HND *cm_get_sam_dom_handle(char *domain, DOM_SID *domain_sid) +{ + struct winbindd_cm_conn *conn, *basic_conn = NULL; + static CLI_POLICY_HND hnd; + NTSTATUS result; + uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED; + + /* Look for existing connections */ + + for (conn = cm_conns; conn; conn = conn->next) { + if (strequal(conn->domain, domain) && + strequal(conn->pipe_name, PIPE_SAMR) && + conn->pipe_data.samr.pipe_type == SAM_PIPE_DOM) { + + if (!connection_ok(conn)) { + /* Shutdown cli? Free conn? Allow retry of DC? */ + DLIST_REMOVE(cm_conns, conn); + return NULL; + } + + goto ok; + } + } + + /* Create a basic handle to open a domain handle from */ + + if (!cm_get_sam_handle(domain)) + return False; + + for (conn = cm_conns; conn; conn = conn->next) { + if (strequal(conn->domain, domain) && + strequal(conn->pipe_name, PIPE_SAMR) && + conn->pipe_data.samr.pipe_type == SAM_PIPE_BASIC) + basic_conn = conn; + } + + if (!(conn = (struct winbindd_cm_conn *) + malloc(sizeof(struct winbindd_cm_conn)))) + return NULL; + + ZERO_STRUCTP(conn); + + fstrcpy(conn->domain, basic_conn->domain); + fstrcpy(conn->controller, basic_conn->controller); + fstrcpy(conn->pipe_name, basic_conn->pipe_name); + + conn->pipe_data.samr.pipe_type = SAM_PIPE_DOM; + conn->cli = basic_conn->cli; + + result = cli_samr_open_domain(conn->cli, conn->cli->mem_ctx, + &basic_conn->pol, des_access, + domain_sid, &conn->pol); + + if (!NT_STATUS_IS_OK(result)) + return NULL; + + /* Add to list */ + + DLIST_ADD(cm_conns, conn); + + ok: + hnd.pol = conn->pol; + hnd.cli = conn->cli; + + return &hnd; +} + +/* Return a SAM policy handle on a domain user */ + +CLI_POLICY_HND *cm_get_sam_user_handle(char *domain, DOM_SID *domain_sid, + uint32 user_rid) +{ + struct winbindd_cm_conn *conn, *basic_conn = NULL; + static CLI_POLICY_HND hnd; + NTSTATUS result; + uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED; + + /* Look for existing connections */ + + for (conn = cm_conns; conn; conn = conn->next) { + if (strequal(conn->domain, domain) && + strequal(conn->pipe_name, PIPE_SAMR) && + conn->pipe_data.samr.pipe_type == SAM_PIPE_USER && + conn->pipe_data.samr.rid == user_rid) { + + if (!connection_ok(conn)) { + /* Shutdown cli? Free conn? Allow retry of DC? */ + DLIST_REMOVE(cm_conns, conn); + return NULL; + } + + goto ok; + } + } + + /* Create a domain handle to open a user handle from */ + + if (!cm_get_sam_dom_handle(domain, domain_sid)) + return NULL; + + for (conn = cm_conns; conn; conn = conn->next) { + if (strequal(conn->domain, domain) && + strequal(conn->pipe_name, PIPE_SAMR) && + conn->pipe_data.samr.pipe_type == SAM_PIPE_DOM) + basic_conn = conn; + } + + if (!basic_conn) { + DEBUG(0, ("No domain sam handle was created!\n")); + return NULL; + } + + if (!(conn = (struct winbindd_cm_conn *) + malloc(sizeof(struct winbindd_cm_conn)))) + return NULL; + + ZERO_STRUCTP(conn); + + fstrcpy(conn->domain, basic_conn->domain); + fstrcpy(conn->controller, basic_conn->controller); + fstrcpy(conn->pipe_name, basic_conn->pipe_name); + + conn->pipe_data.samr.pipe_type = SAM_PIPE_USER; + conn->cli = basic_conn->cli; + conn->pipe_data.samr.rid = user_rid; + + result = cli_samr_open_user(conn->cli, conn->cli->mem_ctx, + &basic_conn->pol, des_access, user_rid, + &conn->pol); + + if (!NT_STATUS_IS_OK(result)) + return NULL; + + /* Add to list */ + + DLIST_ADD(cm_conns, conn); + + ok: + hnd.pol = conn->pol; + hnd.cli = conn->cli; + + return &hnd; +} + +/* Return a SAM policy handle on a domain group */ + +CLI_POLICY_HND *cm_get_sam_group_handle(char *domain, DOM_SID *domain_sid, + uint32 group_rid) +{ + struct winbindd_cm_conn *conn, *basic_conn = NULL; + static CLI_POLICY_HND hnd; + NTSTATUS result; + uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED; + + /* Look for existing connections */ + + for (conn = cm_conns; conn; conn = conn->next) { + if (strequal(conn->domain, domain) && + strequal(conn->pipe_name, PIPE_SAMR) && + conn->pipe_data.samr.pipe_type == SAM_PIPE_GROUP && + conn->pipe_data.samr.rid == group_rid) { + + if (!connection_ok(conn)) { + /* Shutdown cli? Free conn? Allow retry of DC? */ + DLIST_REMOVE(cm_conns, conn); + return NULL; + } + + goto ok; + } + } + + /* Create a domain handle to open a user handle from */ + + if (!cm_get_sam_dom_handle(domain, domain_sid)) + return NULL; + + for (conn = cm_conns; conn; conn = conn->next) { + if (strequal(conn->domain, domain) && + strequal(conn->pipe_name, PIPE_SAMR) && + conn->pipe_data.samr.pipe_type == SAM_PIPE_DOM) + basic_conn = conn; + } + + if (!basic_conn) { + DEBUG(0, ("No domain sam handle was created!\n")); + return NULL; + } + + if (!(conn = (struct winbindd_cm_conn *) + malloc(sizeof(struct winbindd_cm_conn)))) + return NULL; + + ZERO_STRUCTP(conn); + + fstrcpy(conn->domain, basic_conn->domain); + fstrcpy(conn->controller, basic_conn->controller); + fstrcpy(conn->pipe_name, basic_conn->pipe_name); + + conn->pipe_data.samr.pipe_type = SAM_PIPE_GROUP; + conn->cli = basic_conn->cli; + conn->pipe_data.samr.rid = group_rid; + + result = cli_samr_open_group(conn->cli, conn->cli->mem_ctx, + &basic_conn->pol, des_access, group_rid, + &conn->pol); + + if (!NT_STATUS_IS_OK(result)) + return NULL; + + /* Add to list */ + + DLIST_ADD(cm_conns, conn); + + ok: + hnd.pol = conn->pol; + hnd.cli = conn->cli; + + return &hnd; +} + +#endif + +/* Get a handle on a netlogon pipe. This is a bit of a hack to re-use the + netlogon pipe as no handle is returned. */ + +NTSTATUS cm_get_netlogon_cli(const char *domain, const unsigned char *trust_passwd, + struct cli_state **cli) +{ + NTSTATUS result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + struct winbindd_cm_conn *conn; + uint32 neg_flags = 0x000001ff; + + if (!cli) + return NT_STATUS_INVALID_PARAMETER; + + /* Open an initial conection - keep the mutex. */ + + if (!NT_STATUS_IS_OK(result = get_connection_from_cache(domain, PIPE_NETLOGON, &conn, True))) + return result; + + result = cli_nt_setup_creds(conn->cli, get_sec_chan(), trust_passwd, &neg_flags, 2); + + if (conn->mutex_ref_count) + secrets_named_mutex_release(conn->controller, &conn->mutex_ref_count); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(0, ("error connecting to domain password server: %s\n", + nt_errstr(result))); + + /* Hit the cache code again. This cleans out the old connection and gets a new one */ + if (conn->cli->fd == -1) { + + if (!NT_STATUS_IS_OK(result = get_connection_from_cache(domain, PIPE_NETLOGON, &conn, True))) + return result; + + /* Try again */ + result = cli_nt_setup_creds( conn->cli, get_sec_chan(),trust_passwd, &neg_flags, 2); + + if (conn->mutex_ref_count) + secrets_named_mutex_release(conn->controller, &conn->mutex_ref_count); + } + + if (!NT_STATUS_IS_OK(result)) { + cli_shutdown(conn->cli); + DLIST_REMOVE(cm_conns, conn); + SAFE_FREE(conn); + return result; + } + } + + *cli = conn->cli; + + return result; +} + +/* Dump the current connection status */ + +static void dump_conn_list(void) +{ + struct winbindd_cm_conn *con; + + DEBUG(0, ("\tDomain Controller Pipe\n")); + + for(con = cm_conns; con; con = con->next) { + char *msg; + + /* Display pipe info */ + + if (asprintf(&msg, "\t%-15s %-15s %-16s", con->domain, con->controller, con->pipe_name) < 0) { + DEBUG(0, ("Error: not enough memory!\n")); + } else { + DEBUG(0, ("%s\n", msg)); + SAFE_FREE(msg); + } + } +} + +void winbindd_cm_status(void) +{ + /* List open connections */ + + DEBUG(0, ("winbindd connection manager status:\n")); + + if (cm_conns) + dump_conn_list(); + else + DEBUG(0, ("\tNo active connections\n")); +} diff --git a/source4/nsswitch/winbindd_dual.c b/source4/nsswitch/winbindd_dual.c new file mode 100644 index 0000000000..207757bcea --- /dev/null +++ b/source4/nsswitch/winbindd_dual.c @@ -0,0 +1,210 @@ +/* + Unix SMB/CIFS implementation. + + Winbind background daemon + + Copyright (C) Andrew Tridgell 2002 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + the idea of the optional dual daemon mode is ot prevent slow domain + responses from clagging up the rest of the system. When in dual + daemon mode winbindd always responds to requests from cache if the + request is in cache, and if the cached answer is stale then it asks + the "dual daemon" to update the cache for that request + + */ + +#include "winbindd.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +extern BOOL opt_dual_daemon; +BOOL background_process = False; +int dual_daemon_pipe = -1; + + +/* a list of requests ready to be sent to the dual daemon */ +struct dual_list { + struct dual_list *next; + char *data; + int length; + int offset; +}; + +static struct dual_list *dual_list; +static struct dual_list *dual_list_end; + +/* + setup a select() including the dual daemon pipe + */ +int dual_select_setup(fd_set *fds, int maxfd) +{ + if (dual_daemon_pipe == -1 || + !dual_list) { + return maxfd; + } + + FD_SET(dual_daemon_pipe, fds); + if (dual_daemon_pipe > maxfd) { + maxfd = dual_daemon_pipe; + } + return maxfd; +} + + +/* + a hook called from the main winbindd select() loop to handle writes + to the dual daemon pipe +*/ +void dual_select(fd_set *fds) +{ + int n; + + if (dual_daemon_pipe == -1 || + !dual_list || + !FD_ISSET(dual_daemon_pipe, fds)) { + return; + } + + n = sys_write(dual_daemon_pipe, + &dual_list->data[dual_list->offset], + dual_list->length - dual_list->offset); + + if (n <= 0) { + /* the pipe is dead! fall back to normal operation */ + dual_daemon_pipe = -1; + return; + } + + dual_list->offset += n; + + if (dual_list->offset == dual_list->length) { + struct dual_list *next; + next = dual_list->next; + free(dual_list->data); + free(dual_list); + dual_list = next; + if (!dual_list) { + dual_list_end = NULL; + } + } +} + +/* + send a request to the background daemon + this is called for stale cached entries +*/ +void dual_send_request(struct winbindd_cli_state *state) +{ + struct dual_list *list; + + if (!background_process) return; + + list = malloc(sizeof(*list)); + if (!list) return; + + list->next = NULL; + list->data = memdup(&state->request, sizeof(state->request)); + list->length = sizeof(state->request); + list->offset = 0; + + if (!dual_list_end) { + dual_list = list; + dual_list_end = list; + } else { + dual_list_end->next = list; + dual_list_end = list; + } + + background_process = False; +} + + +/* +the main dual daemon +*/ +void do_dual_daemon(void) +{ + int fdpair[2]; + struct winbindd_cli_state state; + + if (pipe(fdpair) != 0) { + return; + } + + ZERO_STRUCT(state); + state.pid = getpid(); + + dual_daemon_pipe = fdpair[1]; + state.sock = fdpair[0]; + + if (fork() != 0) { + close(fdpair[0]); + return; + } + close(fdpair[1]); + + if (!winbind_setup_common()) + _exit(0); + + dual_daemon_pipe = -1; + opt_dual_daemon = False; + + while (1) { + /* free up any talloc memory */ + lp_talloc_free(); + main_loop_talloc_free(); + + /* fetch a request from the main daemon */ + winbind_client_read(&state); + + if (state.finished) { + /* we lost contact with our parent */ + exit(0); + } + + /* process full rquests */ + if (state.read_buf_len == sizeof(state.request)) { + DEBUG(4,("dual daemon request %d\n", (int)state.request.cmd)); + + /* special handling for the stateful requests */ + switch (state.request.cmd) { + case WINBINDD_GETPWENT: + winbindd_setpwent(&state); + break; + + case WINBINDD_GETGRENT: + case WINBINDD_GETGRLST: + winbindd_setgrent(&state); + break; + default: + break; + } + + winbind_process_packet(&state); + SAFE_FREE(state.response.extra_data); + + free_getent_state(state.getpwent_state); + free_getent_state(state.getgrent_state); + state.getpwent_state = NULL; + state.getgrent_state = NULL; + } + } +} + diff --git a/source4/nsswitch/winbindd_group.c b/source4/nsswitch/winbindd_group.c new file mode 100644 index 0000000000..d06db5943c --- /dev/null +++ b/source4/nsswitch/winbindd_group.c @@ -0,0 +1,896 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon for ntdom nss module + + Copyright (C) Tim Potter 2000 + Copyright (C) Jeremy Allison 2001. + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "winbindd.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/*************************************************************** + Empty static struct for negative caching. +****************************************************************/ + +/* Fill a grent structure from various other information */ + +static BOOL fill_grent(struct winbindd_gr *gr, const char *dom_name, + const char *gr_name, gid_t unix_gid) +{ + fstring full_group_name; + /* Fill in uid/gid */ + fill_domain_username(full_group_name, dom_name, gr_name); + + gr->gr_gid = unix_gid; + + /* Group name and password */ + + safe_strcpy(gr->gr_name, full_group_name, sizeof(gr->gr_name) - 1); + safe_strcpy(gr->gr_passwd, "x", sizeof(gr->gr_passwd) - 1); + + return True; +} + +/* Fill in the group membership field of a NT group given by group_sid */ + +static BOOL fill_grent_mem(struct winbindd_domain *domain, + DOM_SID *group_sid, + enum SID_NAME_USE group_name_type, + int *num_gr_mem, char **gr_mem, int *gr_mem_len) +{ + DOM_SID **sid_mem = NULL; + uint32 num_names = 0; + uint32 *name_types = NULL; + unsigned int buf_len, buf_ndx, i; + char **names = NULL, *buf; + BOOL result = False; + TALLOC_CTX *mem_ctx; + NTSTATUS status; + fstring sid_string; + + if (!(mem_ctx = talloc_init("fill_grent_mem(%s)", domain->name))) + return False; + + /* Initialise group membership information */ + + DEBUG(10, ("group SID %s\n", sid_to_string(sid_string, group_sid))); + + *num_gr_mem = 0; + + if (group_name_type != SID_NAME_DOM_GRP) { + DEBUG(1, ("SID %s in domain %s isn't a domain group\n", + sid_to_string(sid_string, group_sid), domain->name)); + goto done; + } + + /* Lookup group members */ + status = domain->methods->lookup_groupmem(domain, mem_ctx, group_sid, &num_names, + &sid_mem, &names, &name_types); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("could not lookup membership for group rid %s in domain %s (error: %s)\n", + sid_to_string(sid_string, group_sid), domain->name, nt_errstr(status))); + + goto done; + } + + DEBUG(10, ("looked up %d names\n", num_names)); + + if (DEBUGLEVEL >= 10) { + for (i = 0; i < num_names; i++) + DEBUG(10, ("\t%20s %s %d\n", names[i], sid_to_string(sid_string, sid_mem[i]), + name_types[i])); + } + + /* Add members to list */ + + buf = NULL; + buf_len = buf_ndx = 0; + + again: + + for (i = 0; i < num_names; i++) { + char *the_name; + fstring name; + int len; + + the_name = names[i]; + + DEBUG(10, ("processing name %s\n", the_name)); + + /* FIXME: need to cope with groups within groups. These + occur in Universal groups on a Windows 2000 native mode + server. */ + + if (name_types[i] != SID_NAME_USER) { + DEBUG(3, ("name %s isn't a domain user\n", the_name)); + continue; + } + + /* Don't bother with machine accounts */ + + if (the_name[strlen(the_name) - 1] == '$') { + DEBUG(10, ("%s is machine account\n", the_name)); + continue; + } + + /* Append domain name */ + + fill_domain_username(name, domain->name, the_name); + + len = strlen(name); + + /* Add to list or calculate buffer length */ + + if (!buf) { + buf_len += len + 1; /* List is comma separated */ + (*num_gr_mem)++; + DEBUG(10, ("buf_len + %d = %d\n", len + 1, buf_len)); + } else { + DEBUG(10, ("appending %s at ndx %d\n", name, len)); + safe_strcpy(&buf[buf_ndx], name, len); + buf_ndx += len; + buf[buf_ndx] = ','; + buf_ndx++; + } + } + + /* Allocate buffer */ + + if (!buf && buf_len != 0) { + if (!(buf = malloc(buf_len))) { + DEBUG(1, ("out of memory\n")); + result = False; + goto done; + } + memset(buf, 0, buf_len); + goto again; + } + + if (buf && buf_ndx > 0) { + buf[buf_ndx - 1] = '\0'; + } + + *gr_mem = buf; + *gr_mem_len = buf_len; + + DEBUG(10, ("num_mem = %d, len = %d, mem = %s\n", *num_gr_mem, + buf_len, *num_gr_mem ? buf : "NULL")); + result = True; + +done: + + talloc_destroy(mem_ctx); + + DEBUG(10, ("fill_grent_mem returning %d\n", result)); + + return result; +} + +/* Return a group structure from a group name */ + +enum winbindd_result winbindd_getgrnam(struct winbindd_cli_state *state) +{ + DOM_SID group_sid; + struct winbindd_domain *domain; + enum SID_NAME_USE name_type; + fstring name_domain, name_group; + char *tmp, *gr_mem; + gid_t gid; + int gr_mem_len; + + /* Ensure null termination */ + state->request.data.groupname[sizeof(state->request.data.groupname)-1]='\0'; + + DEBUG(3, ("[%5d]: getgrnam %s\n", state->pid, + state->request.data.groupname)); + + /* Parse domain and groupname */ + + memset(name_group, 0, sizeof(fstring)); + + tmp = state->request.data.groupname; + if (!parse_domain_user(tmp, name_domain, name_group)) + return WINBINDD_ERROR; + + /* Get info for the domain */ + + if ((domain = find_domain_from_name(name_domain)) == NULL) { + DEBUG(0, ("could not get domain sid for domain %s\n", + name_domain)); + return WINBINDD_ERROR; + } + + /* Get rid and name type from name */ + + if (!winbindd_lookup_sid_by_name(domain, name_group, &group_sid, + &name_type)) { + DEBUG(1, ("group %s in domain %s does not exist\n", + name_group, name_domain)); + return WINBINDD_ERROR; + } + + if ((name_type != SID_NAME_ALIAS) && (name_type != SID_NAME_DOM_GRP)) { + DEBUG(1, ("name '%s' is not a local or domain group: %d\n", + name_group, name_type)); + return WINBINDD_ERROR; + } + + if (!winbindd_idmap_get_gid_from_sid(&group_sid, &gid)) { + DEBUG(1, ("error converting unix gid to sid\n")); + return WINBINDD_ERROR; + } + + if (!fill_grent(&state->response.data.gr, name_domain, + name_group, gid) || + !fill_grent_mem(domain, &group_sid, name_type, + &state->response.data.gr.num_gr_mem, + &gr_mem, &gr_mem_len)) { + return WINBINDD_ERROR; + } + + /* Group membership lives at start of extra data */ + + state->response.data.gr.gr_mem_ofs = 0; + + state->response.length += gr_mem_len; + state->response.extra_data = gr_mem; + + return WINBINDD_OK; +} + +/* Return a group structure from a gid number */ + +enum winbindd_result winbindd_getgrgid(struct winbindd_cli_state *state) +{ + struct winbindd_domain *domain; + DOM_SID group_sid; + enum SID_NAME_USE name_type; + fstring dom_name; + fstring group_name; + int gr_mem_len; + char *gr_mem; + + DEBUG(3, ("[%5d]: getgrgid %d\n", state->pid, + state->request.data.gid)); + + /* Bug out if the gid isn't in the winbind range */ + + if ((state->request.data.gid < server_state.gid_low) || + (state->request.data.gid > server_state.gid_high)) + return WINBINDD_ERROR; + + /* Get rid from gid */ + + if (!winbindd_idmap_get_sid_from_gid(state->request.data.gid, &group_sid)) { + DEBUG(1, ("could not convert gid %d to rid\n", + state->request.data.gid)); + return WINBINDD_ERROR; + } + + /* Get name from sid */ + + if (!winbindd_lookup_name_by_sid(&group_sid, dom_name, group_name, &name_type)) { + DEBUG(1, ("could not lookup sid\n")); + return WINBINDD_ERROR; + } + + if (!((name_type == SID_NAME_ALIAS) || + (name_type == SID_NAME_DOM_GRP))) { + DEBUG(1, ("name '%s' is not a local or domain group: %d\n", + group_name, name_type)); + return WINBINDD_ERROR; + } + + /* Fill in group structure */ + + domain = find_domain_from_sid(&group_sid); + + if (!domain) { + DEBUG(1,("Can't find domain from sid\n")); + return WINBINDD_ERROR; + } + + if (!fill_grent(&state->response.data.gr, dom_name, group_name, + state->request.data.gid) || + !fill_grent_mem(domain, &group_sid, name_type, + &state->response.data.gr.num_gr_mem, + &gr_mem, &gr_mem_len)) + return WINBINDD_ERROR; + + /* Group membership lives at start of extra data */ + + state->response.data.gr.gr_mem_ofs = 0; + + state->response.length += gr_mem_len; + state->response.extra_data = gr_mem; + + return WINBINDD_OK; +} + +/* + * set/get/endgrent functions + */ + +/* "Rewind" file pointer for group database enumeration */ + +enum winbindd_result winbindd_setgrent(struct winbindd_cli_state *state) +{ + struct winbindd_domain *domain; + + DEBUG(3, ("[%5d]: setgrent\n", state->pid)); + + /* Check user has enabled this */ + + if (!lp_winbind_enum_groups()) + return WINBINDD_ERROR; + + /* Free old static data if it exists */ + + if (state->getgrent_state != NULL) { + free_getent_state(state->getgrent_state); + state->getgrent_state = NULL; + } + + /* Create sam pipes for each domain we know about */ + + for (domain = domain_list(); domain != NULL; domain = domain->next) { + struct getent_state *domain_state; + + /* Create a state record for this domain */ + + if ((domain_state = (struct getent_state *) + malloc(sizeof(struct getent_state))) == NULL) { + DEBUG(1, ("winbindd_setgrent: malloc failed for domain_state!\n")); + return WINBINDD_ERROR; + } + + ZERO_STRUCTP(domain_state); + + fstrcpy(domain_state->domain_name, domain->name); + + /* Add to list of open domains */ + + DLIST_ADD(state->getgrent_state, domain_state); + } + + return WINBINDD_OK; +} + +/* Close file pointer to ntdom group database */ + +enum winbindd_result winbindd_endgrent(struct winbindd_cli_state *state) +{ + DEBUG(3, ("[%5d]: endgrent\n", state->pid)); + + free_getent_state(state->getgrent_state); + state->getgrent_state = NULL; + + return WINBINDD_OK; +} + +/* Get the list of domain groups and domain aliases for a domain. We fill in + the sam_entries and num_sam_entries fields with domain group information. + The dispinfo_ndx field is incremented to the index of the next group to + fetch. Return True if some groups were returned, False otherwise. */ + +#define MAX_FETCH_SAM_ENTRIES 100 + +static BOOL get_sam_group_entries(struct getent_state *ent) +{ + NTSTATUS status; + uint32 num_entries; + struct acct_info *name_list = NULL, *tmp_name_list = NULL; + TALLOC_CTX *mem_ctx; + BOOL result = False; + struct acct_info *sam_grp_entries = NULL; + struct winbindd_domain *domain; + + if (ent->got_sam_entries) + return False; + + if (!(mem_ctx = talloc_init("get_sam_group_entries(%s)", + ent->domain_name))) { + DEBUG(1, ("get_sam_group_entries: could not create talloc context!\n")); + return False; + } + + /* Free any existing group info */ + + SAFE_FREE(ent->sam_entries); + ent->num_sam_entries = 0; + ent->got_sam_entries = True; + + /* Enumerate domain groups */ + + num_entries = 0; + + if (!(domain = find_domain_from_name(ent->domain_name))) { + DEBUG(3, ("no such domain %s in get_sam_group_entries\n", ent->domain_name)); + goto done; + } + + /* always get the domain global groups */ + + status = domain->methods->enum_dom_groups(domain, mem_ctx, &num_entries, &sam_grp_entries); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("get_sam_group_entries: could not enumerate domain groups! Error: %s\n", nt_errstr(status))); + result = False; + goto done; + } + + /* Copy entries into return buffer */ + + if (num_entries) { + if ( !(name_list = malloc(sizeof(struct acct_info) * num_entries)) ) { + DEBUG(0,("get_sam_group_entries: Failed to malloc memory for %d domain groups!\n", + num_entries)); + result = False; + goto done; + } + memcpy( name_list, sam_grp_entries, num_entries * sizeof(struct acct_info) ); + } + + ent->num_sam_entries = num_entries; + + /* get the domain local groups if we are a member of + a native win2k domain */ + + if ( domain->native_mode && domain->methods->enum_local_groups ) + { + DEBUG(4,("get_sam_group_entries: Native Mode 2k domain; enumerating local groups as well\n")); + + status = domain->methods->enum_local_groups(domain, mem_ctx, &num_entries, &sam_grp_entries); + + if ( !NT_STATUS_IS_OK(status) ) { + DEBUG(3,("get_sam_group_entries: Failed to enumerate domain local groups!\n")); + num_entries = 0; + } + else + DEBUG(4,("get_sam_group_entries: Returned %d local groups\n", num_entries)); + + /* Copy entries into return buffer */ + + if ( num_entries ) { + if ( !(tmp_name_list = Realloc( name_list, sizeof(struct acct_info) * (ent->num_sam_entries+num_entries))) ) + { + DEBUG(0,("get_sam_group_entries: Failed to realloc more memory for %d local groups!\n", + num_entries)); + result = False; + SAFE_FREE( name_list ); + goto done; + } + + name_list = tmp_name_list; + + memcpy( &name_list[ent->num_sam_entries], sam_grp_entries, + num_entries * sizeof(struct acct_info) ); + } + + ent->num_sam_entries += num_entries; + } + + + /* Fill in remaining fields */ + + ent->sam_entries = name_list; + ent->sam_entry_index = 0; + + result = (ent->num_sam_entries > 0); + + done: + talloc_destroy(mem_ctx); + + return result; +} + +/* Fetch next group entry from ntdom database */ + +#define MAX_GETGRENT_GROUPS 500 + +enum winbindd_result winbindd_getgrent(struct winbindd_cli_state *state) +{ + struct getent_state *ent; + struct winbindd_gr *group_list = NULL; + int num_groups, group_list_ndx = 0, i, gr_mem_list_len = 0; + char *new_extra_data, *gr_mem_list = NULL; + + DEBUG(3, ("[%5d]: getgrent\n", state->pid)); + + /* Check user has enabled this */ + + if (!lp_winbind_enum_groups()) + return WINBINDD_ERROR; + + num_groups = MIN(MAX_GETGRENT_GROUPS, state->request.data.num_entries); + + if ((state->response.extra_data = + malloc(num_groups * sizeof(struct winbindd_gr))) == NULL) + return WINBINDD_ERROR; + + state->response.data.num_entries = 0; + + group_list = (struct winbindd_gr *)state->response.extra_data; + + if (!(ent = state->getgrent_state)) + return WINBINDD_ERROR; + + /* Start sending back groups */ + + for (i = 0; i < num_groups; i++) { + struct acct_info *name_list = NULL; + fstring domain_group_name; + uint32 result; + gid_t group_gid; + int gr_mem_len; + char *gr_mem, *new_gr_mem_list; + DOM_SID group_sid; + struct winbindd_domain *domain; + + /* Do we need to fetch another chunk of groups? */ + + tryagain: + + DEBUG(10, ("entry_index = %d, num_entries = %d\n", + ent->sam_entry_index, ent->num_sam_entries)); + + if (ent->num_sam_entries == ent->sam_entry_index) { + + while(ent && !get_sam_group_entries(ent)) { + struct getent_state *next_ent; + + DEBUG(10, ("freeing state info for domain %s\n", ent->domain_name)); + + /* Free state information for this domain */ + + SAFE_FREE(ent->sam_entries); + + next_ent = ent->next; + DLIST_REMOVE(state->getgrent_state, ent); + + SAFE_FREE(ent); + ent = next_ent; + } + + /* No more domains */ + + if (!ent) + break; + } + + name_list = ent->sam_entries; + + if (!(domain = + find_domain_from_name(ent->domain_name))) { + DEBUG(3, ("No such domain %s in winbindd_getgrent\n", ent->domain_name)); + result = False; + goto done; + } + + /* Lookup group info */ + + sid_copy(&group_sid, &domain->sid); + sid_append_rid(&group_sid, name_list[ent->sam_entry_index].rid); + + if (!winbindd_idmap_get_gid_from_sid( + &group_sid, + &group_gid)) { + + DEBUG(1, ("could not look up gid for group %s\n", + name_list[ent->sam_entry_index].acct_name)); + + ent->sam_entry_index++; + goto tryagain; + } + + DEBUG(10, ("got gid %d for group %x\n", group_gid, + name_list[ent->sam_entry_index].rid)); + + /* Fill in group entry */ + + fill_domain_username(domain_group_name, ent->domain_name, + name_list[ent->sam_entry_index].acct_name); + + result = fill_grent(&group_list[group_list_ndx], + ent->domain_name, + name_list[ent->sam_entry_index].acct_name, + group_gid); + + /* Fill in group membership entry */ + + if (result) { + DOM_SID member_sid; + group_list[group_list_ndx].num_gr_mem = 0; + gr_mem = NULL; + gr_mem_len = 0; + + /* Get group membership */ + if (state->request.cmd == WINBINDD_GETGRLST) { + result = True; + } else { + sid_copy(&member_sid, &domain->sid); + sid_append_rid(&member_sid, name_list[ent->sam_entry_index].rid); + result = fill_grent_mem( + domain, + &member_sid, + SID_NAME_DOM_GRP, + &group_list[group_list_ndx].num_gr_mem, + &gr_mem, &gr_mem_len); + } + } + + if (result) { + /* Append to group membership list */ + new_gr_mem_list = Realloc( + gr_mem_list, + gr_mem_list_len + gr_mem_len); + + if (!new_gr_mem_list && (group_list[group_list_ndx].num_gr_mem != 0)) { + DEBUG(0, ("out of memory\n")); + SAFE_FREE(gr_mem_list); + gr_mem_list_len = 0; + break; + } + + DEBUG(10, ("list_len = %d, mem_len = %d\n", + gr_mem_list_len, gr_mem_len)); + + gr_mem_list = new_gr_mem_list; + + memcpy(&gr_mem_list[gr_mem_list_len], gr_mem, + gr_mem_len); + + SAFE_FREE(gr_mem); + + group_list[group_list_ndx].gr_mem_ofs = + gr_mem_list_len; + + gr_mem_list_len += gr_mem_len; + } + + ent->sam_entry_index++; + + /* Add group to return list */ + + if (result) { + + DEBUG(10, ("adding group num_entries = %d\n", + state->response.data.num_entries)); + + group_list_ndx++; + state->response.data.num_entries++; + + state->response.length += + sizeof(struct winbindd_gr); + + } else { + DEBUG(0, ("could not lookup domain group %s\n", + domain_group_name)); + } + } + + /* Copy the list of group memberships to the end of the extra data */ + + if (group_list_ndx == 0) + goto done; + + new_extra_data = Realloc( + state->response.extra_data, + group_list_ndx * sizeof(struct winbindd_gr) + gr_mem_list_len); + + if (!new_extra_data) { + DEBUG(0, ("out of memory\n")); + group_list_ndx = 0; + SAFE_FREE(state->response.extra_data); + SAFE_FREE(gr_mem_list); + + return WINBINDD_ERROR; + } + + state->response.extra_data = new_extra_data; + + memcpy(&((char *)state->response.extra_data) + [group_list_ndx * sizeof(struct winbindd_gr)], + gr_mem_list, gr_mem_list_len); + + SAFE_FREE(gr_mem_list); + + state->response.length += gr_mem_list_len; + + DEBUG(10, ("returning %d groups, length = %d\n", + group_list_ndx, gr_mem_list_len)); + + /* Out of domains */ + + done: + + return (group_list_ndx > 0) ? WINBINDD_OK : WINBINDD_ERROR; +} + +/* List domain groups without mapping to unix ids */ + +enum winbindd_result winbindd_list_groups(struct winbindd_cli_state *state) +{ + uint32 total_entries = 0; + struct winbindd_domain *domain; + char *extra_data = NULL; + char *ted = NULL; + unsigned int extra_data_len = 0, i; + + DEBUG(3, ("[%5d]: list groups\n", state->pid)); + + /* Enumerate over trusted domains */ + + for (domain = domain_list(); domain; domain = domain->next) { + struct getent_state groups; + + ZERO_STRUCT(groups); + + /* Get list of sam groups */ + ZERO_STRUCT(groups); + fstrcpy(groups.domain_name, domain->name); + + get_sam_group_entries(&groups); + + if (groups.num_sam_entries == 0) { + /* this domain is empty or in an error state */ + continue; + } + + /* keep track the of the total number of groups seen so + far over all domains */ + total_entries += groups.num_sam_entries; + + /* Allocate some memory for extra data. Note that we limit + account names to sizeof(fstring) = 128 characters. */ + ted = Realloc(extra_data, sizeof(fstring) * total_entries); + + if (!ted) { + DEBUG(0,("failed to enlarge buffer!\n")); + SAFE_FREE(extra_data); + return WINBINDD_ERROR; + } else + extra_data = ted; + + /* Pack group list into extra data fields */ + for (i = 0; i < groups.num_sam_entries; i++) { + char *group_name = ((struct acct_info *) + groups.sam_entries)[i].acct_name; + fstring name; + + fill_domain_username(name, domain->name, group_name); + /* Append to extra data */ + memcpy(&extra_data[extra_data_len], name, + strlen(name)); + extra_data_len += strlen(name); + extra_data[extra_data_len++] = ','; + } + + free(groups.sam_entries); + } + + /* Assign extra_data fields in response structure */ + if (extra_data) { + extra_data[extra_data_len - 1] = '\0'; + state->response.extra_data = extra_data; + state->response.length += extra_data_len; + } + + /* No domains may have responded but that's still OK so don't + return an error. */ + + return WINBINDD_OK; +} + +/* Get user supplementary groups. This is much quicker than trying to + invert the groups database. */ + +enum winbindd_result winbindd_getgroups(struct winbindd_cli_state *state) +{ + fstring name_domain, name_user; + DOM_SID user_sid; + enum SID_NAME_USE name_type; + uint32 num_groups, num_gids; + NTSTATUS status; + DOM_SID **user_gids; + struct winbindd_domain *domain; + enum winbindd_result result = WINBINDD_ERROR; + gid_t *gid_list; + unsigned int i; + TALLOC_CTX *mem_ctx; + + /* Ensure null termination */ + state->request.data.username[sizeof(state->request.data.username)-1]='\0'; + + DEBUG(3, ("[%5d]: getgroups %s\n", state->pid, + state->request.data.username)); + + if (!(mem_ctx = talloc_init("winbindd_getgroups(%s)", + state->request.data.username))) + return WINBINDD_ERROR; + + /* Parse domain and username */ + + if (!parse_domain_user(state->request.data.username, name_domain, + name_user)) + goto done; + + /* Get info for the domain */ + + if ((domain = find_domain_from_name(name_domain)) == NULL) { + DEBUG(0, ("could not find domain entry for domain %s\n", + name_domain)); + goto done; + } + + /* Get rid and name type from name. The following costs 1 packet */ + + if (!winbindd_lookup_sid_by_name(domain, name_user, &user_sid, + &name_type)) { + DEBUG(1, ("user '%s' does not exist\n", name_user)); + goto done; + } + + if (name_type != SID_NAME_USER) { + DEBUG(1, ("name '%s' is not a user name: %d\n", + name_user, name_type)); + goto done; + } + + status = domain->methods->lookup_usergroups(domain, mem_ctx, + &user_sid, &num_groups, + &user_gids); + if (!NT_STATUS_IS_OK(status)) goto done; + + /* Copy data back to client */ + + num_gids = 0; + gid_list = malloc(sizeof(gid_t) * num_groups); + + if (state->response.extra_data) + goto done; + + for (i = 0; i < num_groups; i++) { + if (!winbindd_idmap_get_gid_from_sid( + user_gids[i], + &gid_list[num_gids])) { + fstring sid_string; + + DEBUG(1, ("unable to convert group sid %s to gid\n", + sid_to_string(sid_string, user_gids[i]))); + continue; + } + + num_gids++; + } + + state->response.data.num_entries = num_gids; + state->response.extra_data = gid_list; + state->response.length += num_gids * sizeof(gid_t); + + result = WINBINDD_OK; + + done: + + talloc_destroy(mem_ctx); + + return result; +} diff --git a/source4/nsswitch/winbindd_idmap.c b/source4/nsswitch/winbindd_idmap.c new file mode 100644 index 0000000000..de547cde41 --- /dev/null +++ b/source4/nsswitch/winbindd_idmap.c @@ -0,0 +1,196 @@ +/* + Unix SMB/CIFS implementation. + Winbind ID Mapping + Copyright (C) Tim Potter 2000 + Copyright (C) Anthony Liguori <aliguor@us.ibm.com> 2003 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "winbindd.h" + +static struct { + const char *name; + /* Function to create a member of the idmap_methods list */ + BOOL (*reg_meth)(struct idmap_methods **methods); + struct idmap_methods *methods; +} builtin_idmap_functions[] = { + { "tdb", winbind_idmap_reg_tdb, NULL }, + /* { "ldap", winbind_idmap_reg_ldap, NULL },*/ + { NULL, NULL, NULL } +}; + +/* singleton pattern: uberlazy evaluation */ +static struct idmap_methods *impl; + +static struct idmap_methods *get_impl(const char *name) +{ + int i = 0; + struct idmap_methods *ret = NULL; + + while (builtin_idmap_functions[i].name && + strcmp(builtin_idmap_functions[i].name, name)) { + i++; + } + + if (builtin_idmap_functions[i].name) { + if (!builtin_idmap_functions[i].methods) { + builtin_idmap_functions[i].reg_meth(&builtin_idmap_functions[i].methods); + } + + ret = builtin_idmap_functions[i].methods; + } + + return ret; +} + +/* Initialize backend */ +BOOL winbindd_idmap_init(void) +{ + BOOL ret = False; + + DEBUG(3, ("winbindd_idmap_init: using '%s' as backend\n", + lp_idmap_backend())); + + if (!impl) { + impl = get_impl(lp_idmap_backend()); + if (!impl) { + DEBUG(0, ("winbindd_idmap_init: could not load backend '%s'\n", + lp_idmap_backend())); + } + } + + if (impl) { + ret = impl->init(); + } + + DEBUG(3, ("winbind_idmap_init: returning %s\n", ret ? "true" : "false")); + + return ret; +} + +/* Get UID from SID */ +BOOL winbindd_idmap_get_uid_from_sid(DOM_SID *sid, uid_t *uid) +{ + BOOL ret = False; + + if (!impl) { + impl = get_impl(lp_idmap_backend()); + if (!impl) { + DEBUG(0, ("winbindd_idmap_init: could not load backend '%s'\n", + lp_idmap_backend())); + } + } + + if (impl) { + ret = impl->get_uid_from_sid(sid, uid); + } + + return ret; +} + +/* Get GID from SID */ +BOOL winbindd_idmap_get_gid_from_sid(DOM_SID *sid, gid_t *gid) +{ + BOOL ret = False; + + if (!impl) { + impl = get_impl(lp_idmap_backend()); + if (!impl) { + DEBUG(0, ("winbindd_idmap_init: could not load backend '%s'\n", + lp_idmap_backend())); + } + } + + if (impl) { + ret = impl->get_gid_from_sid(sid, gid); + } + + return ret; +} + +/* Get SID from UID */ +BOOL winbindd_idmap_get_sid_from_uid(uid_t uid, DOM_SID *sid) +{ + BOOL ret = False; + + if (!impl) { + impl = get_impl(lp_idmap_backend()); + if (!impl) { + DEBUG(0, ("winbindd_idmap_init: could not load backend '%s'\n", + lp_idmap_backend())); + } + } + + if (impl) { + ret = impl->get_sid_from_uid(uid, sid); + } + + return ret; +} + +/* Get SID from GID */ +BOOL winbindd_idmap_get_sid_from_gid(gid_t gid, DOM_SID *sid) +{ + BOOL ret = False; + + if (!impl) { + impl = get_impl(lp_idmap_backend()); + } + + if (impl) { + ret = impl->get_sid_from_gid(gid, sid); + } else { + DEBUG(0, ("winbindd_idmap_init: could not load backend '%s'\n", + lp_idmap_backend())); + } + + return ret; +} + +/* Close backend */ +BOOL winbindd_idmap_close(void) +{ + BOOL ret = False; + + if (!impl) { + impl = get_impl(lp_idmap_backend()); + } + + if (impl) { + ret = impl->close(); + } else { + DEBUG(0, ("winbindd_idmap_init: could not load backend '%s'\n", + lp_idmap_backend())); + } + + return ret; +} + +/* Dump backend status */ +void winbindd_idmap_status(void) +{ + if (!impl) { + impl = get_impl(lp_idmap_backend()); + } + + if (impl) { + impl->status(); + } else { + DEBUG(0, ("winbindd_idmap_init: could not load backend '%s'\n", + lp_idmap_backend())); + } +} + diff --git a/source4/nsswitch/winbindd_idmap_tdb.c b/source4/nsswitch/winbindd_idmap_tdb.c new file mode 100644 index 0000000000..911b3b41d2 --- /dev/null +++ b/source4/nsswitch/winbindd_idmap_tdb.c @@ -0,0 +1,441 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - user related function + + Copyright (C) Tim Potter 2000 + Copyright (C) Anthony Liguori 2003 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "winbindd.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/* High water mark keys */ +#define HWM_GROUP "GROUP HWM" +#define HWM_USER "USER HWM" + +/* idmap version determines auto-conversion */ +#define IDMAP_VERSION 2 + +/* Globals */ +static TDB_CONTEXT *idmap_tdb; + +/* convert one record to the new format */ +static int tdb_convert_fn(TDB_CONTEXT * tdb, TDB_DATA key, TDB_DATA data, + void *ignored) +{ + struct winbindd_domain *domain; + char *p; + DOM_SID sid; + uint32 rid; + fstring keystr; + fstring dom_name; + TDB_DATA key2; + + p = strchr(key.dptr, '/'); + if (!p) + return 0; + + *p = 0; + fstrcpy(dom_name, key.dptr); + *p++ = '/'; + + domain = find_domain_from_name(dom_name); + if (!domain) { + /* We must delete the old record. */ + DEBUG(0, + ("winbindd: tdb_convert_fn : Unable to find domain %s\n", + dom_name)); + DEBUG(0, + ("winbindd: tdb_convert_fn : deleting record %s\n", + key.dptr)); + tdb_delete(idmap_tdb, key); + return 0; + } + + rid = atoi(p); + + sid_copy(&sid, &domain->sid); + sid_append_rid(&sid, rid); + + sid_to_string(keystr, &sid); + key2.dptr = keystr; + key2.dsize = strlen(keystr) + 1; + + if (tdb_store(idmap_tdb, key2, data, TDB_INSERT) != 0) { + /* not good! */ + DEBUG(0, + ("winbindd: tdb_convert_fn : Unable to update record %s\n", + key2.dptr)); + DEBUG(0, + ("winbindd: tdb_convert_fn : conversion failed - idmap corrupt ?\n")); + return -1; + } + + if (tdb_store(idmap_tdb, data, key2, TDB_REPLACE) != 0) { + /* not good! */ + DEBUG(0, + ("winbindd: tdb_convert_fn : Unable to update record %s\n", + data.dptr)); + DEBUG(0, + ("winbindd: tdb_convert_fn : conversion failed - idmap corrupt ?\n")); + return -1; + } + + tdb_delete(idmap_tdb, key); + + return 0; +} + +/***************************************************************************** + Convert the idmap database from an older version. +*****************************************************************************/ +static BOOL tdb_idmap_convert(const char *idmap_name) +{ + int32 vers = tdb_fetch_int32(idmap_tdb, "IDMAP_VERSION"); + BOOL bigendianheader = + (idmap_tdb->flags & TDB_BIGENDIAN) ? True : False; + + if (vers == IDMAP_VERSION) + return True; + + if (((vers == -1) && bigendianheader) + || (IREV(vers) == IDMAP_VERSION)) { + /* Arrggghh ! Bytereversed or old big-endian - make order independent ! */ + /* + * high and low records were created on a + * big endian machine and will need byte-reversing. + */ + + int32 wm; + + wm = tdb_fetch_int32(idmap_tdb, HWM_USER); + + if (wm != -1) { + wm = IREV(wm); + } else + wm = server_state.uid_low; + + if (tdb_store_int32(idmap_tdb, HWM_USER, wm) == -1) { + DEBUG(0, + ("tdb_idmap_convert: Unable to byteswap user hwm in idmap database\n")); + return False; + } + + wm = tdb_fetch_int32(idmap_tdb, HWM_GROUP); + if (wm != -1) { + wm = IREV(wm); + } else + wm = server_state.gid_low; + + if (tdb_store_int32(idmap_tdb, HWM_GROUP, wm) == -1) { + DEBUG(0, + ("tdb_idmap_convert: Unable to byteswap group hwm in idmap database\n")); + return False; + } + } + + /* the old format stored as DOMAIN/rid - now we store the SID direct */ + tdb_traverse(idmap_tdb, tdb_convert_fn, NULL); + + if (tdb_store_int32(idmap_tdb, "IDMAP_VERSION", IDMAP_VERSION) == + -1) { + DEBUG(0, + ("tdb_idmap_convert: Unable to byteswap group hwm in idmap database\n")); + return False; + } + + return True; +} + +/* Allocate either a user or group id from the pool */ +static BOOL tdb_allocate_id(uid_t * id, BOOL isgroup) +{ + int hwm; + + /* Get current high water mark */ + if ((hwm = tdb_fetch_int32(idmap_tdb, + isgroup ? HWM_GROUP : HWM_USER)) == + -1) { + return False; + } + + /* Return next available uid in list */ + if ((isgroup && (hwm > server_state.gid_high)) || + (!isgroup && (hwm > server_state.uid_high))) { + DEBUG(0, + ("winbind %sid range full!\n", isgroup ? "g" : "u")); + return False; + } + + if (id) { + *id = hwm; + } + + hwm++; + + /* Store new high water mark */ + tdb_store_int32(idmap_tdb, isgroup ? HWM_GROUP : HWM_USER, hwm); + + return True; +} + +/* Get a sid from an id */ +static BOOL tdb_get_sid_from_id(int id, DOM_SID * sid, BOOL isgroup) +{ + TDB_DATA key, data; + fstring keystr; + BOOL result = False; + + slprintf(keystr, sizeof(keystr), "%s %d", isgroup ? "GID" : "UID", + id); + + key.dptr = keystr; + key.dsize = strlen(keystr) + 1; + + data = tdb_fetch(idmap_tdb, key); + + if (data.dptr) { + result = string_to_sid(sid, data.dptr); + SAFE_FREE(data.dptr); + } + + return result; +} + +/* Get an id from a sid */ +static BOOL tdb_get_id_from_sid(DOM_SID * sid, uid_t * id, BOOL isgroup) +{ + TDB_DATA data, key; + fstring keystr; + BOOL result = False; + + /* Check if sid is present in database */ + sid_to_string(keystr, sid); + + key.dptr = keystr; + key.dsize = strlen(keystr) + 1; + + data = tdb_fetch(idmap_tdb, key); + + if (data.dptr) { + fstring scanstr; + int the_id; + + /* Parse and return existing uid */ + fstrcpy(scanstr, isgroup ? "GID" : "UID"); + fstrcat(scanstr, " %d"); + + if (sscanf(data.dptr, scanstr, &the_id) == 1) { + /* Store uid */ + if (id) { + *id = the_id; + } + + result = True; + } + + SAFE_FREE(data.dptr); + } else { + + /* Allocate a new id for this sid */ + if (id && tdb_allocate_id(id, isgroup)) { + fstring keystr2; + + /* Store new id */ + slprintf(keystr2, sizeof(keystr2), "%s %d", + isgroup ? "GID" : "UID", *id); + + data.dptr = keystr2; + data.dsize = strlen(keystr2) + 1; + + tdb_store(idmap_tdb, key, data, TDB_REPLACE); + tdb_store(idmap_tdb, data, key, TDB_REPLACE); + + result = True; + } + } + + return result; +} + +/***************************************************************************** + Initialise idmap database. +*****************************************************************************/ +static BOOL tdb_idmap_init(void) +{ + /* Open tdb cache */ + if (!(idmap_tdb = tdb_open_log(lock_path("winbindd_idmap.tdb"), 0, + TDB_DEFAULT, O_RDWR | O_CREAT, + 0600))) { + DEBUG(0, + ("winbindd_idmap_init: Unable to open idmap database\n")); + return False; + } + + /* possibly convert from an earlier version */ + if (!tdb_idmap_convert(lock_path("winbindd_idmap.tdb"))) { + DEBUG(0, + ("winbindd_idmap_init: Unable to open idmap database\n")); + return False; + } + + /* Create high water marks for group and user id */ + if (tdb_fetch_int32(idmap_tdb, HWM_USER) == -1) { + if (tdb_store_int32 + (idmap_tdb, HWM_USER, server_state.uid_low) == -1) { + DEBUG(0, + ("winbindd_idmap_init: Unable to initialise user hwm in idmap database\n")); + return False; + } + } + + if (tdb_fetch_int32(idmap_tdb, HWM_GROUP) == -1) { + if (tdb_store_int32 + (idmap_tdb, HWM_GROUP, server_state.gid_low) == -1) { + DEBUG(0, + ("winbindd_idmap_init: Unable to initialise group hwm in idmap database\n")); + return False; + } + } + + return True; +} + +/* Get a sid from a uid */ +static BOOL tdb_get_sid_from_uid(uid_t uid, DOM_SID * sid) +{ + return tdb_get_sid_from_id((int) uid, sid, False); +} + +/* Get a sid from a gid */ +static BOOL tdb_get_sid_from_gid(gid_t gid, DOM_SID * sid) +{ + return tdb_get_sid_from_id((int) gid, sid, True); +} + +/* Get a uid from a sid */ +static BOOL tdb_get_uid_from_sid(DOM_SID * sid, uid_t * uid) +{ + return tdb_get_id_from_sid(sid, uid, False); +} + +/* Get a gid from a group sid */ +static BOOL tdb_get_gid_from_sid(DOM_SID * sid, gid_t * gid) +{ + return tdb_get_id_from_sid(sid, gid, True); +} + +/* Close the tdb */ +static BOOL tdb_idmap_close(void) +{ + if (idmap_tdb) + return (tdb_close(idmap_tdb) == 0); + return True; +} + + +/* Dump status information to log file. Display different stuff based on + the debug level: + + Debug Level Information Displayed + ================================================================= + 0 Percentage of [ug]id range allocated + 0 High water marks (next allocated ids) +*/ + +#define DUMP_INFO 0 + +static void tdb_idmap_status(void) +{ + int user_hwm, group_hwm; + + DEBUG(0, ("winbindd idmap status:\n")); + + /* Get current high water marks */ + + if ((user_hwm = tdb_fetch_int32(idmap_tdb, HWM_USER)) == -1) { + DEBUG(DUMP_INFO, + ("\tCould not get userid high water mark!\n")); + } + + if ((group_hwm = tdb_fetch_int32(idmap_tdb, HWM_GROUP)) == -1) { + DEBUG(DUMP_INFO, + ("\tCould not get groupid high water mark!\n")); + } + + /* Display next ids to allocate */ + + if (user_hwm != -1) { + DEBUG(DUMP_INFO, + ("\tNext userid to allocate is %d\n", user_hwm)); + } + + if (group_hwm != -1) { + DEBUG(DUMP_INFO, + ("\tNext groupid to allocate is %d\n", group_hwm)); + } + + /* Display percentage of id range already allocated. */ + + if (user_hwm != -1) { + int num_users = user_hwm - server_state.uid_low; + int total_users = + server_state.uid_high - server_state.uid_low; + + DEBUG(DUMP_INFO, + ("\tUser id range is %d%% full (%d of %d)\n", + num_users * 100 / total_users, num_users, + total_users)); + } + + if (group_hwm != -1) { + int num_groups = group_hwm - server_state.gid_low; + int total_groups = + server_state.gid_high - server_state.gid_low; + + DEBUG(DUMP_INFO, + ("\tGroup id range is %d%% full (%d of %d)\n", + num_groups * 100 / total_groups, num_groups, + total_groups)); + } + + /* Display complete mapping of users and groups to rids */ +} + +struct idmap_methods tdb_idmap_methods = { + tdb_idmap_init, + + tdb_get_sid_from_uid, + tdb_get_sid_from_gid, + + tdb_get_uid_from_sid, + tdb_get_gid_from_sid, + + tdb_idmap_close, + + tdb_idmap_status +}; + +BOOL winbind_idmap_reg_tdb(struct idmap_methods **meth) +{ + *meth = &tdb_idmap_methods; + + return True; +} diff --git a/source4/nsswitch/winbindd_misc.c b/source4/nsswitch/winbindd_misc.c new file mode 100644 index 0000000000..b85cd0570d --- /dev/null +++ b/source4/nsswitch/winbindd_misc.c @@ -0,0 +1,235 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - miscellaneous other functions + + Copyright (C) Tim Potter 2000 + Copyright (C) Andrew Bartlett 2002 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "winbindd.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/* Check the machine account password is valid */ + +enum winbindd_result winbindd_check_machine_acct(struct winbindd_cli_state *state) +{ + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + uchar trust_passwd[16]; + int num_retries = 0; + struct cli_state *cli; + DEBUG(3, ("[%5d]: check machine account\n", state->pid)); + + /* Get trust account password */ + + again: + if (!secrets_fetch_trust_account_password( + lp_workgroup(), trust_passwd, NULL)) { + result = NT_STATUS_INTERNAL_ERROR; + goto done; + } + + /* This call does a cli_nt_setup_creds() which implicitly checks + the trust account password. */ + + /* Don't shut this down - it belongs to the connection cache code */ + result = cm_get_netlogon_cli(lp_workgroup(), trust_passwd, &cli); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(3, ("could not open handle to NETLOGON pipe\n")); + goto done; + } + + /* There is a race condition between fetching the trust account + password and the periodic machine password change. So it's + possible that the trust account password has been changed on us. + We are returned NT_STATUS_ACCESS_DENIED if this happens. */ + +#define MAX_RETRIES 8 + + if ((num_retries < MAX_RETRIES) && + NT_STATUS_V(result) == NT_STATUS_V(NT_STATUS_ACCESS_DENIED)) { + num_retries++; + goto again; + } + + /* Pass back result code - zero for success, other values for + specific failures. */ + + DEBUG(3, ("secret is %s\n", NT_STATUS_IS_OK(result) ? + "good" : "bad")); + + done: + state->response.data.auth.nt_status = NT_STATUS_V(result); + fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result)); + fstrcpy(state->response.data.auth.error_string, nt_errstr(result)); + state->response.data.auth.pam_error = nt_status_to_pam(result); + + DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, ("Checking the trust account password returned %s\n", + state->response.data.auth.nt_status_string)); + + return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR; +} + +enum winbindd_result winbindd_list_trusted_domains(struct winbindd_cli_state + *state) +{ + struct winbindd_domain *domain; + int total_entries = 0, extra_data_len = 0; + char *ted, *extra_data = NULL; + + DEBUG(3, ("[%5d]: list trusted domains\n", state->pid)); + + /* We need to refresh the trusted domain list as the domains may + have changed since we last looked. There may be a sequence + number or something we should use but I haven't found it yet. */ + + if (!init_domain_list()) { + DEBUG(1, ("winbindd_list_trusted_domains: could not " + "refresh trusted domain list\n")); + return WINBINDD_ERROR; + } + + for(domain = domain_list(); domain; domain = domain->next) { + + /* Skip own domain */ + + if (strequal(domain->name, lp_workgroup())) continue; + + /* Add domain to list */ + + total_entries++; + ted = Realloc(extra_data, sizeof(fstring) * + total_entries); + + if (!ted) { + DEBUG(0,("winbindd_list_trusted_domains: failed to enlarge buffer!\n")); + SAFE_FREE(extra_data); + return WINBINDD_ERROR; + } else + extra_data = ted; + + memcpy(&extra_data[extra_data_len], domain->name, + strlen(domain->name)); + + extra_data_len += strlen(domain->name); + extra_data[extra_data_len++] = ','; + } + + if (extra_data) { + if (extra_data_len > 1) + extra_data[extra_data_len - 1] = '\0'; + state->response.extra_data = extra_data; + state->response.length += extra_data_len; + } + + return WINBINDD_OK; +} + + +enum winbindd_result winbindd_show_sequence(struct winbindd_cli_state *state) +{ + struct winbindd_domain *domain; + char *extra_data = NULL; + + DEBUG(3, ("[%5d]: show sequence\n", state->pid)); + + extra_data = strdup(""); + + /* this makes for a very simple data format, and is easily parsable as well + if that is ever needed */ + for (domain = domain_list(); domain; domain = domain->next) { + char *s; + + domain->methods->sequence_number(domain, &domain->sequence_number); + + if (DOM_SEQUENCE_NONE == (unsigned)domain->sequence_number) { + asprintf(&s,"%s%s : DISCONNECTED\n", extra_data, + domain->name); + } else { + asprintf(&s,"%s%s : %u\n", extra_data, + domain->name, (unsigned)domain->sequence_number); + } + free(extra_data); + extra_data = s; + } + + state->response.extra_data = extra_data; + /* must add one to length to copy the 0 for string termination */ + state->response.length += strlen(extra_data) + 1; + + return WINBINDD_OK; +} + +enum winbindd_result winbindd_ping(struct winbindd_cli_state + *state) +{ + DEBUG(3, ("[%5d]: ping\n", state->pid)); + + return WINBINDD_OK; +} + +/* List various tidbits of information */ + +enum winbindd_result winbindd_info(struct winbindd_cli_state *state) +{ + + DEBUG(3, ("[%5d]: request misc info\n", state->pid)); + + state->response.data.info.winbind_separator = *lp_winbind_separator(); + fstrcpy(state->response.data.info.samba_version, VERSION); + + return WINBINDD_OK; +} + +/* Tell the client the current interface version */ + +enum winbindd_result winbindd_interface_version(struct winbindd_cli_state *state) +{ + + DEBUG(3, ("[%5d]: request interface version\n", state->pid)); + + state->response.data.interface_version = WINBIND_INTERFACE_VERSION; + + return WINBINDD_OK; +} + +/* What domain are we a member of? */ + +enum winbindd_result winbindd_domain_name(struct winbindd_cli_state *state) +{ + + DEBUG(3, ("[%5d]: request domain name\n", state->pid)); + + fstrcpy(state->response.data.domain_name, lp_workgroup()); + + return WINBINDD_OK; +} + +/* What's my name again? */ + +enum winbindd_result winbindd_netbios_name(struct winbindd_cli_state *state) +{ + + DEBUG(3, ("[%5d]: request netbios name\n", state->pid)); + + fstrcpy(state->response.data.netbios_name, lp_netbios_name()); + + return WINBINDD_OK; +} diff --git a/source4/nsswitch/winbindd_nss.h b/source4/nsswitch/winbindd_nss.h new file mode 100644 index 0000000000..2c87a77100 --- /dev/null +++ b/source4/nsswitch/winbindd_nss.h @@ -0,0 +1,242 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon for ntdom nss module + + Copyright (C) Tim Potter 2000 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef SAFE_FREE +#define SAFE_FREE(x) do { if(x) {free(x); x=NULL;} } while(0) +#endif + +#ifndef _WINBINDD_NTDOM_H +#define _WINBINDD_NTDOM_H + +#define WINBINDD_SOCKET_NAME "pipe" /* Name of PF_UNIX socket */ +#define WINBINDD_SOCKET_DIR "/tmp/.winbindd" /* Name of PF_UNIX dir */ + +#define WINBINDD_DOMAIN_ENV "WINBINDD_DOMAIN" /* Environment variables */ +#define WINBINDD_DONT_ENV "_NO_WINBINDD" + +/* Update this when you change the interface. */ + +#define WINBIND_INTERFACE_VERSION 7 + +/* Socket commands */ + +enum winbindd_cmd { + + WINBINDD_INTERFACE_VERSION, /* Always a well known value */ + + /* Get users and groups */ + + WINBINDD_GETPWNAM, + WINBINDD_GETPWUID, + WINBINDD_GETGRNAM, + WINBINDD_GETGRGID, + WINBINDD_GETGROUPS, + + /* Enumerate users and groups */ + + WINBINDD_SETPWENT, + WINBINDD_ENDPWENT, + WINBINDD_GETPWENT, + WINBINDD_SETGRENT, + WINBINDD_ENDGRENT, + WINBINDD_GETGRENT, + + /* PAM authenticate and password change */ + + WINBINDD_PAM_AUTH, + WINBINDD_PAM_AUTH_CRAP, + WINBINDD_PAM_CHAUTHTOK, + + /* List various things */ + + WINBINDD_LIST_USERS, /* List w/o rid->id mapping */ + WINBINDD_LIST_GROUPS, /* Ditto */ + WINBINDD_LIST_TRUSTDOM, + + /* SID conversion */ + + WINBINDD_LOOKUPSID, + WINBINDD_LOOKUPNAME, + + /* Lookup functions */ + + WINBINDD_SID_TO_UID, + WINBINDD_SID_TO_GID, + WINBINDD_UID_TO_SID, + WINBINDD_GID_TO_SID, + + /* Miscellaneous other stuff */ + + WINBINDD_CHECK_MACHACC, /* Check machine account pw works */ + WINBINDD_PING, /* Just tell me winbind is running */ + WINBINDD_INFO, /* Various bit of info. Currently just tidbits */ + WINBINDD_DOMAIN_NAME, /* The domain this winbind server is a member of (lp_workgroup()) */ + + WINBINDD_SHOW_SEQUENCE, /* display sequence numbers of domains */ + + /* WINS commands */ + + WINBINDD_WINS_BYIP, + WINBINDD_WINS_BYNAME, + + /* this is like GETGRENT but gives an empty group list */ + WINBINDD_GETGRLST, + + WINBINDD_NETBIOS_NAME, /* The netbios name of the server */ + /* Placeholder for end of cmd list */ + + WINBINDD_NUM_CMDS +}; + +#define WINBIND_PAM_INFO3_NDR 0x0001 +#define WINBIND_PAM_INFO3_TEXT 0x0002 +#define WINBIND_PAM_NTKEY 0x0004 +#define WINBIND_PAM_LMKEY 0x0008 +#define WINBIND_PAM_CONTACT_TRUSTDOM 0x0010 + +/* Winbind request structure */ + +struct winbindd_request { + uint32 length; + enum winbindd_cmd cmd; /* Winbindd command to execute */ + pid_t pid; /* pid of calling process */ + + union { + fstring winsreq; /* WINS request */ + fstring username; /* getpwnam */ + fstring groupname; /* getgrnam */ + uid_t uid; /* getpwuid, uid_to_sid */ + gid_t gid; /* getgrgid, gid_to_sid */ + struct { + /* We deliberatedly don't split into domain/user to + avoid having the client know what the separator + character is. */ + fstring user; + fstring pass; + } auth; /* pam_winbind auth module */ + struct { + unsigned char chal[8]; + fstring user; + fstring domain; + fstring lm_resp; + uint16 lm_resp_len; + fstring nt_resp; + uint16 nt_resp_len; + fstring workstation; + uint32 flags; + } auth_crap; + struct { + fstring user; + fstring oldpass; + fstring newpass; + } chauthtok; /* pam_winbind passwd module */ + fstring sid; /* lookupsid, sid_to_[ug]id */ + struct { + fstring dom_name; /* lookupname */ + fstring name; + } name; + uint32 num_entries; /* getpwent, getgrent */ + } data; + char null_term; +}; + +/* Response values */ + +enum winbindd_result { + WINBINDD_ERROR, + WINBINDD_OK +}; + +/* Winbind response structure */ + +struct winbindd_response { + + /* Header information */ + + uint32 length; /* Length of response */ + enum winbindd_result result; /* Result code */ + + /* Fixed length return data */ + + union { + int interface_version; /* Try to ensure this is always in the same spot... */ + + fstring winsresp; /* WINS response */ + + /* getpwnam, getpwuid */ + + struct winbindd_pw { + fstring pw_name; + fstring pw_passwd; + uid_t pw_uid; + gid_t pw_gid; + fstring pw_gecos; + fstring pw_dir; + fstring pw_shell; + } pw; + + /* getgrnam, getgrgid */ + + struct winbindd_gr { + fstring gr_name; + fstring gr_passwd; + gid_t gr_gid; + int num_gr_mem; + int gr_mem_ofs; /* offset to group membership */ + } gr; + + uint32 num_entries; /* getpwent, getgrent */ + struct winbindd_sid { + fstring sid; /* lookupname, [ug]id_to_sid */ + int type; + } sid; + struct winbindd_name { + fstring dom_name; /* lookupsid */ + fstring name; + int type; + } name; + uid_t uid; /* sid_to_uid */ + gid_t gid; /* sid_to_gid */ + struct winbindd_info { + char winbind_separator; + fstring samba_version; + } info; + fstring domain_name; + fstring netbios_name; + + struct auth_reply { + uint32 nt_status; + fstring nt_status_string; + fstring error_string; + int pam_error; + char nt_session_key[16]; + char first_8_lm_hash[8]; + } auth; + } data; + + /* Variable length return data */ + + void *extra_data; /* getgrnam, getgrgid, getgrent */ +}; + +#endif diff --git a/source4/nsswitch/winbindd_pam.c b/source4/nsswitch/winbindd_pam.c new file mode 100644 index 0000000000..8a0326b42c --- /dev/null +++ b/source4/nsswitch/winbindd_pam.c @@ -0,0 +1,368 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - pam auth funcions + + Copyright (C) Andrew Tridgell 2000 + Copyright (C) Tim Potter 2001 + Copyright (C) Andrew Bartlett 2001-2002 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "winbindd.h" +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + + +static NTSTATUS append_info3_as_ndr(TALLOC_CTX *mem_ctx, + struct winbindd_cli_state *state, + NET_USER_INFO_3 *info3) +{ + prs_struct ps; + uint32 size; + if (!prs_init(&ps, 256 /* Random, non-zero number */, mem_ctx, MARSHALL)) { + return NT_STATUS_NO_MEMORY; + } + if (!net_io_user_info3("", info3, &ps, 1, 3)) { + prs_mem_free(&ps); + return NT_STATUS_UNSUCCESSFUL; + } + + size = prs_data_size(&ps); + state->response.extra_data = malloc(size); + if (!state->response.extra_data) { + prs_mem_free(&ps); + return NT_STATUS_NO_MEMORY; + } + prs_copy_all_data_out(state->response.extra_data, &ps); + state->response.length += size; + prs_mem_free(&ps); + return NT_STATUS_OK; +} + +/* Return a password structure from a username. */ + +enum winbindd_result winbindd_pam_auth(struct winbindd_cli_state *state) +{ + NTSTATUS result; + fstring name_domain, name_user; + unsigned char trust_passwd[16]; + time_t last_change_time; + uint32 smb_uid_low; + NET_USER_INFO_3 info3; + struct cli_state *cli = NULL; + uchar chal[8]; + TALLOC_CTX *mem_ctx = NULL; + DATA_BLOB lm_resp; + DATA_BLOB nt_resp; + + /* Ensure null termination */ + state->request.data.auth.user[sizeof(state->request.data.auth.user)-1]='\0'; + + /* Ensure null termination */ + state->request.data.auth.pass[sizeof(state->request.data.auth.pass)-1]='\0'; + + DEBUG(3, ("[%5d]: pam auth %s\n", state->pid, + state->request.data.auth.user)); + + if (!(mem_ctx = talloc_init("winbind pam auth for %s", state->request.data.auth.user))) { + DEBUG(0, ("winbindd_pam_auth: could not talloc_init()!\n")); + result = NT_STATUS_NO_MEMORY; + goto done; + } + + /* Parse domain and username */ + + if (!parse_domain_user(state->request.data.auth.user, name_domain, + name_user)) { + DEBUG(5,("no domain separator (%s) in username (%s) - failing auth\n", lp_winbind_separator(), state->request.data.auth.user)); + result = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + { + unsigned char local_lm_response[24]; + unsigned char local_nt_response[24]; + + generate_random_buffer(chal, 8, False); + SMBencrypt(state->request.data.auth.pass, chal, local_lm_response); + + SMBNTencrypt(state->request.data.auth.pass, chal, local_nt_response); + + lm_resp = data_blob_talloc(mem_ctx, local_lm_response, sizeof(local_lm_response)); + nt_resp = data_blob_talloc(mem_ctx, local_nt_response, sizeof(local_nt_response)); + } + + /* + * Get the machine account password for our primary domain + */ + + if (!secrets_fetch_trust_account_password( + lp_workgroup(), trust_passwd, &last_change_time)) { + DEBUG(0, ("winbindd_pam_auth: could not fetch trust account " + "password for domain %s\n", lp_workgroup())); + result = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + goto done; + } + + /* We really don't care what LUID we give the user. */ + + generate_random_buffer( (unsigned char *)&smb_uid_low, 4, False); + + ZERO_STRUCT(info3); + + /* Don't shut this down - it belongs to the connection cache code */ + result = cm_get_netlogon_cli(lp_workgroup(), trust_passwd, &cli); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(3, ("could not open handle to NETLOGON pipe\n")); + goto done; + } + + result = cli_netlogon_sam_network_logon(cli, mem_ctx, + name_user, name_domain, + lp_netbios_name(), chal, + lm_resp, nt_resp, + &info3); + + uni_group_cache_store_netlogon(mem_ctx, &info3); +done: + + state->response.data.auth.nt_status = NT_STATUS_V(result); + fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result)); + fstrcpy(state->response.data.auth.error_string, get_friendly_nt_error_msg(result)); + state->response.data.auth.pam_error = nt_status_to_pam(result); + + DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, ("Plain-text authentication for user %s returned %s (PAM: %d)\n", + state->request.data.auth.user, + state->response.data.auth.nt_status_string, + state->response.data.auth.pam_error)); + + if (mem_ctx) + talloc_destroy(mem_ctx); + + return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR; +} + +/* Challenge Response Authentication Protocol */ + +enum winbindd_result winbindd_pam_auth_crap(struct winbindd_cli_state *state) +{ + NTSTATUS result; + unsigned char trust_passwd[16]; + time_t last_change_time; + NET_USER_INFO_3 info3; + struct cli_state *cli = NULL; + TALLOC_CTX *mem_ctx = NULL; + char *user = NULL; + const char *domain = NULL; + const char *contact_domain; + const char *workstation; + + DATA_BLOB lm_resp, nt_resp; + + /* Ensure null termination */ + state->request.data.auth_crap.user[sizeof(state->request.data.auth_crap.user)-1]='\0'; + + /* Ensure null termination */ + state->request.data.auth_crap.domain[sizeof(state->request.data.auth_crap.domain)-1]='\0'; + + if (!(mem_ctx = talloc_init("winbind pam auth crap for (utf8) %s", state->request.data.auth_crap.user))) { + DEBUG(0, ("winbindd_pam_auth_crap: could not talloc_init()!\n")); + result = NT_STATUS_NO_MEMORY; + goto done; + } + + if (pull_utf8_talloc(mem_ctx, &user, state->request.data.auth_crap.user) == (size_t)-1) { + DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n")); + } + + if (*state->request.data.auth_crap.domain) { + char *dom = NULL; + if (pull_utf8_talloc(mem_ctx, &dom, state->request.data.auth_crap.domain) == (size_t)-1) { + DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n")); + } + domain = dom; + } else if (lp_winbind_use_default_domain()) { + domain = lp_workgroup(); + } else { + DEBUG(5,("no domain specified with username (%s) - failing auth\n", + user)); + result = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + DEBUG(3, ("[%5d]: pam auth crap domain: %s user: %s\n", state->pid, + domain, user)); + + if (lp_allow_trusted_domains() && (state->request.data.auth_crap.flags & WINBIND_PAM_CONTACT_TRUSTDOM)) { + contact_domain = domain; + } else { + contact_domain = lp_workgroup(); + } + + if (*state->request.data.auth_crap.workstation) { + char *wrk = NULL; + if (pull_utf8_talloc(mem_ctx, &wrk, state->request.data.auth_crap.workstation) == (size_t)-1) { + DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n")); + } + workstation = wrk; + } else { + workstation = lp_netbios_name(); + } + + if (state->request.data.auth_crap.lm_resp_len > sizeof(state->request.data.auth_crap.lm_resp) + || state->request.data.auth_crap.nt_resp_len > sizeof(state->request.data.auth_crap.nt_resp)) { + DEBUG(0, ("winbindd_pam_auth_crap: invalid password length %u/%u\n", + state->request.data.auth_crap.lm_resp_len, + state->request.data.auth_crap.nt_resp_len)); + result = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + lm_resp = data_blob_talloc(mem_ctx, state->request.data.auth_crap.lm_resp, state->request.data.auth_crap.lm_resp_len); + nt_resp = data_blob_talloc(mem_ctx, state->request.data.auth_crap.nt_resp, state->request.data.auth_crap.nt_resp_len); + + /* + * Get the machine account password for the domain to contact. + * This is either our own domain for a workstation, or possibly + * any domain for a PDC with trusted domains. + */ + + if (!secrets_fetch_trust_account_password ( + contact_domain, trust_passwd, &last_change_time)) { + DEBUG(0, ("winbindd_pam_auth: could not fetch trust account " + "password for domain %s\n", contact_domain)); + result = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + goto done; + } + + ZERO_STRUCT(info3); + + /* Don't shut this down - it belongs to the connection cache code */ + result = cm_get_netlogon_cli(contact_domain, trust_passwd, &cli); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(3, ("could not open handle to NETLOGON pipe (error: %s)\n", nt_errstr(result))); + goto done; + } + + result = cli_netlogon_sam_network_logon(cli, mem_ctx, + user, domain, + workstation, state->request.data.auth_crap.chal, + lm_resp, nt_resp, + &info3); + + if (NT_STATUS_IS_OK(result)) { + uni_group_cache_store_netlogon(mem_ctx, &info3); + if (state->request.data.auth_crap.flags & WINBIND_PAM_INFO3_NDR) { + result = append_info3_as_ndr(mem_ctx, state, &info3); + } + +#if 0 + /* we don't currently do this stuff right */ + /* Doing an assert in a daemon is going to be a pretty bad + idea. - tpot */ + if (state->request.data.auth_crap.flags & WINBIND_PAM_NTKEY) { + SMB_ASSERT(sizeof(state->response.data.auth.nt_session_key) == sizeof(info3.user_sess_key)); + memcpy(state->response.data.auth.nt_session_key, info3.user_sess_key, sizeof(state->response.data.auth.nt_session_key) /* 16 */); + } + if (state->request.data.auth_crap.flags & WINBIND_PAM_LMKEY) { + SMB_ASSERT(sizeof(state->response.data.auth.nt_session_key) <= sizeof(info3.user_sess_key)); + memcpy(state->response.data.auth.first_8_lm_hash, info3.padding, sizeof(state->response.data.auth.nt_session_key) /* 16 */); + } +#endif + } + +done: + + state->response.data.auth.nt_status = NT_STATUS_V(result); + push_utf8_fstring(state->response.data.auth.nt_status_string, nt_errstr(result)); + push_utf8_fstring(state->response.data.auth.error_string, nt_errstr(result)); + state->response.data.auth.pam_error = nt_status_to_pam(result); + + DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, + ("NTLM CRAP authentication for user [%s]\\[%s] returned %s (PAM: %d)\n", + domain, + user, + state->response.data.auth.nt_status_string, + state->response.data.auth.pam_error)); + + if (mem_ctx) + talloc_destroy(mem_ctx); + + return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR; +} + +/* Change a user password */ + +enum winbindd_result winbindd_pam_chauthtok(struct winbindd_cli_state *state) +{ + NTSTATUS result; + char *oldpass, *newpass; + fstring domain, user; + CLI_POLICY_HND *hnd; + + DEBUG(3, ("[%5d]: pam chauthtok %s\n", state->pid, + state->request.data.chauthtok.user)); + + /* Setup crap */ + + if (state == NULL) + return WINBINDD_ERROR; + + if (!parse_domain_user(state->request.data.chauthtok.user, domain, + user)) { + result = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + /* Change password */ + + oldpass = state->request.data.chauthtok.oldpass; + newpass = state->request.data.chauthtok.newpass; + + /* Get sam handle */ + + if (!(hnd = cm_get_sam_handle(domain))) { + DEBUG(1, ("could not get SAM handle on DC for %s\n", domain)); + result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + goto done; + } + + if (!cli_oem_change_password(hnd->cli, user, newpass, oldpass)) { + DEBUG(1, ("password change failed for user %s/%s\n", domain, + user)); + result = NT_STATUS_WRONG_PASSWORD; + } else { + result = NT_STATUS_OK; + } + +done: + state->response.data.auth.nt_status = NT_STATUS_V(result); + fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result)); + fstrcpy(state->response.data.auth.error_string, nt_errstr(result)); + state->response.data.auth.pam_error = nt_status_to_pam(result); + + DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, + ("Password change for user [%s]\\[%s] returned %s (PAM: %d)\n", + domain, + user, + state->response.data.auth.nt_status_string, + state->response.data.auth.pam_error)); + + return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR; +} diff --git a/source4/nsswitch/winbindd_rpc.c b/source4/nsswitch/winbindd_rpc.c new file mode 100644 index 0000000000..9989f27109 --- /dev/null +++ b/source4/nsswitch/winbindd_rpc.c @@ -0,0 +1,776 @@ +/* + Unix SMB/CIFS implementation. + + Winbind rpc backend functions + + Copyright (C) Tim Potter 2000-2001,2003 + Copyright (C) Andrew Tridgell 2001 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "winbindd.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + + +/* Query display info for a domain. This returns enough information plus a + bit extra to give an overview of domain users for the User Manager + application. */ +static NTSTATUS query_user_list(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_entries, + WINBIND_USERINFO **info) +{ + CLI_POLICY_HND *hnd; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + POLICY_HND dom_pol; + BOOL got_dom_pol = False; + uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED; + unsigned int i, start_idx, retry; + + DEBUG(3,("rpc: query_user_list\n")); + + *num_entries = 0; + *info = NULL; + + retry = 0; + do { + /* Get sam handle */ + + if (!(hnd = cm_get_sam_handle(domain->name))) + goto done; + + /* Get domain handle */ + + result = cli_samr_open_domain(hnd->cli, mem_ctx, &hnd->pol, + des_access, &domain->sid, &dom_pol); + + } while (!NT_STATUS_IS_OK(result) && (retry++ < 1) && hnd && hnd->cli && hnd->cli->fd == -1); + + if (!NT_STATUS_IS_OK(result)) + goto done; + + got_dom_pol = True; + + i = start_idx = 0; + do { + TALLOC_CTX *ctx2; + char **dom_users; + uint32 num_dom_users, *dom_rids, j, size = 0xffff; + uint16 acb_mask = ACB_NORMAL; + + if (!(ctx2 = talloc_init("winbindd enum_users"))) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + result = cli_samr_enum_dom_users( + hnd->cli, ctx2, &dom_pol, &start_idx, acb_mask, + size, &dom_users, &dom_rids, &num_dom_users); + + *num_entries += num_dom_users; + + *info = talloc_realloc( + mem_ctx, *info, + (*num_entries) * sizeof(WINBIND_USERINFO)); + + if (!(*info)) { + result = NT_STATUS_NO_MEMORY; + talloc_destroy(ctx2); + goto done; + } + + for (j = 0; j < num_dom_users; i++, j++) { + (*info)[i].acct_name = + talloc_strdup(mem_ctx, dom_users[j]); + (*info)[i].full_name = talloc_strdup(mem_ctx, ""); + (*info)[i].user_sid = rid_to_talloced_sid(domain, mem_ctx, dom_rids[j]); + /* For the moment we set the primary group for + every user to be the Domain Users group. + There are serious problems with determining + the actual primary group for large domains. + This should really be made into a 'winbind + force group' smb.conf parameter or + something like that. */ + (*info)[i].group_sid + = rid_to_talloced_sid(domain, + mem_ctx, + DOMAIN_GROUP_RID_USERS); + } + + talloc_destroy(ctx2); + + } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)); + + done: + + if (got_dom_pol) + cli_samr_close(hnd->cli, mem_ctx, &dom_pol); + + return result; +} + +/* list all domain groups */ +static NTSTATUS enum_dom_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_entries, + struct acct_info **info) +{ + uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED; + CLI_POLICY_HND *hnd; + POLICY_HND dom_pol; + NTSTATUS status; + uint32 start = 0; + int retry; + + *num_entries = 0; + *info = NULL; + + DEBUG(3,("rpc: enum_dom_groups\n")); + + retry = 0; + do { + if (!(hnd = cm_get_sam_handle(domain->name))) + return NT_STATUS_UNSUCCESSFUL; + + status = cli_samr_open_domain(hnd->cli, mem_ctx, + &hnd->pol, des_access, &domain->sid, &dom_pol); + } while (!NT_STATUS_IS_OK(status) && (retry++ < 1) && hnd && hnd->cli && hnd->cli->fd == -1); + + if (!NT_STATUS_IS_OK(status)) + return status; + + do { + struct acct_info *info2 = NULL; + uint32 count = 0; + TALLOC_CTX *mem_ctx2; + + mem_ctx2 = talloc_init("enum_dom_groups[rpc]"); + + /* start is updated by this call. */ + status = cli_samr_enum_dom_groups(hnd->cli, mem_ctx2, &dom_pol, + &start, + 0xFFFF, /* buffer size? */ + &info2, &count); + + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) { + talloc_destroy(mem_ctx2); + break; + } + + (*info) = talloc_realloc(mem_ctx, *info, + sizeof(**info) * ((*num_entries) + count)); + if (! *info) { + talloc_destroy(mem_ctx2); + cli_samr_close(hnd->cli, mem_ctx, &dom_pol); + return NT_STATUS_NO_MEMORY; + } + + memcpy(&(*info)[*num_entries], info2, count*sizeof(*info2)); + (*num_entries) += count; + talloc_destroy(mem_ctx2); + } while (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)); + + cli_samr_close(hnd->cli, mem_ctx, &dom_pol); + + return status; +} + +/* List all domain groups */ + +static NTSTATUS enum_local_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_entries, + struct acct_info **info) +{ + uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED; + CLI_POLICY_HND *hnd; + POLICY_HND dom_pol; + NTSTATUS result; + int retry; + + *num_entries = 0; + *info = NULL; + + retry = 0; + do { + if ( !(hnd = cm_get_sam_handle(domain->name)) ) + return NT_STATUS_UNSUCCESSFUL; + + result = cli_samr_open_domain( hnd->cli, mem_ctx, &hnd->pol, + des_access, &domain->sid, &dom_pol); + } while (!NT_STATUS_IS_OK(result) && (retry++ < 1) && hnd && hnd->cli && hnd->cli->fd == -1); + + if ( !NT_STATUS_IS_OK(result)) + return result; + + do { + struct acct_info *info2 = NULL; + uint32 count = 0, start = *num_entries; + TALLOC_CTX *mem_ctx2; + + mem_ctx2 = talloc_init("enum_dom_local_groups[rpc]"); + + result = cli_samr_enum_als_groups( hnd->cli, mem_ctx2, &dom_pol, + &start, 0xFFFF, &info2, &count); + + if ( !NT_STATUS_IS_OK(result) + && !NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES) ) + { + talloc_destroy(mem_ctx2); + break; + } + + (*info) = talloc_realloc(mem_ctx, *info, + sizeof(**info) * ((*num_entries) + count)); + if (! *info) { + talloc_destroy(mem_ctx2); + cli_samr_close(hnd->cli, mem_ctx, &dom_pol); + return NT_STATUS_NO_MEMORY; + } + + memcpy(&(*info)[*num_entries], info2, count*sizeof(*info2)); + (*num_entries) += count; + talloc_destroy(mem_ctx2); + } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)); + + cli_samr_close(hnd->cli, mem_ctx, &dom_pol); + + return result; +} + +/* convert a single name to a sid in a domain */ +static NTSTATUS name_to_sid(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *name, + DOM_SID *sid, + enum SID_NAME_USE *type) +{ + CLI_POLICY_HND *hnd; + NTSTATUS status; + DOM_SID *sids = NULL; + uint32 *types = NULL; + const char *full_name; + int retry; + + DEBUG(3,("rpc: name_to_sid name=%s\n", name)); + + full_name = talloc_asprintf(mem_ctx, "%s\\%s", domain->name, name); + + if (!full_name) { + DEBUG(0, ("talloc_asprintf failed!\n")); + return NT_STATUS_NO_MEMORY; + } + + retry = 0; + do { + if (!(hnd = cm_get_lsa_handle(domain->name))) { + return NT_STATUS_UNSUCCESSFUL; + } + + status = cli_lsa_lookup_names(hnd->cli, mem_ctx, &hnd->pol, 1, + &full_name, &sids, &types); + } while (!NT_STATUS_IS_OK(status) && (retry++ < 1) && hnd && hnd->cli && hnd->cli->fd == -1); + + /* Return rid and type if lookup successful */ + + if (NT_STATUS_IS_OK(status)) { + sid_copy(sid, &sids[0]); + *type = types[0]; + } + + return status; +} + +/* + convert a domain SID to a user or group name +*/ +static NTSTATUS sid_to_name(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *sid, + char **name, + enum SID_NAME_USE *type) +{ + CLI_POLICY_HND *hnd; + char **domains; + char **names; + uint32 *types; + NTSTATUS status; + int retry; + + DEBUG(3,("rpc: sid_to_name\n")); + + retry = 0; + do { + if (!(hnd = cm_get_lsa_handle(domain->name))) + return NT_STATUS_UNSUCCESSFUL; + + status = cli_lsa_lookup_sids(hnd->cli, mem_ctx, &hnd->pol, + 1, sid, &domains, &names, &types); + } while (!NT_STATUS_IS_OK(status) && (retry++ < 1) && hnd && hnd->cli && hnd->cli->fd == -1); + + if (NT_STATUS_IS_OK(status)) { + *type = types[0]; + *name = names[0]; + DEBUG(5,("Mapped sid to [%s]\\[%s]\n", domains[0], *name)); + + /* Paranoia */ + if (strcasecmp(domain->name, domains[0]) != 0) { + DEBUG(1, ("domain name from domain param and PDC lookup return differ! (%s vs %s)\n", domain->name, domains[0])); + return NT_STATUS_UNSUCCESSFUL; + } + } + return status; +} + +/* Lookup user information from a rid or username. */ +static NTSTATUS query_user(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *user_sid, + WINBIND_USERINFO *user_info) +{ + CLI_POLICY_HND *hnd; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + POLICY_HND dom_pol, user_pol; + BOOL got_dom_pol = False, got_user_pol = False; + SAM_USERINFO_CTR *ctr; + int retry; + fstring sid_string; + uint32 user_rid; + + DEBUG(3,("rpc: query_user rid=%s\n", sid_to_string(sid_string, user_sid))); + if (!sid_peek_check_rid(&domain->sid, user_sid, &user_rid)) { + goto done; + } + + retry = 0; + do { + /* Get sam handle */ + if (!(hnd = cm_get_sam_handle(domain->name))) + goto done; + + /* Get domain handle */ + + result = cli_samr_open_domain(hnd->cli, mem_ctx, &hnd->pol, + SEC_RIGHTS_MAXIMUM_ALLOWED, + &domain->sid, &dom_pol); + } while (!NT_STATUS_IS_OK(result) && (retry++ < 1) && hnd && hnd->cli && hnd->cli->fd == -1); + + if (!NT_STATUS_IS_OK(result)) + goto done; + + got_dom_pol = True; + + /* Get user handle */ + result = cli_samr_open_user(hnd->cli, mem_ctx, &dom_pol, + SEC_RIGHTS_MAXIMUM_ALLOWED, user_rid, &user_pol); + + if (!NT_STATUS_IS_OK(result)) + goto done; + + got_user_pol = True; + + /* Get user info */ + result = cli_samr_query_userinfo(hnd->cli, mem_ctx, &user_pol, + 0x15, &ctr); + + if (!NT_STATUS_IS_OK(result)) + goto done; + + cli_samr_close(hnd->cli, mem_ctx, &user_pol); + got_user_pol = False; + + user_info->user_sid = rid_to_talloced_sid(domain, mem_ctx, user_rid); + user_info->group_sid = rid_to_talloced_sid(domain, mem_ctx, ctr->info.id21->group_rid); + user_info->acct_name = unistr2_tdup(mem_ctx, + &ctr->info.id21->uni_user_name); + user_info->full_name = unistr2_tdup(mem_ctx, + &ctr->info.id21->uni_full_name); + + done: + /* Clean up policy handles */ + if (got_user_pol) + cli_samr_close(hnd->cli, mem_ctx, &user_pol); + + if (got_dom_pol) + cli_samr_close(hnd->cli, mem_ctx, &dom_pol); + + return result; +} + +/* Lookup groups a user is a member of. I wish Unix had a call like this! */ +static NTSTATUS lookup_usergroups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *user_sid, + uint32 *num_groups, DOM_SID ***user_gids) +{ + CLI_POLICY_HND *hnd; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + POLICY_HND dom_pol, user_pol; + uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED; + BOOL got_dom_pol = False, got_user_pol = False; + DOM_GID *user_groups; + unsigned int i; + unsigned int retry; + fstring sid_string; + uint32 user_rid; + + DEBUG(3,("rpc: lookup_usergroups sid=%s\n", sid_to_string(sid_string, user_sid))); + + *num_groups = 0; + + /* First try cached universal groups from logon */ + *user_gids = uni_group_cache_fetch(&domain->sid, user_sid, mem_ctx, num_groups); + if((*num_groups > 0) && *user_gids) { + return NT_STATUS_OK; + } else { + *user_gids = NULL; + *num_groups = 0; + } + + retry = 0; + do { + /* Get sam handle */ + if (!(hnd = cm_get_sam_handle(domain->name))) + goto done; + + /* Get domain handle */ + result = cli_samr_open_domain(hnd->cli, mem_ctx, &hnd->pol, + des_access, &domain->sid, &dom_pol); + } while (!NT_STATUS_IS_OK(result) && (retry++ < 1) && hnd && hnd->cli && hnd->cli->fd == -1); + + if (!NT_STATUS_IS_OK(result)) + goto done; + + got_dom_pol = True; + + + if (!sid_peek_check_rid(&domain->sid, user_sid, &user_rid)) { + goto done; + } + + /* Get user handle */ + result = cli_samr_open_user(hnd->cli, mem_ctx, &dom_pol, + des_access, user_rid, &user_pol); + + if (!NT_STATUS_IS_OK(result)) + goto done; + + got_user_pol = True; + + /* Query user rids */ + result = cli_samr_query_usergroups(hnd->cli, mem_ctx, &user_pol, + num_groups, &user_groups); + + if (!NT_STATUS_IS_OK(result) || (*num_groups) == 0) + goto done; + + (*user_gids) = talloc(mem_ctx, sizeof(uint32) * (*num_groups)); + if (!(*user_gids)) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + for (i=0;i<(*num_groups);i++) { + (*user_gids)[i] = rid_to_talloced_sid(domain, mem_ctx, user_groups[i].g_rid); + } + + done: + /* Clean up policy handles */ + if (got_user_pol) + cli_samr_close(hnd->cli, mem_ctx, &user_pol); + + if (got_dom_pol) + cli_samr_close(hnd->cli, mem_ctx, &dom_pol); + + return result; +} + + +/* Lookup group membership given a rid. */ +static NTSTATUS lookup_groupmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *group_sid, uint32 *num_names, + DOM_SID ***sid_mem, char ***names, + uint32 **name_types) +{ + CLI_POLICY_HND *hnd; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + uint32 i, total_names = 0; + POLICY_HND dom_pol, group_pol; + uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED; + BOOL got_dom_pol = False, got_group_pol = False; + uint32 *rid_mem = NULL; + uint32 group_rid; + int retry; + unsigned int j; + fstring sid_string; + + DEBUG(10,("rpc: lookup_groupmem %s sid=%s\n", domain->name, sid_to_string(sid_string, group_sid))); + + if (!sid_peek_check_rid(&domain->sid, group_sid, &group_rid)) { + goto done; + } + + *num_names = 0; + + retry = 0; + do { + /* Get sam handle */ + if (!(hnd = cm_get_sam_handle(domain->name))) + goto done; + + /* Get domain handle */ + + result = cli_samr_open_domain(hnd->cli, mem_ctx, &hnd->pol, + des_access, &domain->sid, &dom_pol); + } while (!NT_STATUS_IS_OK(result) && (retry++ < 1) && hnd && hnd->cli && hnd->cli->fd == -1); + + if (!NT_STATUS_IS_OK(result)) + goto done; + + got_dom_pol = True; + + /* Get group handle */ + + result = cli_samr_open_group(hnd->cli, mem_ctx, &dom_pol, + des_access, group_rid, &group_pol); + + if (!NT_STATUS_IS_OK(result)) + goto done; + + got_group_pol = True; + + /* Step #1: Get a list of user rids that are the members of the + group. */ + + result = cli_samr_query_groupmem(hnd->cli, mem_ctx, + &group_pol, num_names, &rid_mem, + name_types); + + if (!NT_STATUS_IS_OK(result)) + goto done; + + /* Step #2: Convert list of rids into list of usernames. Do this + in bunches of ~1000 to avoid crashing NT4. It looks like there + is a buffer overflow or something like that lurking around + somewhere. */ + +#define MAX_LOOKUP_RIDS 900 + + *names = talloc_zero(mem_ctx, *num_names * sizeof(char *)); + *name_types = talloc_zero(mem_ctx, *num_names * sizeof(uint32)); + *sid_mem = talloc_zero(mem_ctx, *num_names * sizeof(DOM_SID *)); + + for (j=0;j<(*num_names);j++) { + (*sid_mem)[j] = rid_to_talloced_sid(domain, mem_ctx, (rid_mem)[j]); + } + + if (!*names || !*name_types) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + for (i = 0; i < *num_names; i += MAX_LOOKUP_RIDS) { + int num_lookup_rids = MIN(*num_names - i, MAX_LOOKUP_RIDS); + uint32 tmp_num_names = 0; + char **tmp_names = NULL; + uint32 *tmp_types = NULL; + + /* Lookup a chunk of rids */ + + result = cli_samr_lookup_rids(hnd->cli, mem_ctx, + &dom_pol, 1000, /* flags */ + num_lookup_rids, + &rid_mem[i], + &tmp_num_names, + &tmp_names, &tmp_types); + + if (!NT_STATUS_IS_OK(result)) + goto done; + + /* Copy result into array. The talloc system will take + care of freeing the temporary arrays later on. */ + + memcpy(&(*names)[i], tmp_names, sizeof(char *) * + tmp_num_names); + + memcpy(&(*name_types)[i], tmp_types, sizeof(uint32) * + tmp_num_names); + + total_names += tmp_num_names; + } + + *num_names = total_names; + + done: + if (got_group_pol) + cli_samr_close(hnd->cli, mem_ctx, &group_pol); + + if (got_dom_pol) + cli_samr_close(hnd->cli, mem_ctx, &dom_pol); + + return result; +} + +/* find the sequence number for a domain */ +static NTSTATUS sequence_number(struct winbindd_domain *domain, uint32 *seq) +{ + TALLOC_CTX *mem_ctx; + CLI_POLICY_HND *hnd; + SAM_UNK_CTR ctr; + uint16 switch_value = 2; + NTSTATUS result; + uint32 seqnum = DOM_SEQUENCE_NONE; + POLICY_HND dom_pol; + BOOL got_dom_pol = False; + uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED; + int retry; + + DEBUG(10,("rpc: fetch sequence_number for %s\n", domain->name)); + + *seq = DOM_SEQUENCE_NONE; + + if (!(mem_ctx = talloc_init("sequence_number[rpc]"))) + return NT_STATUS_NO_MEMORY; + + retry = 0; + do { + /* Get sam handle */ + if (!(hnd = cm_get_sam_handle(domain->name))) + goto done; + + /* Get domain handle */ + result = cli_samr_open_domain(hnd->cli, mem_ctx, &hnd->pol, + des_access, &domain->sid, &dom_pol); + } while (!NT_STATUS_IS_OK(result) && (retry++ < 1) && hnd && hnd->cli && hnd->cli->fd == -1); + + if (!NT_STATUS_IS_OK(result)) + goto done; + + got_dom_pol = True; + + /* Query domain info */ + + result = cli_samr_query_dom_info(hnd->cli, mem_ctx, &dom_pol, + switch_value, &ctr); + + if (NT_STATUS_IS_OK(result)) { + seqnum = ctr.info.inf2.seq_num; + DEBUG(10,("domain_sequence_number: for domain %s is %u\n", domain->name, (unsigned)seqnum )); + } else { + DEBUG(10,("domain_sequence_number: failed to get sequence number (%u) for domain %s\n", + (unsigned)seqnum, domain->name )); + } + + done: + + if (got_dom_pol) + cli_samr_close(hnd->cli, mem_ctx, &dom_pol); + + talloc_destroy(mem_ctx); + + *seq = seqnum; + + return result; +} + +/* get a list of trusted domains */ +static NTSTATUS trusted_domains(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 *num_domains, + char ***names, + char ***alt_names, + DOM_SID **dom_sids) +{ + CLI_POLICY_HND *hnd; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + uint32 enum_ctx = 0; + int retry; + + DEBUG(3,("rpc: trusted_domains\n")); + + *num_domains = 0; + *alt_names = NULL; + + retry = 0; + do { + if (!(hnd = cm_get_lsa_handle(lp_workgroup()))) + goto done; + + result = cli_lsa_enum_trust_dom(hnd->cli, mem_ctx, + &hnd->pol, &enum_ctx, + num_domains, names, dom_sids); + } while (!NT_STATUS_IS_OK(result) && (retry++ < 1) && hnd && hnd->cli && hnd->cli->fd == -1); + +done: + return result; +} + +/* find the domain sid for a domain */ +static NTSTATUS domain_sid(struct winbindd_domain *domain, DOM_SID *sid) +{ + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + TALLOC_CTX *mem_ctx; + CLI_POLICY_HND *hnd; + fstring level5_dom; + int retry; + + DEBUG(3,("rpc: domain_sid\n")); + + if (!(mem_ctx = talloc_init("domain_sid[rpc]"))) + return NT_STATUS_NO_MEMORY; + + retry = 0; + do { + /* Get sam handle */ + if (!(hnd = cm_get_lsa_handle(domain->name))) + goto done; + + status = cli_lsa_query_info_policy(hnd->cli, mem_ctx, + &hnd->pol, 0x05, level5_dom, sid); + } while (!NT_STATUS_IS_OK(status) && (retry++ < 1) && hnd && hnd->cli && hnd->cli->fd == -1); + +done: + talloc_destroy(mem_ctx); + return status; +} + +/* find alternate names list for the domain - none for rpc */ +static NTSTATUS alternate_name(struct winbindd_domain *domain) +{ + return NT_STATUS_OK; +} + + +/* the rpc backend methods are exposed via this structure */ +struct winbindd_methods msrpc_methods = { + False, + query_user_list, + enum_dom_groups, + enum_local_groups, + name_to_sid, + sid_to_name, + query_user, + lookup_usergroups, + lookup_groupmem, + sequence_number, + trusted_domains, + domain_sid, + alternate_name +}; diff --git a/source4/nsswitch/winbindd_sid.c b/source4/nsswitch/winbindd_sid.c new file mode 100644 index 0000000000..6ab2eaa646 --- /dev/null +++ b/source4/nsswitch/winbindd_sid.c @@ -0,0 +1,235 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - sid related functions + + Copyright (C) Tim Potter 2000 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "winbindd.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/* Convert a string */ + +enum winbindd_result winbindd_lookupsid(struct winbindd_cli_state *state) +{ + extern DOM_SID global_sid_Builtin; + enum SID_NAME_USE type; + DOM_SID sid, tmp_sid; + uint32 rid; + fstring name; + fstring dom_name; + + /* Ensure null termination */ + state->request.data.sid[sizeof(state->request.data.sid)-1]='\0'; + + DEBUG(3, ("[%5d]: lookupsid %s\n", state->pid, + state->request.data.sid)); + + /* Lookup sid from PDC using lsa_lookup_sids() */ + + if (!string_to_sid(&sid, state->request.data.sid)) { + DEBUG(5, ("%s not a SID\n", state->request.data.sid)); + return WINBINDD_ERROR; + } + + /* Don't look up BUILTIN sids */ + + sid_copy(&tmp_sid, &sid); + sid_split_rid(&tmp_sid, &rid); + + if (sid_equal(&tmp_sid, &global_sid_Builtin)) { + return WINBINDD_ERROR; + } + + /* Lookup the sid */ + + if (!winbindd_lookup_name_by_sid(&sid, dom_name, name, &type)) { + return WINBINDD_ERROR; + } + + fstrcpy(state->response.data.name.dom_name, dom_name); + fstrcpy(state->response.data.name.name, name); + + state->response.data.name.type = type; + + return WINBINDD_OK; +} + + +/** + * Look up the SID for a qualified name. + **/ +enum winbindd_result winbindd_lookupname(struct winbindd_cli_state *state) +{ + enum SID_NAME_USE type; + fstring sid_str; + char *name_domain, *name_user; + DOM_SID sid; + struct winbindd_domain *domain; + + /* Ensure null termination */ + state->request.data.sid[sizeof(state->request.data.name.dom_name)-1]='\0'; + + /* Ensure null termination */ + state->request.data.sid[sizeof(state->request.data.name.name)-1]='\0'; + + DEBUG(3, ("[%5d]: lookupname %s%s%s\n", state->pid, + state->request.data.name.dom_name, + lp_winbind_separator(), + state->request.data.name.name)); + + name_domain = state->request.data.name.dom_name; + name_user = state->request.data.name.name; + + if ((domain = find_domain_from_name(name_domain)) == NULL) { + DEBUG(0, ("could not find domain entry for domain %s\n", + name_domain)); + return WINBINDD_ERROR; + } + + /* Lookup name from PDC using lsa_lookup_names() */ + if (!winbindd_lookup_sid_by_name(domain, name_user, &sid, &type)) { + return WINBINDD_ERROR; + } + + sid_to_string(sid_str, &sid); + fstrcpy(state->response.data.sid.sid, sid_str); + state->response.data.sid.type = type; + + return WINBINDD_OK; +} + +/* Convert a sid to a uid. We assume we only have one rid attached to the + sid. */ + +enum winbindd_result winbindd_sid_to_uid(struct winbindd_cli_state *state) +{ + DOM_SID sid; + + /* Ensure null termination */ + state->request.data.sid[sizeof(state->request.data.sid)-1]='\0'; + + DEBUG(3, ("[%5d]: sid to uid %s\n", state->pid, + state->request.data.sid)); + + /* Split sid into domain sid and user rid */ + if (!string_to_sid(&sid, state->request.data.sid)) { + DEBUG(1, ("Could not get convert sid %s from string\n", + state->request.data.sid)); + return WINBINDD_ERROR; + } + + /* Find uid for this sid and return it */ + if (!winbindd_idmap_get_uid_from_sid(&sid, &state->response.data.uid)) { + DEBUG(1, ("Could not get uid for sid %s\n", + state->request.data.sid)); + return WINBINDD_ERROR; + } + + return WINBINDD_OK; +} + +/* Convert a sid to a gid. We assume we only have one rid attached to the + sid.*/ + +enum winbindd_result winbindd_sid_to_gid(struct winbindd_cli_state *state) +{ + DOM_SID sid; + + /* Ensure null termination */ + state->request.data.sid[sizeof(state->request.data.sid)-1]='\0'; + + DEBUG(3, ("[%5d]: sid to gid %s\n", state->pid, + state->request.data.sid)); + + if (!string_to_sid(&sid, state->request.data.sid)) { + DEBUG(1, ("Could not cvt string to sid %s\n", + state->request.data.sid)); + return WINBINDD_ERROR; + } + + /* Find gid for this sid and return it */ + if (!winbindd_idmap_get_gid_from_sid(&sid, &state->response.data.gid)) { + DEBUG(1, ("Could not get gid for sid %s\n", + state->request.data.sid)); + return WINBINDD_ERROR; + } + + return WINBINDD_OK; +} + +/* Convert a uid to a sid */ + +enum winbindd_result winbindd_uid_to_sid(struct winbindd_cli_state *state) +{ + DOM_SID sid; + + /* Bug out if the uid isn't in the winbind range */ + + if ((state->request.data.uid < server_state.uid_low ) || + (state->request.data.uid > server_state.uid_high)) { + return WINBINDD_ERROR; + } + + DEBUG(3, ("[%5d]: uid to sid %d\n", state->pid, + state->request.data.uid)); + + /* Lookup rid for this uid */ + if (!winbindd_idmap_get_sid_from_uid(state->request.data.uid, &sid)) { + DEBUG(1, ("Could not convert uid %d to rid\n", + state->request.data.uid)); + return WINBINDD_ERROR; + } + + sid_to_string(state->response.data.sid.sid, &sid); + state->response.data.sid.type = SID_NAME_USER; + + return WINBINDD_OK; +} + +/* Convert a gid to a sid */ + +enum winbindd_result winbindd_gid_to_sid(struct winbindd_cli_state *state) +{ + DOM_SID sid; + + /* Bug out if the gid isn't in the winbind range */ + + if ((state->request.data.gid < server_state.gid_low) || + (state->request.data.gid > server_state.gid_high)) { + return WINBINDD_ERROR; + } + + DEBUG(3, ("[%5d]: gid to sid %d\n", state->pid, + state->request.data.gid)); + + /* Lookup sid for this uid */ + if (!winbindd_idmap_get_sid_from_gid(state->request.data.gid, &sid)) { + DEBUG(1, ("Could not convert gid %d to sid\n", + state->request.data.gid)); + return WINBINDD_ERROR; + } + + /* Construct sid and return it */ + sid_to_string(state->response.data.sid.sid, &sid); + state->response.data.sid.type = SID_NAME_DOM_GRP; + + return WINBINDD_OK; +} diff --git a/source4/nsswitch/winbindd_user.c b/source4/nsswitch/winbindd_user.c new file mode 100644 index 0000000000..ee05543d30 --- /dev/null +++ b/source4/nsswitch/winbindd_user.c @@ -0,0 +1,607 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - user related functions + + Copyright (C) Tim Potter 2000 + Copyright (C) Jeremy Allison 2001. + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "winbindd.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/* Fill a pwent structure with information we have obtained */ + +static BOOL winbindd_fill_pwent(char *dom_name, char *user_name, + DOM_SID *user_sid, DOM_SID *group_sid, + char *full_name, struct winbindd_pw *pw) +{ + extern userdom_struct current_user_info; + fstring output_username; + pstring homedir; + fstring sid_string; + + if (!pw || !dom_name || !user_name) + return False; + + /* Resolve the uid number */ + + if (!winbindd_idmap_get_uid_from_sid(user_sid, + &pw->pw_uid)) { + DEBUG(1, ("error getting user id for sid %s\n", sid_to_string(sid_string, user_sid))); + return False; + } + + /* Resolve the gid number */ + + if (!winbindd_idmap_get_gid_from_sid(group_sid, + &pw->pw_gid)) { + DEBUG(1, ("error getting group id for sid %s\n", sid_to_string(sid_string, group_sid))); + return False; + } + + /* Username */ + + fill_domain_username(output_username, dom_name, user_name); + + safe_strcpy(pw->pw_name, output_username, sizeof(pw->pw_name) - 1); + + /* Full name (gecos) */ + + safe_strcpy(pw->pw_gecos, full_name, sizeof(pw->pw_gecos) - 1); + + /* Home directory and shell - use template config parameters. The + defaults are /tmp for the home directory and /bin/false for + shell. */ + + /* The substitution of %U and %D in the 'template homedir' is done + by lp_string() calling standard_sub_basic(). */ + + fstrcpy(current_user_info.smb_name, user_name); + sub_set_smb_name(user_name); + fstrcpy(current_user_info.domain, dom_name); + + pstrcpy(homedir, lp_template_homedir()); + + safe_strcpy(pw->pw_dir, homedir, sizeof(pw->pw_dir) - 1); + + safe_strcpy(pw->pw_shell, lp_template_shell(), + sizeof(pw->pw_shell) - 1); + + /* Password - set to "x" as we can't generate anything useful here. + Authentication can be done using the pam_winbind module. */ + + safe_strcpy(pw->pw_passwd, "x", sizeof(pw->pw_passwd) - 1); + + return True; +} + +/* Return a password structure from a username. */ + +enum winbindd_result winbindd_getpwnam(struct winbindd_cli_state *state) +{ + WINBIND_USERINFO user_info; + DOM_SID user_sid; + NTSTATUS status; + fstring name_domain, name_user; + enum SID_NAME_USE name_type; + struct winbindd_domain *domain; + TALLOC_CTX *mem_ctx; + + /* Ensure null termination */ + state->request.data.username[sizeof(state->request.data.username)-1]='\0'; + + DEBUG(3, ("[%5d]: getpwnam %s\n", state->pid, + state->request.data.username)); + + /* Parse domain and username */ + + if (!parse_domain_user(state->request.data.username, name_domain, + name_user)) + return WINBINDD_ERROR; + + if ((domain = find_domain_from_name(name_domain)) == NULL) { + DEBUG(5, ("no such domain: %s\n", name_domain)); + return WINBINDD_ERROR; + } + + /* Get rid and name type from name */ + + if (!winbindd_lookup_sid_by_name(domain, name_user, &user_sid, &name_type)) { + DEBUG(1, ("user '%s' does not exist\n", name_user)); + return WINBINDD_ERROR; + } + + if (name_type != SID_NAME_USER) { + DEBUG(1, ("name '%s' is not a user name: %d\n", name_user, + name_type)); + return WINBINDD_ERROR; + } + + /* Get some user info. Split the user rid from the sid obtained + from the winbind_lookup_by_name() call and use it in a + winbind_lookup_userinfo() */ + + if (!(mem_ctx = talloc_init("winbindd_getpwnam([%s]\\[%s])", + name_domain, name_user))) { + DEBUG(1, ("out of memory\n")); + return WINBINDD_ERROR; + } + + status = domain->methods->query_user(domain, mem_ctx, &user_sid, + &user_info); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("error getting user info for user '[%s]\\[%s]'\n", + name_domain, name_user)); + talloc_destroy(mem_ctx); + return WINBINDD_ERROR; + } + + /* Now take all this information and fill in a passwd structure */ + if (!winbindd_fill_pwent(name_domain, name_user, + user_info.user_sid, user_info.group_sid, + user_info.full_name, + &state->response.data.pw)) { + talloc_destroy(mem_ctx); + return WINBINDD_ERROR; + } + + talloc_destroy(mem_ctx); + + return WINBINDD_OK; +} + +/* Return a password structure given a uid number */ + +enum winbindd_result winbindd_getpwuid(struct winbindd_cli_state *state) +{ + DOM_SID user_sid; + struct winbindd_domain *domain; + fstring dom_name; + fstring user_name; + enum SID_NAME_USE name_type; + WINBIND_USERINFO user_info; + gid_t gid; + TALLOC_CTX *mem_ctx; + NTSTATUS status; + + /* Bug out if the uid isn't in the winbind range */ + + if ((state->request.data.uid < server_state.uid_low ) || + (state->request.data.uid > server_state.uid_high)) + return WINBINDD_ERROR; + + DEBUG(3, ("[%5d]: getpwuid %d\n", state->pid, + state->request.data.uid)); + + /* Get rid from uid */ + + if (!winbindd_idmap_get_sid_from_uid(state->request.data.uid, + &user_sid)) { + DEBUG(1, ("could not convert uid %d to SID\n", + state->request.data.uid)); + return WINBINDD_ERROR; + } + + /* Get name and name type from rid */ + + if (!winbindd_lookup_name_by_sid(&user_sid, dom_name, user_name, &name_type)) { + fstring temp; + + sid_to_string(temp, &user_sid); + DEBUG(1, ("could not lookup sid %s\n", temp)); + return WINBINDD_ERROR; + } + + domain = find_domain_from_sid(&user_sid); + + if (!domain) { + DEBUG(1,("Can't find domain from sid\n")); + return WINBINDD_ERROR; + } + + /* Get some user info */ + + if (!(mem_ctx = talloc_init("winbind_getpwuid(%d)", + state->request.data.uid))) { + + DEBUG(1, ("out of memory\n")); + return WINBINDD_ERROR; + } + + status = domain->methods->query_user(domain, mem_ctx, &user_sid, + &user_info); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("error getting user info for user '%s'\n", + user_name)); + talloc_destroy(mem_ctx); + return WINBINDD_ERROR; + } + + /* Resolve gid number */ + + if (!winbindd_idmap_get_gid_from_sid(user_info.group_sid, &gid)) { + DEBUG(1, ("error getting group id for user %s\n", user_name)); + talloc_destroy(mem_ctx); + return WINBINDD_ERROR; + } + + /* Fill in password structure */ + + if (!winbindd_fill_pwent(domain->name, user_name, user_info.user_sid, + user_info.group_sid, + user_info.full_name, &state->response.data.pw)) { + talloc_destroy(mem_ctx); + return WINBINDD_ERROR; + } + + talloc_destroy(mem_ctx); + + return WINBINDD_OK; +} + +/* + * set/get/endpwent functions + */ + +/* Rewind file pointer for ntdom passwd database */ + +enum winbindd_result winbindd_setpwent(struct winbindd_cli_state *state) +{ + struct winbindd_domain *domain; + + DEBUG(3, ("[%5d]: setpwent\n", state->pid)); + + /* Check user has enabled this */ + + if (!lp_winbind_enum_users()) + return WINBINDD_ERROR; + + /* Free old static data if it exists */ + + if (state->getpwent_state != NULL) { + free_getent_state(state->getpwent_state); + state->getpwent_state = NULL; + } + + /* Create sam pipes for each domain we know about */ + + for(domain = domain_list(); domain != NULL; domain = domain->next) { + struct getent_state *domain_state; + + /* Create a state record for this domain */ + + if ((domain_state = (struct getent_state *) + malloc(sizeof(struct getent_state))) == NULL) + return WINBINDD_ERROR; + + ZERO_STRUCTP(domain_state); + + fstrcpy(domain_state->domain_name, domain->name); + + /* Add to list of open domains */ + + DLIST_ADD(state->getpwent_state, domain_state); + } + + return WINBINDD_OK; +} + +/* Close file pointer to ntdom passwd database */ + +enum winbindd_result winbindd_endpwent(struct winbindd_cli_state *state) +{ + DEBUG(3, ("[%5d]: endpwent\n", state->pid)); + + free_getent_state(state->getpwent_state); + state->getpwent_state = NULL; + + return WINBINDD_OK; +} + +/* Get partial list of domain users for a domain. We fill in the sam_entries, + and num_sam_entries fields with domain user information. The dispinfo_ndx + field is incremented to the index of the next user to fetch. Return True if + some users were returned, False otherwise. */ + +#define MAX_FETCH_SAM_ENTRIES 100 + +static BOOL get_sam_user_entries(struct getent_state *ent) +{ + NTSTATUS status; + uint32 num_entries; + WINBIND_USERINFO *info; + struct getpwent_user *name_list = NULL; + BOOL result = False; + TALLOC_CTX *mem_ctx; + struct winbindd_domain *domain; + struct winbindd_methods *methods; + unsigned int i; + + if (ent->num_sam_entries) + return False; + + if (!(mem_ctx = talloc_init("get_sam_user_entries(%s)", + ent->domain_name))) + return False; + + if (!(domain = find_domain_from_name(ent->domain_name))) { + DEBUG(3, ("no such domain %s in get_sam_user_entries\n", + ent->domain_name)); + return False; + } + + methods = domain->methods; + + /* Free any existing user info */ + + SAFE_FREE(ent->sam_entries); + ent->num_sam_entries = 0; + + /* Call query_user_list to get a list of usernames and user rids */ + + num_entries = 0; + + status = methods->query_user_list(domain, mem_ctx, &num_entries, + &info); + + if (num_entries) { + struct getpwent_user *tnl; + + tnl = (struct getpwent_user *)Realloc(name_list, + sizeof(struct getpwent_user) * + (ent->num_sam_entries + + num_entries)); + + if (!tnl) { + DEBUG(0,("get_sam_user_entries realloc failed.\n")); + SAFE_FREE(name_list); + goto done; + } else + name_list = tnl; + } + + for (i = 0; i < num_entries; i++) { + /* Store account name and gecos */ + if (!info[i].acct_name) { + fstrcpy(name_list[ent->num_sam_entries + i].name, ""); + } else { + fstrcpy(name_list[ent->num_sam_entries + i].name, + info[i].acct_name); + } + if (!info[i].full_name) { + fstrcpy(name_list[ent->num_sam_entries + i].gecos, ""); + } else { + fstrcpy(name_list[ent->num_sam_entries + i].gecos, + info[i].full_name); + } + + /* User and group ids */ + sid_copy(&name_list[ent->num_sam_entries+i].user_sid, info[i].user_sid); + sid_copy(&name_list[ent->num_sam_entries+i].group_sid, info[i].group_sid); + } + + ent->num_sam_entries += num_entries; + + /* Fill in remaining fields */ + + ent->sam_entries = name_list; + ent->sam_entry_index = 0; + result = ent->num_sam_entries > 0; + + done: + + talloc_destroy(mem_ctx); + + return result; +} + +/* Fetch next passwd entry from ntdom database */ + +#define MAX_GETPWENT_USERS 500 + +enum winbindd_result winbindd_getpwent(struct winbindd_cli_state *state) +{ + struct getent_state *ent; + struct winbindd_pw *user_list; + int num_users, user_list_ndx = 0, i; + + DEBUG(3, ("[%5d]: getpwent\n", state->pid)); + + /* Check user has enabled this */ + + if (!lp_winbind_enum_users()) + return WINBINDD_ERROR; + + /* Allocate space for returning a chunk of users */ + + num_users = MIN(MAX_GETPWENT_USERS, state->request.data.num_entries); + + if ((state->response.extra_data = + malloc(num_users * sizeof(struct winbindd_pw))) == NULL) + return WINBINDD_ERROR; + + memset(state->response.extra_data, 0, num_users * + sizeof(struct winbindd_pw)); + + user_list = (struct winbindd_pw *)state->response.extra_data; + + if (!(ent = state->getpwent_state)) + return WINBINDD_ERROR; + + /* Start sending back users */ + + for (i = 0; i < num_users; i++) { + struct getpwent_user *name_list = NULL; + fstring domain_user_name; + uint32 result; + + /* Do we need to fetch another chunk of users? */ + + if (ent->num_sam_entries == ent->sam_entry_index) { + + while(ent && !get_sam_user_entries(ent)) { + struct getent_state *next_ent; + + /* Free state information for this domain */ + + SAFE_FREE(ent->sam_entries); + + next_ent = ent->next; + DLIST_REMOVE(state->getpwent_state, ent); + + SAFE_FREE(ent); + ent = next_ent; + } + + /* No more domains */ + + if (!ent) + break; + } + + name_list = ent->sam_entries; + + /* Skip machine accounts */ + + if (name_list[ent->sam_entry_index]. + name[strlen(name_list[ent->sam_entry_index].name) - 1] + == '$') { + ent->sam_entry_index++; + continue; + } + + /* Lookup user info */ + + result = winbindd_fill_pwent( + ent->domain_name, + name_list[ent->sam_entry_index].name, + &name_list[ent->sam_entry_index].user_sid, + &name_list[ent->sam_entry_index].group_sid, + name_list[ent->sam_entry_index].gecos, + &user_list[user_list_ndx]); + + ent->sam_entry_index++; + + /* Add user to return list */ + + if (result) { + + user_list_ndx++; + state->response.data.num_entries++; + state->response.length += + sizeof(struct winbindd_pw); + + } else + DEBUG(1, ("could not lookup domain user %s\n", + domain_user_name)); + } + + /* Out of domains */ + + return (user_list_ndx > 0) ? WINBINDD_OK : WINBINDD_ERROR; +} + +/* List domain users without mapping to unix ids */ + +enum winbindd_result winbindd_list_users(struct winbindd_cli_state *state) +{ + struct winbindd_domain *domain; + WINBIND_USERINFO *info; + uint32 num_entries = 0, total_entries = 0; + char *ted, *extra_data = NULL; + int extra_data_len = 0; + TALLOC_CTX *mem_ctx; + enum winbindd_result rv = WINBINDD_ERROR; + + DEBUG(3, ("[%5d]: list users\n", state->pid)); + + if (!(mem_ctx = talloc_init("winbindd_list_users"))) + return WINBINDD_ERROR; + + /* Enumerate over trusted domains */ + + for (domain = domain_list(); domain; domain = domain->next) { + NTSTATUS status; + struct winbindd_methods *methods; + unsigned int i; + + methods = domain->methods; + + /* Query display info */ + status = methods->query_user_list(domain, mem_ctx, + &num_entries, &info); + + if (num_entries == 0) + continue; + + /* Allocate some memory for extra data */ + total_entries += num_entries; + + ted = Realloc(extra_data, sizeof(fstring) * total_entries); + + if (!ted) { + DEBUG(0,("failed to enlarge buffer!\n")); + SAFE_FREE(extra_data); + goto done; + } else + extra_data = ted; + + /* Pack user list into extra data fields */ + + for (i = 0; i < num_entries; i++) { + fstring acct_name, name; + + if (!info[i].acct_name) { + fstrcpy(acct_name, ""); + } else { + fstrcpy(acct_name, info[i].acct_name); + } + + fill_domain_username(name, domain->name, acct_name); + + /* Append to extra data */ + memcpy(&extra_data[extra_data_len], name, + strlen(name)); + extra_data_len += strlen(name); + extra_data[extra_data_len++] = ','; + } + } + + /* Assign extra_data fields in response structure */ + + if (extra_data) { + extra_data[extra_data_len - 1] = '\0'; + state->response.extra_data = extra_data; + state->response.length += extra_data_len; + } + + /* No domains responded but that's still OK so don't return an + error. */ + + rv = WINBINDD_OK; + + done: + + talloc_destroy(mem_ctx); + + return rv; +} diff --git a/source4/nsswitch/winbindd_util.c b/source4/nsswitch/winbindd_util.c new file mode 100644 index 0000000000..7ccf032041 --- /dev/null +++ b/source4/nsswitch/winbindd_util.c @@ -0,0 +1,553 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon for ntdom nss module + + Copyright (C) Tim Potter 2000-2001 + Copyright (C) 2001 by Martin Pool <mbp@samba.org> + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "winbindd.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/** + * @file winbindd_util.c + * + * Winbind daemon for NT domain authentication nss module. + **/ + + +/** + * Used to clobber name fields that have an undefined value. + * + * Correct code should never look at a field that has this value. + **/ + +static const fstring name_deadbeef = "<deadbeef>"; + +/* The list of trusted domains. Note that the list can be deleted and + recreated using the init_domain_list() function so pointers to + individual winbindd_domain structures cannot be made. Keep a copy of + the domain name instead. */ + +static struct winbindd_domain *_domain_list; + +struct winbindd_domain *domain_list(void) +{ + /* Initialise list */ + + if (!_domain_list) + init_domain_list(); + + return _domain_list; +} + +/* Free all entries in the trusted domain list */ + +void free_domain_list(void) +{ + struct winbindd_domain *domain = _domain_list; + + while(domain) { + struct winbindd_domain *next = domain->next; + + DLIST_REMOVE(_domain_list, domain); + SAFE_FREE(domain); + domain = next; + } +} + + +/* Add a trusted domain to our list of domains */ +static struct winbindd_domain *add_trusted_domain(const char *domain_name, const char *alt_name, + struct winbindd_methods *methods, + DOM_SID *sid) +{ + struct winbindd_domain *domain; + + /* We can't call domain_list() as this function is called from + init_domain_list() and we'll get stuck in a loop. */ + for (domain = _domain_list; domain; domain = domain->next) { + if (strcasecmp(domain_name, domain->name) == 0 || + strcasecmp(domain_name, domain->alt_name) == 0) { + return domain; + } + if (alt_name && *alt_name) { + if (strcasecmp(alt_name, domain->name) == 0 || + strcasecmp(alt_name, domain->alt_name) == 0) { + return domain; + } + } + } + + /* Create new domain entry */ + + if ((domain = (struct winbindd_domain *) + malloc(sizeof(*domain))) == NULL) + return NULL; + + /* Fill in fields */ + + ZERO_STRUCTP(domain); + + /* prioritise the short name */ + if (strchr_m(domain_name, '.') && alt_name && *alt_name) { + fstrcpy(domain->name, alt_name); + fstrcpy(domain->alt_name, domain_name); + } else { + fstrcpy(domain->name, domain_name); + if (alt_name) { + fstrcpy(domain->alt_name, alt_name); + } + } + + domain->methods = methods; + domain->sequence_number = DOM_SEQUENCE_NONE; + domain->last_seq_check = 0; + if (sid) { + sid_copy(&domain->sid, sid); + } + + /* see if this is a native mode win2k domain, but only for our own domain */ + + if ( strequal( lp_workgroup(), domain_name) ) { + domain->native_mode = cm_check_for_native_mode_win2k( domain_name ); + DEBUG(3,("add_trusted_domain: %s is a %s mode domain\n", domain_name, + domain->native_mode ? "native" : "mixed" )); + } + + /* Link to domain list */ + DLIST_ADD(_domain_list, domain); + + DEBUG(1,("Added domain %s %s %s\n", + domain->name, domain->alt_name, + sid?sid_string_static(&domain->sid):"")); + + return domain; +} + + +/* + rescan our domains looking for new trusted domains + */ +void rescan_trusted_domains(BOOL force) +{ + struct winbindd_domain *domain; + TALLOC_CTX *mem_ctx; + static time_t last_scan; + time_t t = time(NULL); + + /* trusted domains might be disabled */ + if (!lp_allow_trusted_domains()) { + return; + } + + /* Only rescan every few minutes but force if necessary */ + + if (((unsigned)(t - last_scan) < WINBINDD_RESCAN_FREQ) && !force) + return; + + last_scan = t; + + DEBUG(1, ("scanning trusted domain list\n")); + + if (!(mem_ctx = talloc_init("init_domain_list"))) + return; + + for (domain = _domain_list; domain; domain = domain->next) { + NTSTATUS result; + char **names; + char **alt_names; + int num_domains = 0; + DOM_SID *dom_sids; + int i; + + result = domain->methods->trusted_domains(domain, mem_ctx, &num_domains, + &names, &alt_names, &dom_sids); + if (!NT_STATUS_IS_OK(result)) { + continue; + } + + /* Add each domain to the trusted domain list. Each domain inherits + the access methods of its parent */ + for(i = 0; i < num_domains; i++) { + DEBUG(10,("Found domain %s\n", names[i])); + add_trusted_domain(names[i], alt_names?alt_names[i]:NULL, + domain->methods, &dom_sids[i]); + + /* store trusted domain in the cache */ + trustdom_cache_store(mem_ctx, names[i], alt_names ? alt_names[i] : NULL, + &dom_sids[i], t + WINBINDD_RESCAN_FREQ); + } + } + + talloc_destroy(mem_ctx); +} + +/* Look up global info for the winbind daemon */ +BOOL init_domain_list(void) +{ + extern struct winbindd_methods cache_methods; + struct winbindd_domain *domain; + + /* Free existing list */ + free_domain_list(); + + /* Add ourselves as the first entry */ + domain = add_trusted_domain(lp_workgroup(), NULL, &cache_methods, NULL); + if (!secrets_fetch_domain_sid(domain->name, &domain->sid)) { + DEBUG(1, ("Could not fetch sid for our domain %s\n", + domain->name)); + return False; + } + + /* get any alternate name for the primary domain */ + cache_methods.alternate_name(domain); + + /* do an initial scan for trusted domains */ + rescan_trusted_domains(True); + + return True; +} + +/* Given a domain name, return the struct winbindd domain info for it + if it is actually working. */ + +struct winbindd_domain *find_domain_from_name(const char *domain_name) +{ + struct winbindd_domain *domain; + + /* Search through list */ + + for (domain = domain_list(); domain != NULL; domain = domain->next) { + if (strequal(domain_name, domain->name) || + (domain->alt_name[0] && strequal(domain_name, domain->alt_name))) + return domain; + } + + /* Not found */ + + return NULL; +} + +/* Given a domain sid, return the struct winbindd domain info for it */ + +struct winbindd_domain *find_domain_from_sid(DOM_SID *sid) +{ + struct winbindd_domain *domain; + + /* Search through list */ + + for (domain = domain_list(); domain != NULL; domain = domain->next) { + if (sid_compare_domain(sid, &domain->sid) == 0) + return domain; + } + + /* Not found */ + + return NULL; +} + +/* Lookup a sid in a domain from a name */ + +BOOL winbindd_lookup_sid_by_name(struct winbindd_domain *domain, + const char *name, DOM_SID *sid, + enum SID_NAME_USE *type) +{ + NTSTATUS result; + TALLOC_CTX *mem_ctx; + /* Don't bother with machine accounts */ + + if (name[strlen(name) - 1] == '$') + return False; + + mem_ctx = talloc_init("lookup_sid_by_name for %s\n", name); + if (!mem_ctx) + return False; + + /* Lookup name */ + result = domain->methods->name_to_sid(domain, mem_ctx, name, sid, type); + + talloc_destroy(mem_ctx); + + /* Return rid and type if lookup successful */ + if (!NT_STATUS_IS_OK(result)) { + *type = SID_NAME_UNKNOWN; + } + + return NT_STATUS_IS_OK(result); +} + +/** + * @brief Lookup a name in a domain from a sid. + * + * @param sid Security ID you want to look up. + * + * @param name On success, set to the name corresponding to @p sid. + * + * @param dom_name On success, set to the 'domain name' corresponding to @p sid. + * + * @param type On success, contains the type of name: alias, group or + * user. + * + * @retval True if the name exists, in which case @p name and @p type + * are set, otherwise False. + **/ +BOOL winbindd_lookup_name_by_sid(DOM_SID *sid, + fstring dom_name, + fstring name, + enum SID_NAME_USE *type) +{ + char *names; + NTSTATUS result; + TALLOC_CTX *mem_ctx; + BOOL rv = False; + struct winbindd_domain *domain; + + domain = find_domain_from_sid(sid); + + if (!domain) { + DEBUG(1,("Can't find domain from sid\n")); + return False; + } + + /* Lookup name */ + + if (!(mem_ctx = talloc_init("winbindd_lookup_name_by_sid"))) + return False; + + result = domain->methods->sid_to_name(domain, mem_ctx, sid, &names, type); + + /* Return name and type if successful */ + + if ((rv = NT_STATUS_IS_OK(result))) { + fstrcpy(dom_name, domain->name); + fstrcpy(name, names); + } else { + *type = SID_NAME_UNKNOWN; + fstrcpy(name, name_deadbeef); + } + + talloc_destroy(mem_ctx); + + return rv; +} + + +/* Free state information held for {set,get,end}{pw,gr}ent() functions */ + +void free_getent_state(struct getent_state *state) +{ + struct getent_state *temp; + + /* Iterate over state list */ + + temp = state; + + while(temp != NULL) { + struct getent_state *next; + + /* Free sam entries then list entry */ + + SAFE_FREE(state->sam_entries); + DLIST_REMOVE(state, state); + next = temp->next; + + SAFE_FREE(temp); + temp = next; + } +} + +/* Parse winbindd related parameters */ + +BOOL winbindd_param_init(void) +{ + /* Parse winbind uid and winbind_gid parameters */ + + if (!lp_winbind_uid(&server_state.uid_low, &server_state.uid_high)) { + DEBUG(0, ("winbind uid range missing or invalid\n")); + return False; + } + + if (!lp_winbind_gid(&server_state.gid_low, &server_state.gid_high)) { + DEBUG(0, ("winbind gid range missing or invalid\n")); + return False; + } + + return True; +} + +/* Check if a domain is present in a comma-separated list of domains */ + +BOOL check_domain_env(char *domain_env, char *domain) +{ + fstring name; + const char *tmp = domain_env; + + while(next_token(&tmp, name, ",", sizeof(fstring))) { + if (strequal(name, domain)) + return True; + } + + return False; +} + +/* Parse a string of the form DOMAIN/user into a domain and a user */ + +BOOL parse_domain_user(const char *domuser, fstring domain, fstring user) +{ + char *p = strchr(domuser,*lp_winbind_separator()); + + if (!(p || lp_winbind_use_default_domain())) + return False; + + if(!p && lp_winbind_use_default_domain()) { + fstrcpy(user, domuser); + fstrcpy(domain, lp_workgroup()); + } else { + fstrcpy(user, p+1); + fstrcpy(domain, domuser); + domain[PTR_DIFF(p, domuser)] = 0; + } + strupper(domain); + return True; +} + +/* + Fill DOMAIN\\USERNAME entry accounting 'winbind use default domain' and + 'winbind separator' options. + This means: + - omit DOMAIN when 'winbind use default domain = true' and DOMAIN is + lp_workgroup + +*/ +void fill_domain_username(fstring name, const char *domain, const char *user) +{ + if(lp_winbind_use_default_domain() && + !strcmp(lp_workgroup(), domain)) { + strlcpy(name, user, sizeof(fstring)); + } else { + slprintf(name, sizeof(fstring) - 1, "%s%s%s", + domain, lp_winbind_separator(), + user); + } +} + +/* + * Winbindd socket accessor functions + */ + +/* Open the winbindd socket */ + +static int _winbindd_socket = -1; + +int open_winbindd_socket(void) +{ + if (_winbindd_socket == -1) { + _winbindd_socket = create_pipe_sock( + WINBINDD_SOCKET_DIR, WINBINDD_SOCKET_NAME, 0755); + DEBUG(10, ("open_winbindd_socket: opened socket fd %d\n", + _winbindd_socket)); + } + + return _winbindd_socket; +} + +/* Close the winbindd socket */ + +void close_winbindd_socket(void) +{ + if (_winbindd_socket != -1) { + DEBUG(10, ("close_winbindd_socket: closing socket fd %d\n", + _winbindd_socket)); + close(_winbindd_socket); + _winbindd_socket = -1; + } +} + +/* + * Client list accessor functions + */ + +static struct winbindd_cli_state *_client_list; +static int _num_clients; + +/* Return list of all connected clients */ + +struct winbindd_cli_state *winbindd_client_list(void) +{ + return _client_list; +} + +/* Add a connection to the list */ + +void winbindd_add_client(struct winbindd_cli_state *cli) +{ + DLIST_ADD(_client_list, cli); + _num_clients++; +} + +/* Remove a client from the list */ + +void winbindd_remove_client(struct winbindd_cli_state *cli) +{ + DLIST_REMOVE(_client_list, cli); + _num_clients--; +} + +/* Close all open clients */ + +void winbindd_kill_all_clients(void) +{ + struct winbindd_cli_state *cl = winbindd_client_list(); + + DEBUG(10, ("winbindd_kill_all_clients: going postal\n")); + + while (cl) { + struct winbindd_cli_state *next; + + next = cl->next; + winbindd_remove_client(cl); + cl = next; + } +} + +/* Return number of open clients */ + +int winbindd_num_clients(void) +{ + return _num_clients; +} + +/* Help with RID -> SID conversion */ + +DOM_SID *rid_to_talloced_sid(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 rid) +{ + DOM_SID *sid; + sid = talloc(mem_ctx, sizeof(*sid)); + if (!sid) { + smb_panic("rid_to_to_talloced_sid: talloc for DOM_SID failed!\n"); + } + sid_copy(sid, &domain->sid); + sid_append_rid(sid, rid); + return sid; +} + diff --git a/source4/nsswitch/winbindd_wins.c b/source4/nsswitch/winbindd_wins.c new file mode 100644 index 0000000000..8ddd5dc10d --- /dev/null +++ b/source4/nsswitch/winbindd_wins.c @@ -0,0 +1,210 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - WINS related functions + + Copyright (C) Andrew Tridgell 1999 + Copyright (C) Herb Lewis 2002 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "winbindd.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/* Use our own create socket code so we don't recurse.... */ + +static int wins_lookup_open_socket_in(void) +{ + struct sockaddr_in sock; + int val=1; + int res; + + memset((char *)&sock,'\0',sizeof(sock)); + +#ifdef HAVE_SOCK_SIN_LEN + sock.sin_len = sizeof(sock); +#endif + sock.sin_port = 0; + sock.sin_family = AF_INET; + sock.sin_addr.s_addr = interpret_addr("0.0.0.0"); + res = socket(AF_INET, SOCK_DGRAM, 0); + if (res == -1) + return -1; + + setsockopt(res,SOL_SOCKET,SO_REUSEADDR,(char *)&val,sizeof(val)); +#ifdef SO_REUSEPORT + setsockopt(res,SOL_SOCKET,SO_REUSEPORT,(char *)&val,sizeof(val)); +#endif /* SO_REUSEPORT */ + + /* now we've got a socket - we need to bind it */ + + if (bind(res, (struct sockaddr * ) &sock,sizeof(sock)) < 0) { + close(res); + return(-1); + } + + set_socket_options(res,"SO_BROADCAST"); + + return res; +} + + +static struct node_status *lookup_byaddr_backend(char *addr, int *count) +{ + int fd; + struct in_addr ip; + struct nmb_name nname; + struct node_status *status; + + fd = wins_lookup_open_socket_in(); + if (fd == -1) + return NULL; + + make_nmb_name(&nname, "*", 0); + ip = *interpret_addr2(addr); + status = node_status_query(fd,&nname,ip, count); + + close(fd); + return status; +} + +static struct in_addr *lookup_byname_backend(const char *name, int *count) +{ + int fd; + struct in_addr *ret = NULL; + int j, flags = 0; + + *count = 0; + + /* always try with wins first */ + if (resolve_wins(name,0x20,&ret,count)) { + return ret; + } + + fd = wins_lookup_open_socket_in(); + if (fd == -1) { + return NULL; + } + + /* uggh, we have to broadcast to each interface in turn */ + for (j=iface_count() - 1; + j >= 0; + j--) { + struct in_addr *bcast = iface_n_bcast(j); + ret = name_query(fd,name,0x20,True,True,*bcast,count, &flags, NULL); + if (ret) break; + } + + close(fd); + return ret; +} + +/* Get hostname from IP */ + +enum winbindd_result winbindd_wins_byip(struct winbindd_cli_state *state) +{ + fstring response; + int i, count, maxlen, size; + struct node_status *status; + + /* Ensure null termination */ + state->request.data.winsreq[sizeof(state->request.data.winsreq)-1]='\0'; + + DEBUG(3, ("[%5d]: wins_byip %s\n", state->pid, + state->request.data.winsreq)); + + *response = '\0'; + maxlen = sizeof(response) - 1; + + if ((status = lookup_byaddr_backend(state->request.data.winsreq, &count))){ + size = strlen(state->request.data.winsreq); + if (size > maxlen) { + SAFE_FREE(status); + return WINBINDD_ERROR; + } + safe_strcat(response,state->request.data.winsreq,maxlen); + safe_strcat(response,"\t",maxlen); + for (i = 0; i < count; i++) { + /* ignore group names */ + if (status[i].flags & 0x80) continue; + if (status[i].type == 0x20) { + size = sizeof(status[i].name) + strlen(response); + if (size > maxlen) { + SAFE_FREE(status); + return WINBINDD_ERROR; + } + safe_strcat(response, status[i].name, maxlen); + safe_strcat(response, " ", maxlen); + } + } + /* make last character a newline */ + response[strlen(response)-1] = '\n'; + SAFE_FREE(status); + } + fstrcpy(state->response.data.winsresp,response); + return WINBINDD_OK; +} + +/* Get IP from hostname */ + +enum winbindd_result winbindd_wins_byname(struct winbindd_cli_state *state) +{ + struct in_addr *ip_list; + int i, count, maxlen, size; + fstring response; + char * addr; + + /* Ensure null termination */ + state->request.data.winsreq[sizeof(state->request.data.winsreq)-1]='\0'; + + DEBUG(3, ("[%5d]: wins_byname %s\n", state->pid, + state->request.data.winsreq)); + + *response = '\0'; + maxlen = sizeof(response) - 1; + + if ((ip_list = lookup_byname_backend(state->request.data.winsreq,&count))){ + for (i = count; i ; i--) { + addr = inet_ntoa(ip_list[i-1]); + size = strlen(addr); + if (size > maxlen) { + SAFE_FREE(ip_list); + return WINBINDD_ERROR; + } + if (i != 0) { + /* Clear out the newline character */ + response[strlen(response)-1] = ' '; + } + safe_strcat(response,addr,maxlen); + safe_strcat(response,"\t",maxlen); + } + size = strlen(state->request.data.winsreq) + strlen(response); + if (size > maxlen) { + SAFE_FREE(ip_list); + return WINBINDD_ERROR; + } + safe_strcat(response,state->request.data.winsreq,maxlen); + safe_strcat(response,"\n",maxlen); + SAFE_FREE(ip_list); + } else + return WINBINDD_ERROR; + + fstrcpy(state->response.data.winsresp,response); + + return WINBINDD_OK; +} diff --git a/source4/nsswitch/wins.c b/source4/nsswitch/wins.c new file mode 100644 index 0000000000..187748d285 --- /dev/null +++ b/source4/nsswitch/wins.c @@ -0,0 +1,322 @@ +/* + Unix SMB/CIFS implementation. + a WINS nsswitch module + Copyright (C) Andrew Tridgell 1999 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "includes.h" +#ifdef HAVE_NS_API_H +#undef VOLATILE + +#include <ns_daemon.h> +#endif + +#ifndef INADDRSZ +#define INADDRSZ 4 +#endif + +static int initialised; + +extern BOOL AllowDebugChange; + +/* Use our own create socket code so we don't recurse.... */ + +static int wins_lookup_open_socket_in(void) +{ + struct sockaddr_in sock; + int val=1; + int res; + + memset((char *)&sock,'\0',sizeof(sock)); + +#ifdef HAVE_SOCK_SIN_LEN + sock.sin_len = sizeof(sock); +#endif + sock.sin_port = 0; + sock.sin_family = AF_INET; + sock.sin_addr.s_addr = interpret_addr("0.0.0.0"); + res = socket(AF_INET, SOCK_DGRAM, 0); + if (res == -1) + return -1; + + setsockopt(res,SOL_SOCKET,SO_REUSEADDR,(char *)&val,sizeof(val)); +#ifdef SO_REUSEPORT + setsockopt(res,SOL_SOCKET,SO_REUSEPORT,(char *)&val,sizeof(val)); +#endif /* SO_REUSEPORT */ + + /* now we've got a socket - we need to bind it */ + + if (bind(res, (struct sockaddr * ) &sock,sizeof(sock)) < 0) { + close(res); + return(-1); + } + + set_socket_options(res,"SO_BROADCAST"); + + return res; +} + + +static void nss_wins_init(void) +{ + initialised = 1; + DEBUGLEVEL = 0; + AllowDebugChange = False; + + TimeInit(); + setup_logging("nss_wins",False); + lp_load(dyn_CONFIGFILE,True,False,False); + load_interfaces(); +} + +static struct node_status *lookup_byaddr_backend(char *addr, int *count) +{ + int fd; + struct in_addr ip; + struct nmb_name nname; + struct node_status *status; + + if (!initialised) { + nss_wins_init(); + } + + fd = wins_lookup_open_socket_in(); + if (fd == -1) + return NULL; + + make_nmb_name(&nname, "*", 0); + ip = *interpret_addr2(addr); + status = node_status_query(fd,&nname,ip, count); + + close(fd); + return status; +} + +static struct in_addr *lookup_byname_backend(const char *name, int *count) +{ + int fd = -1; + struct in_addr *ret = NULL; + struct in_addr p; + int j, flags = 0; + + if (!initialised) { + nss_wins_init(); + } + + *count = 0; + + /* always try with wins first */ + if (resolve_wins(name,0x20,&ret,count)) { + return ret; + } + + fd = wins_lookup_open_socket_in(); + if (fd == -1) { + return NULL; + } + + /* uggh, we have to broadcast to each interface in turn */ + for (j=iface_count() - 1;j >= 0;j--) { + struct in_addr *bcast = iface_n_bcast(j); + ret = name_query(fd,name,0x20,True,True,*bcast,count, &flags, NULL); + if (ret) break; + } + +out: + close(fd); + return ret; +} + + +#ifdef HAVE_NS_API_H +/* IRIX version */ + +int init(void) +{ + nsd_logprintf(NSD_LOG_MIN, "entering init (wins)\n"); + nss_wins_init(); + return NSD_OK; +} + +int lookup(nsd_file_t *rq) +{ + char *map; + char *key; + char *addr; + struct in_addr *ip_list; + struct node_status *status; + int i, count, len, size; + char response[1024]; + BOOL found = False; + + nsd_logprintf(NSD_LOG_MIN, "entering lookup (wins)\n"); + if (! rq) + return NSD_ERROR; + + map = nsd_attr_fetch_string(rq->f_attrs, "table", (char*)0); + if (! map) { + rq->f_status = NS_FATAL; + return NSD_ERROR; + } + + key = nsd_attr_fetch_string(rq->f_attrs, "key", (char*)0); + if (! key || ! *key) { + rq->f_status = NS_FATAL; + return NSD_ERROR; + } + + response[0] = '\0'; + len = sizeof(response) - 2; + + /* + * response needs to be a string of the following format + * ip_address[ ip_address]*\tname[ alias]* + */ + if (strcasecmp(map,"hosts.byaddr") == 0) { + if ( status = lookup_byaddr_backend(key, &count)) { + size = strlen(key) + 1; + if (size > len) { + free(status); + return NSD_ERROR; + } + len -= size; + strncat(response,key,size); + strncat(response,"\t",1); + for (i = 0; i < count; i++) { + /* ignore group names */ + if (status[i].flags & 0x80) continue; + if (status[i].type == 0x20) { + size = sizeof(status[i].name) + 1; + if (size > len) { + free(status); + return NSD_ERROR; + } + len -= size; + strncat(response, status[i].name, size); + strncat(response, " ", 1); + found = True; + } + } + response[strlen(response)-1] = '\n'; + free(status); + } + } else if (strcasecmp(map,"hosts.byname") == 0) { + if (ip_list = lookup_byname_backend(key, &count)) { + for (i = count; i ; i--) { + addr = inet_ntoa(ip_list[i-1]); + size = strlen(addr) + 1; + if (size > len) { + free(ip_list); + return NSD_ERROR; + } + len -= size; + if (i != 0) + response[strlen(response)-1] = ' '; + strncat(response,addr,size); + strncat(response,"\t",1); + } + size = strlen(key) + 1; + if (size > len) { + free(ip_list); + return NSD_ERROR; + } + strncat(response,key,size); + strncat(response,"\n",1); + found = True; + free(ip_list); + } + } + + if (found) { + nsd_logprintf(NSD_LOG_LOW, "lookup (wins %s) %s\n",map,response); + nsd_set_result(rq,NS_SUCCESS,response,strlen(response),VOLATILE); + return NSD_OK; + } + nsd_logprintf(NSD_LOG_LOW, "lookup (wins) not found\n"); + rq->f_status = NS_NOTFOUND; + return NSD_NEXT; +} + +#else +/**************************************************************************** +gethostbyname() - we ignore any domain portion of the name and only +handle names that are at most 15 characters long + **************************************************************************/ +NSS_STATUS +_nss_wins_gethostbyname_r(const char *name, struct hostent *he, + char *buffer, size_t buflen, int *errnop, + int *h_errnop) +{ + char **host_addresses; + struct in_addr *ip_list; + int i, count; + size_t namelen = strlen(name) + 1; + + memset(he, '\0', sizeof(*he)); + + ip_list = lookup_byname_backend(name, &count); + if (!ip_list) { + return NSS_STATUS_NOTFOUND; + } + + if (buflen < namelen + (2*count+1)*INADDRSZ) { + /* no ENOMEM error type?! */ + return NSS_STATUS_NOTFOUND; + } + + + host_addresses = (char **)buffer; + he->h_addr_list = host_addresses; + host_addresses[count] = NULL; + buffer += (count + 1) * INADDRSZ; + buflen += (count + 1) * INADDRSZ; + he->h_addrtype = AF_INET; + he->h_length = INADDRSZ; + + for (i=0;i<count;i++) { + memcpy(buffer, &ip_list[i].s_addr, INADDRSZ); + *host_addresses = buffer; + buffer += INADDRSZ; + buflen -= INADDRSZ; + host_addresses++; + } + + if (ip_list) + free(ip_list); + + memcpy(buffer, name, namelen); + he->h_name = buffer; + + return NSS_STATUS_SUCCESS; +} + + +NSS_STATUS +_nss_wins_gethostbyname2_r(const char *name, int af, struct hostent *he, + char *buffer, size_t buflen, int *errnop, + int *h_errnop) +{ + if(af!=AF_INET) { + *h_errnop = NO_DATA; + *errnop = EAFNOSUPPORT; + return NSS_STATUS_UNAVAIL; + } + + return _nss_wins_gethostbyname_r(name,he,buffer,buflen,errnop,h_errnop); +} +#endif |