diff options
Diffstat (limited to 'source3/nsswitch')
30 files changed, 12588 insertions, 0 deletions
diff --git a/source3/nsswitch/.cvsignore b/source3/nsswitch/.cvsignore new file mode 100644 index 0000000000..090b859b37 --- /dev/null +++ b/source3/nsswitch/.cvsignore @@ -0,0 +1,3 @@ +*.po +*.po32 +diffs diff --git a/source3/nsswitch/README b/source3/nsswitch/README new file mode 100644 index 0000000000..9f0c581df6 --- /dev/null +++ b/source3/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/source3/nsswitch/hp_nss_common.h b/source3/nsswitch/hp_nss_common.h new file mode 100644 index 0000000000..5f39e9abb0 --- /dev/null +++ b/source3/nsswitch/hp_nss_common.h @@ -0,0 +1,47 @@ +#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. +*/ + +#include <synch.h> +#include <pthread.h> + +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/source3/nsswitch/hp_nss_dbdefs.h b/source3/nsswitch/hp_nss_dbdefs.h new file mode 100644 index 0000000000..bd24772e33 --- /dev/null +++ b/source3/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/source3/nsswitch/nss.h b/source3/nsswitch/nss.h new file mode 100644 index 0000000000..e021b013b5 --- /dev/null +++ b/source3/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) +/* 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/source3/nsswitch/pam_winbind.c b/source3/nsswitch/pam_winbind.c new file mode 100644 index 0000000000..b192a347f4 --- /dev/null +++ b/source3/nsswitch/pam_winbind.c @@ -0,0 +1,721 @@ +/* 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" + +/* prototypes from common.c */ +void init_request(struct winbindd_request *req,int rq_type); +int write_sock(void *buffer, int count); +int read_reply(struct winbindd_response *response); + +/* 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 */ +} + + +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 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!"); + return PAM_SERVICE_ERR; + } + + /* Wait for reply */ + if (read_reply(response) == -1) { + _pam_log(LOG_ERR, "read from socket failed!"); + return PAM_SERVICE_ERR; + } + + /* Copy reply data from socket */ + if (response->result != WINBINDD_OK) { + if (response->data.auth.pam_error != PAM_SUCCESS) { + _pam_log(LOG_ERR, "request failed, PAM error was %d, NT error was %s", + 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; +} + +/* talk to winbindd */ +static int winbind_auth_request(const char *user, const char *pass, int ctrl) +{ + struct winbindd_request request; + struct winbindd_response response; + int retval; + + 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); + + retval = winbind_request(WINBINDD_PAM_AUTH, &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_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: + /* Otherwise, the authentication looked good */ + _pam_log(LOG_NOTICE, "user '%s' granted acces", 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; + } + /* should not be reached */ +} + +/* talk to winbindd */ +static int winbind_chauthtok_request(const char *user, const char *oldpass, + const char *newpass) +{ + 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 winbind_request(WINBINDD_PAM_CHAUTHTOK, &request, &response); +} + +/* + * 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 + */ + +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_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); + _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/source3/nsswitch/pam_winbind.h b/source3/nsswitch/pam_winbind.h new file mode 100644 index 0000000000..9897249e16 --- /dev/null +++ b/source3/nsswitch/pam_winbind.h @@ -0,0 +1,94 @@ +/* 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_nss_config.h" +#include "winbindd_nss.h" diff --git a/source3/nsswitch/wb_client.c b/source3/nsswitch/wb_client.c new file mode 100644 index 0000000000..53550ca353 --- /dev/null +++ b/source3/nsswitch/wb_client.c @@ -0,0 +1,299 @@ +/* + 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" + +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) { + string_to_sid(sid, response.data.sid.sid); + *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(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, 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) { + string_to_sid(sid, response.data.sid.sid); + } 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, 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) { + string_to_sid(sid, response.data.sid.sid); + } 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/source3/nsswitch/wb_common.c b/source3/nsswitch/wb_common.c new file mode 100644 index 0000000000..6a2143f8f0 --- /dev/null +++ b/source3/nsswitch/wb_common.c @@ -0,0 +1,395 @@ +/* + Unix SMB/CIFS implementation. + + winbind client common 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 "winbind_nss_config.h" +#include "winbindd_nss.h" + +/* Global variables. These are effectively the client state information */ + +int winbindd_fd = -1; /* fd for winbindd socket */ +static char *excluded_domain; + +/* Free a response structure */ + +void free_response(struct winbindd_response *response) +{ + /* Free any allocated extra_data */ + + if (response) + SAFE_FREE(response->extra_data); +} + +/* + smbd needs to be able to exclude lookups for its own domain +*/ +void winbind_exclude_domain(const char *domain) +{ + SAFE_FREE(excluded_domain); + excluded_domain = strdup(domain); +} + + +/* Initialise a request structure */ + +void init_request(struct winbindd_request *request, int request_type) +{ + static char *domain_env; + static BOOL initialised; + + request->length = sizeof(struct winbindd_request); + + request->cmd = (enum winbindd_cmd)request_type; + request->pid = getpid(); + request->domain[0] = '\0'; + + if (!initialised) { + initialised = True; + domain_env = getenv(WINBINDD_DOMAIN_ENV); + } + + if (domain_env) { + strncpy(request->domain, domain_env, + sizeof(request->domain) - 1); + request->domain[sizeof(request->domain) - 1] = '\0'; + } +} + +/* 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; + } +} + +/* Connect to winbindd socket */ + +int winbind_open_pipe_sock(void) +{ + struct sockaddr_un sunaddr; + static pid_t our_pid; + struct stat st; + pstring path; + + 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 ((winbindd_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + return -1; + } + + if (connect(winbindd_fd, (struct sockaddr *)&sunaddr, + sizeof(sunaddr)) == -1) { + close_sock(); + return -1; + } + + return winbindd_fd; +} + +/* Write data to winbindd socket with timeout */ + +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 with timeout */ + +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; + } + + /* smbd may have excluded this domain */ + if (excluded_domain && + strcasecmp(excluded_domain, request->domain) == 0) { + 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/source3/nsswitch/wbinfo.c b/source3/nsswitch/wbinfo.c new file mode 100644 index 0000000000..4ea1e6a30a --- /dev/null +++ b/source3/nsswitch/wbinfo.c @@ -0,0 +1,873 @@ +/* + Unix SMB/CIFS implementation. + + Winbind status program. + + 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 "includes.h" +#include "winbindd.h" +#include "debug.h" + +/* Prototypes from common.h */ + +NSS_STATUS winbindd_request(int req_type, + struct winbindd_request *request, + struct winbindd_response *response); + +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 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) { + 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; + BOOL result; + + ZERO_STRUCT(response); + + result = winbindd_request(WINBINDD_CHECK_MACHACC, NULL, &response) == + NSS_STATUS_SUCCESS; + + if (result) { + + if (response.data.num_entries == 0) + d_printf("Secret is good\n"); + else + d_printf("Secret is bad\n0x%08x\n", + response.data.num_entries); + + return True; + } + + return False; +} + +/* 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"); + + 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((uchar *)pass, request.data.auth_crap.chal, + (uchar *)request.data.auth_crap.lm_resp); + SMBNTencrypt((uchar *)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"); + + 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; + 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 = (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; + 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 = (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 in secrets.tdb */ + + if (!secrets_store(SECRETS_AUTH_USER, username, + strlen(username) + 1) || + !secrets_store(SECRETS_AUTH_DOMAIN, domain, + strlen(domain) + 1) || + !secrets_store(SECRETS_AUTH_PASSWORD, password, + strlen(password) + 1)) { + d_fprintf(stderr, "error storing authenticated user info\n"); + return False; + } + + return True; +} + +static BOOL wbinfo_ping(void) +{ + NSS_STATUS result; + + result = winbindd_request(WINBINDD_PING, NULL, NULL); + + /* Display response */ + + d_printf("'ping' to winbindd %s\n", + (result == NSS_STATUS_SUCCESS) ? "succeeded" : "failed"); + + return result == NSS_STATUS_SUCCESS; +} + +/* Print program usage */ + +static void usage(void) +{ + d_printf("Usage: wbinfo -ug | -n name | -sSY sid | -UG uid/gid | -tm " + "| -a user%%password\n"); + d_printf("\t-u\t\t\tlists all domain users\n"); + d_printf("\t-g\t\t\tlists all domain groups\n"); + d_printf("\t-n name\t\t\tconverts name to sid\n"); + d_printf("\t-s sid\t\t\tconverts sid to name\n"); + d_printf("\t-N name\t\t\tconverts NetBIOS name to IP (WINS)\n"); + d_printf("\t-I name\t\t\tconverts IP address to NetBIOS name (WINS)\n"); + d_printf("\t-U uid\t\t\tconverts uid to sid\n"); + d_printf("\t-G gid\t\t\tconverts gid to sid\n"); + d_printf("\t-S sid\t\t\tconverts sid to uid\n"); + d_printf("\t-Y sid\t\t\tconverts sid to gid\n"); + d_printf("\t-t\t\t\tcheck shared secret\n"); + d_printf("\t-m\t\t\tlist trusted domains\n"); + d_printf("\t-r user\t\t\tget user groups\n"); + d_printf("\t-a user%%password\tauthenticate user\n"); + d_printf("\t-p 'ping' winbindd to see if it is alive\n"); + d_printf("\t--sequence\t\tshow sequence numbers of all domains\n"); +} + +/* Main program */ + +enum { + OPT_SET_AUTH_USER = 1000, + OPT_SEQUENCE, +}; + +int main(int argc, char **argv) +{ + extern pstring global_myname; + int opt; + + poptContext pc; + static char *string_arg; + static int int_arg; + BOOL got_command = False; + int result = 1; + + struct poptOption long_options[] = { + + /* longName, shortName, argInfo, argPtr, value, descrip, + argDesc */ + + { "help", 'h', POPT_ARG_NONE, 0, 'h' }, + { "domain-users", 'u', POPT_ARG_NONE, 0, 'u' }, + { "domain-groups", 'g', POPT_ARG_NONE, 0, 'g' }, + { "WINS-by-name", 'N', POPT_ARG_STRING, &string_arg, 'N' }, + { "WINS-by-ip", 'I', POPT_ARG_STRING, &string_arg, 'I' }, + { "name-to-sid", 'n', POPT_ARG_STRING, &string_arg, 'n' }, + { "sid-to-name", 's', POPT_ARG_STRING, &string_arg, 's' }, + { "uid-to-sid", 'U', POPT_ARG_INT, &int_arg, 'U' }, + { "gid-to-sid", 'G', POPT_ARG_INT, &int_arg, 'G' }, + { "sid-to-uid", 'S', POPT_ARG_STRING, &string_arg, 'S' }, + { "sid-to-gid", 'Y', POPT_ARG_STRING, &string_arg, 'Y' }, + { "check-secret", 't', POPT_ARG_NONE, 0, 't' }, + { "trusted-domains", 'm', POPT_ARG_NONE, 0, 'm' }, + { "sequence", 0, POPT_ARG_NONE, 0, OPT_SEQUENCE }, + { "user-groups", 'r', POPT_ARG_STRING, &string_arg, 'r' }, + { "authenticate", 'a', POPT_ARG_STRING, &string_arg, 'a' }, + { "set-auth-user", 0, POPT_ARG_STRING, &string_arg, OPT_SET_AUTH_USER }, + { "ping", 'p', POPT_ARG_NONE, 0, 'p' }, + { 0, 0, 0, 0 } + }; + + /* Samba client initialisation */ + + if (!*global_myname) { + char *p; + + fstrcpy(global_myname, myhostname()); + p = strchr(global_myname, '.'); + if (p) + *p = 0; + } + + 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); + } + + load_interfaces(); + + /* Parse command line options */ + + if (argc == 1) { + usage(); + return 1; + } + + /* Parse options */ + + pc = poptGetContext("wbinfo", argc, (const char **)argv, long_options, 0); + + 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 'h': + usage(); + result = 0; + goto done; + 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: + if (!(wbinfo_set_auth_user(string_arg))) + goto done; + break; + default: + d_fprintf(stderr, "Invalid option\n"); + usage(); + goto done; + } + } + + result = 0; + + /* Exit code */ + + done: + poptFreeContext(pc); + return result; +} diff --git a/source3/nsswitch/winbind_nss.c b/source3/nsswitch/winbind_nss.c new file mode 100644 index 0000000000..0a49f5ec96 --- /dev/null +++ b/source3/nsswitch/winbind_nss.c @@ -0,0 +1,1324 @@ +/* + 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_nss_config.h" +#include "winbindd_nss.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; + +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 free_response(struct winbindd_response *response); + +#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: + 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, int *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); + + 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 int fill_grent(struct group *result, struct winbindd_gr *gr, + char *gr_mem, char **buffer, int *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 = (int)*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 */ + +NSS_STATUS +_nss_winbind_getgrent_r(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(WINBINDD_GETGRENT, &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; +} + +/* 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/source3/nsswitch/winbind_nss_config.h b/source3/nsswitch/winbind_nss_config.h new file mode 100644 index 0000000000..0de63878be --- /dev/null +++ b/source3/nsswitch/winbind_nss_config.h @@ -0,0 +1,147 @@ +/* + 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 */ + +#include <config.h> + +#include <stdio.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.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 + +#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 */ +#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) + +/* zero a structure given a pointer to the structure */ +#define ZERO_STRUCTP(x) { if ((x) != NULL) memset((char *)(x), 0, sizeof(*(x))); } + +/* 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/source3/nsswitch/winbind_nss_solaris.c b/source3/nsswitch/winbind_nss_solaris.c new file mode 100644 index 0000000000..8bf1487f5a --- /dev/null +++ b/source3/nsswitch/winbind_nss_solaris.c @@ -0,0 +1,281 @@ +/* + Solaris NSS wrapper for winbind + - Shirish Kalele 2000 + + Based on Luke Howard's ldap_nss module for Solaris + */ + +#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/source3/nsswitch/winbindd.c b/source3/nsswitch/winbindd.c new file mode 100644 index 0000000000..e9ee2a7693 --- /dev/null +++ b/source3/nsswitch/winbindd.c @@ -0,0 +1,849 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon for ntdom nss module + + Copyright (C) by Tim Potter 2000, 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" + +/* List of all connected clients */ + +struct winbindd_cli_state *client_list; +static int num_clients; +BOOL opt_nocache; + +/* 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); +} + +#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", num_clients)); + + if (DEBUGLEVEL >= 2 && num_clients) { + DEBUG(2, ("\tclient list:\n")); + for(tmp = 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(); +} + +/* Create winbindd socket */ + +static int create_sock(void) +{ + return create_pipe_sock( WINBINDD_SOCKET_DIR, + WINBINDD_SOCKET_NAME, 0755); +} + +struct dispatch_table { + enum winbindd_cmd cmd; + enum winbindd_result (*fn)(struct winbindd_cli_state *state); + 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" }, + + /* 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" }, + + /* 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 accept_sock) +{ + struct sockaddr_un sunaddr; + struct winbindd_cli_state *state; + socklen_t len; + int sock; + + /* Accept connection */ + + len = sizeof(sunaddr); + + do { + sock = accept(accept_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; + + /* Add to connection list */ + + DLIST_ADD(client_list, state); + num_clients++; +} + +/* 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 */ + + DLIST_REMOVE(client_list, state); + SAFE_FREE(state); + num_clients--; + } +} + +/* Process a complete received packet from a client */ + +static void process_packet(struct winbindd_cli_state *state) +{ + /* Process request */ + + state->pid = state->request.pid; + + process_request(state); + + /* Update client state */ + + state->read_buf_len = 0; + state->write_buf_len = sizeof(struct winbindd_response); +} + +/* Read some data from a client connection */ + +static void client_read(struct winbindd_cli_state *state) +{ + int n; + + /* Read data */ + + do { + + n = read(state->sock, state->read_buf_len + + (char *)&state->request, + sizeof(state->request) - state->read_buf_len); + + } while (n == -1 && errno == EINTR); + + 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; +} + +/* 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; + } + + do { + num_written = write(state->sock, data, state->write_buf_len); + } while (num_written == -1 && errno == EINTR); + + 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; + + /* 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 accept_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(int accept_sock) +{ + /* We'll be doing this a lot */ + + while (1) { + struct winbindd_cli_state *state; + fd_set r_fds, w_fds; + int maxfd = accept_sock, selret; + struct timeval timeout; + + /* Handle messages */ + + message_dispatch(); + + /* Free up temporary memory */ + + lp_talloc_free(); + main_loop_talloc_free(); + + /* Initialise fd lists for select() */ + + FD_ZERO(&r_fds); + FD_ZERO(&w_fds); + FD_SET(accept_sock, &r_fds); + + timeout.tv_sec = WINBINDD_ESTABLISH_LOOP; + timeout.tv_usec = 0; + + /* Set up client readers and writers */ + + state = 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 accept_sock readable */ + + if (selret > 0) { + + if (FD_ISSET(accept_sock, &r_fds)) + new_connection(accept_sock); + + /* Process activity on client connections */ + + for (state = client_list; state; state = state->next) { + + /* Data available for reading */ + + if (FD_ISSET(state->sock, &r_fds)) { + + /* Read data */ + + 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 (%d) send, should be (%d)\n", + *(uint32 *) &state->request, sizeof(state->request))); + + remove_client(state); + break; + } + + /* A request packet might be + complete */ + + if (state->read_buf_len == + sizeof(state->request)) { + 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) { + + /* Flush winbindd cache */ + + flush_caches(); + reload_services_file(True); + do_sighup = False; + } + + if (do_sigusr2) { + print_winbindd_status(); + do_sigusr2 = False; + } + } +} + +/* Main function */ + +struct winbindd_state server_state; /* Server state information */ + + +static void usage(void) +{ + printf("Usage: winbindd [options]\n"); + printf("\t-i interactive 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; + extern pstring global_myname; + extern fstring global_myworkgroup; + extern BOOL append_log; + pstring logfile; + int accept_sock; + BOOL interactive = 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 ); + + /* Append to log file by default as we are a single process daemon + program. */ + + append_log = True; + + 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, "id:s:nh")) != EOF) { + switch (opt) { + + /* Don't become a daemon */ + case 'i': + interactive = 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); + } + } + + snprintf(logfile, sizeof(logfile), "%s/log.winbindd", dyn_LOGFILEBASE); + lp_set_logfile(logfile); + setup_logging("winbindd", interactive); + 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); + } + + pidfile_create("winbindd"); + + /* Setup names. */ + + if (!*global_myname) { + char *p; + + fstrcpy(global_myname, myhostname()); + p = strchr(global_myname, '.'); + if (p) + *p = 0; + } + + fstrcpy(global_myworkgroup, lp_workgroup()); + + if (!interactive) + become_daemon(); + +#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 + + load_interfaces(); + + secrets_init(); + + /* Get list of domains we look up requests for. This includes the + domain which we are a member of as well as any trusted + domains. */ + + init_domain_list(); + + ZERO_STRUCT(server_state); + + /* Winbind daemon initialisation */ + + if (!winbindd_param_init()) + return 1; + + if (!winbindd_idmap_init()) + return 1; + + /* 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); + + /* Initialise messaging system */ + + if (!message_init()) { + DEBUG(0, ("unable to initialise messaging system\n")); + exit(1); + } + + register_msg_pool_usage(); + + /* Create UNIX domain socket */ + + if ((accept_sock = create_sock()) == -1) { + DEBUG(0, ("failed to create socket\n")); + return 1; + } + + /* Loop waiting for requests */ + + process_loop(accept_sock); + + uni_group_cache_shutdown(); + return 0; +} diff --git a/source3/nsswitch/winbindd.h b/source3/nsswitch/winbindd.h new file mode 100644 index 0000000000..4d35c27c21 --- /dev/null +++ b/source3/nsswitch/winbindd.h @@ -0,0 +1,206 @@ +/* + 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 _WINBINDD_H +#define _WINBINDD_H + +#include "includes.h" +#include "nterr.h" + +#include "winbindd_nss.h" + +/* 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 */ + 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 */ + uint32 user_rid, group_rid; /* NT user and group rids */ +}; + +/* 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; + uint32 user_rid; + uint32 group_rid; /* primary group */ +} WINBIND_USERINFO; + +/* Structures to hold per domain information */ + +struct winbindd_domain { + fstring name; /* Domain name */ + fstring full_name; /* full Domain name (realm) */ + DOM_SID sid; /* SID for this domain */ + + /* 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 groups */ + NTSTATUS (*enum_dom_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, + 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 rid */ + NTSTATUS (*query_user)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 user_rid, + 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, + uint32 user_rid, + uint32 *num_groups, uint32 **user_gids); + + /* find all members of the group with the specified group_rid */ + NTSTATUS (*lookup_groupmem)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 group_rid, uint32 *num_names, + uint32 **rid_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, + DOM_SID **dom_sids); + + /* find the domain sid */ + NTSTATUS (*domain_sid)(struct winbindd_domain *domain, + DOM_SID *sid); +}; + +/* Used to glue a policy handle and cli_state together */ + +typedef struct { + struct cli_state *cli; + POLICY_HND pol; +} CLI_POLICY_HND; + +#include "winbindd_proto.h" + +#include "rpc_parse.h" +#include "rpc_client.h" + +#define WINBINDD_ESTABLISH_LOOP 30 +#define DOM_SEQUENCE_NONE ((uint32)-1) + +/* SETENV */ +#if HAVE_SETENV +#define SETENV(name, value, overwrite) setenv(name,value,overwrite) +#elif HAVE_PUTENV +#define SETENV(name, value, overwrite) \ +{ \ + fstring envvar; \ + slprintf(envvar, sizeof(fstring), "%s=%s", name, value); \ + putenv(envvar); \ +} +#else +#define SETENV(name, value, overwrite) ; +#endif + +#endif /* _WINBINDD_H */ diff --git a/source3/nsswitch/winbindd_ads.c b/source3/nsswitch/winbindd_ads.c new file mode 100644 index 0000000000..22bad667c3 --- /dev/null +++ b/source3/nsswitch/winbindd_ads.c @@ -0,0 +1,802 @@ +/* + Unix SMB/CIFS implementation. + + Winbind ADS 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" + +#ifdef HAVE_ADS + +/* the realm of our primary LDAP server */ +static char *primary_realm; + + +/* + a wrapper around ldap_search_s that retries depending on the error code + this is supposed to catch dropped connections and auto-reconnect +*/ +ADS_STATUS ads_do_search_retry(ADS_STRUCT *ads, const char *bind_path, int scope, + const char *exp, + const char **attrs, void **res) +{ + ADS_STATUS status; + int count = 3; + char *bp; + + if (!ads->ld && + time(NULL) - ads->last_attempt < ADS_RECONNECT_TIME) { + return ADS_ERROR(LDAP_SERVER_DOWN); + } + + bp = strdup(bind_path); + + while (count--) { + status = ads_do_search_all(ads, bp, scope, exp, attrs, res); + if (ADS_ERR_OK(status)) { + DEBUG(5,("Search for %s gave %d replies\n", + exp, ads_count_replies(ads, *res))); + free(bp); + return status; + } + + if (*res) ads_msgfree(ads, *res); + *res = NULL; + DEBUG(1,("Reopening ads connection to %s after error %s\n", + ads->ldap_server, ads_errstr(status))); + if (ads->ld) { + ldap_unbind(ads->ld); + } + ads->ld = NULL; + status = ads_connect(ads); + if (!ADS_ERR_OK(status)) { + DEBUG(1,("ads_search_retry: failed to reconnect (%s)\n", + ads_errstr(status))); + ads_destroy(&ads); + free(bp); + return status; + } + } + free(bp); + + DEBUG(1,("ads reopen failed after error %s\n", ads_errstr(status))); + return status; +} + + +ADS_STATUS ads_search_retry(ADS_STRUCT *ads, void **res, + const char *exp, + const char **attrs) +{ + return ads_do_search_retry(ads, ads->bind_path, LDAP_SCOPE_SUBTREE, + exp, attrs, res); +} + +ADS_STATUS ads_search_retry_dn(ADS_STRUCT *ads, void **res, + const char *dn, + const char **attrs) +{ + return ads_do_search_retry(ads, dn, LDAP_SCOPE_BASE, + "(objectclass=*)", attrs, res); +} + +/* + 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; + char *ccache; + struct in_addr server_ip; + char *sname; + + if (domain->private) { + return (ADS_STRUCT *)domain->private; + } + + /* we don't want this to affect the users ccache */ + ccache = lock_path("winbindd_ccache"); + SETENV("KRB5CCNAME", ccache, 1); + unlink(ccache); + + if (resolve_name(domain->name, &server_ip, 0x1b)) { + sname = inet_ntoa(server_ip); + } else { + if (strcasecmp(domain->name, lp_workgroup()) != 0) { + DEBUG(1,("can't find domain controller for %s\n", domain->name)); + return NULL; + } + sname = NULL; + } + + ads = ads_init(primary_realm, sname, NULL, 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->password); + ads->password = secrets_fetch_machine_password(); + + status = ads_connect(ads); + if (!ADS_ERR_OK(status)) { + 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.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->realm); + } + + fstrcpy(domain->full_name, ads->server_realm); + + domain->private = (void *)ads; + return ads; +} + +/* useful utility */ +static void sid_from_rid(struct winbindd_domain *domain, uint32 rid, DOM_SID *sid) +{ + sid_copy(sid, &domain->sid); + sid_append_rid(sid, rid); +} + +/* turn a sAMAccountType into a SID_NAME_USE */ +static enum SID_NAME_USE ads_atype_map(uint32 atype) +{ + switch (atype & 0xF0000000) { + case ATYPE_GROUP: + return SID_NAME_DOM_GRP; + case ATYPE_USER: + return SID_NAME_USER; + default: + DEBUG(1,("hmm, need to map account type 0x%x\n", atype)); + } + return SID_NAME_UNKNOWN; +} + +/* 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[] = {"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; + uint32 rid, 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_string(ads, mem_ctx, msg, "sAMAccountName"); + 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; + } + + if (!sid_peek_rid(&sid, &rid)) { + DEBUG(1,("No rid for %s !?\n", name)); + continue; + } + + (*info)[i].acct_name = name; + (*info)[i].full_name = gecos; + (*info)[i].user_rid = rid; + (*info)[i].group_rid = group; + 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[] = {"sAMAccountName", "name", "objectSid", + "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: 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,("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; + uint32 rid; + uint32 account_type; + + if (!ads_pull_uint32(ads, msg, "sAMAccountType", + &account_type) || + !(account_type & ATYPE_GROUP)) continue; + + name = ads_pull_string(ads, mem_ctx, msg, "sAMAccountName"); + 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_rid(&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; +} + + +/* convert a single name to a sid in a domain */ +static NTSTATUS name_to_sid(struct winbindd_domain *domain, + const char *name, + DOM_SID *sid, + enum SID_NAME_USE *type) +{ + ADS_STRUCT *ads = NULL; + const char *attrs[] = {"objectSid", "sAMAccountType", NULL}; + int count; + ADS_STATUS rc; + void *res = NULL; + char *exp; + uint32 t; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + DEBUG(3,("ads: name_to_sid\n")); + + ads = ads_cached_connection(domain); + if (!ads) goto done; + + asprintf(&exp, "(sAMAccountName=%s)", name); + rc = ads_search_retry(ads, &res, exp, attrs); + free(exp); + if (!ADS_ERR_OK(rc)) { + DEBUG(1,("name_to_sid ads_search: %s\n", ads_errstr(rc))); + goto done; + } + + count = ads_count_replies(ads, res); + if (count != 1) { + DEBUG(1,("name_to_sid: %s not found\n", name)); + goto done; + } + + if (!ads_pull_sid(ads, res, "objectSid", sid)) { + DEBUG(1,("No sid for %s !?\n", name)); + goto done; + } + + if (!ads_pull_uint32(ads, res, "sAMAccountType", &t)) { + DEBUG(1,("No sAMAccountType for %s !?\n", name)); + goto done; + } + + *type = ads_atype_map(t); + + status = NT_STATUS_OK; + + DEBUG(3,("ads name_to_sid mapped %s\n", name)); + +done: + if (res) ads_msgfree(ads, res); + + return status; +} + +/* 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; + const char *attrs[] = {"sAMAccountName", "sAMAccountType", NULL}; + ADS_STATUS rc; + void *msg = NULL; + char *exp; + char *sidstr; + uint32 atype; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + DEBUG(3,("ads: sid_to_name\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,("sid_to_name ads_search: %s\n", ads_errstr(rc))); + goto done; + } + + if (!ads_pull_uint32(ads, msg, "sAMAccountType", &atype)) { + goto done; + } + + *name = ads_pull_string(ads, mem_ctx, msg, "sAMAccountName"); + *type = ads_atype_map(atype); + + status = NT_STATUS_OK; + + DEBUG(3,("ads sid_to_name mapped %s\n", *name)); + +done: + if (msg) ads_msgfree(ads, msg); + + return status; +} + + +/* convert a sid to a distnguished name */ +static NTSTATUS sid_to_distinguished_name(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + DOM_SID *sid, + char **dn) +{ + ADS_STRUCT *ads = NULL; + const char *attrs[] = {"distinguishedName", NULL}; + ADS_STATUS rc; + void *msg = NULL; + char *exp; + char *sidstr; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + DEBUG(3,("ads: sid_to_distinguished_name\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,("sid_to_distinguished_name ads_search: %s\n", ads_errstr(rc))); + goto done; + } + + *dn = ads_pull_string(ads, mem_ctx, msg, "distinguishedName"); + + status = NT_STATUS_OK; + + DEBUG(3,("ads sid_to_distinguished_name mapped %s\n", *dn)); + +done: + if (msg) ads_msgfree(ads, msg); + + return status; +} + + +/* Lookup user information from a rid */ +static NTSTATUS query_user(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 user_rid, + WINBIND_USERINFO *info) +{ + ADS_STRUCT *ads = NULL; + const char *attrs[] = {"sAMAccountName", "name", "objectSid", + "primaryGroupID", NULL}; + ADS_STATUS rc; + int count; + void *msg = NULL; + char *exp; + DOM_SID sid; + char *sidstr; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + DEBUG(3,("ads: query_user\n")); + + sid_from_rid(domain, user_rid, &sid); + + 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(rid=%d) ads_search: %s\n", user_rid, ads_errstr(rc))); + goto done; + } + + count = ads_count_replies(ads, msg); + if (count != 1) { + DEBUG(1,("query_user(rid=%d): Not found\n", user_rid)); + goto done; + } + + info->acct_name = ads_pull_string(ads, mem_ctx, msg, "sAMAccountName"); + info->full_name = ads_pull_string(ads, mem_ctx, msg, "name"); + if (!ads_pull_sid(ads, msg, "objectSid", &sid)) { + DEBUG(1,("No sid for %d !?\n", user_rid)); + goto done; + } + if (!ads_pull_uint32(ads, msg, "primaryGroupID", &info->group_rid)) { + DEBUG(1,("No primary group for %d !?\n", user_rid)); + goto done; + } + + if (!sid_peek_rid(&sid, &info->user_rid)) { + DEBUG(1,("No rid for %d !?\n", user_rid)); + goto done; + } + + 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. */ +static NTSTATUS lookup_usergroups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 user_rid, + uint32 *num_groups, uint32 **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; + uint32 primary_group; + DOM_SID sid; + char *sidstr; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + *num_groups = 0; + + DEBUG(3,("ads: lookup_usergroups\n")); + + (*num_groups) = 0; + + sid_from_rid(domain, user_rid, &sid); + + 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,("lookup_usergroups(rid=%d) ads_search: %s\n", user_rid, ads_errstr(rc))); + goto done; + } + + user_dn = ads_pull_string(ads, mem_ctx, msg, "distinguishedName"); + + 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(rid=%d) ads_search tokenGroups: %s\n", user_rid, ads_errstr(rc))); + goto done; + } + + if (!ads_pull_uint32(ads, msg, "primaryGroupID", &primary_group)) { + DEBUG(1,("%s: No primary group for rid=%d !?\n", domain->name, user_rid)); + goto done; + } + + count = ads_pull_sids(ads, mem_ctx, msg, "tokenGroups", &sids) + 1; + (*user_gids) = (uint32 *)talloc_zero(mem_ctx, sizeof(uint32) * count); + (*user_gids)[(*num_groups)++] = primary_group; + + for (i=1;i<count;i++) { + uint32 rid; + if (!sid_peek_rid(&sids[i-1], &rid)) continue; + (*user_gids)[*num_groups] = rid; + (*num_groups)++; + } + + status = NT_STATUS_OK; + DEBUG(3,("ads lookup_usergroups for rid=%d\n", user_rid)); +done: + if (msg) ads_msgfree(ads, msg); + + return status; +} + + +static NTSTATUS lookup_groupmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 group_rid, uint32 *num_names, + uint32 **rid_mem, char ***names, + uint32 **name_types) +{ + DOM_SID group_sid; + const char *attrs[] = {"sAMAccountName", "objectSid", "sAMAccountType", NULL}; + ADS_STATUS rc; + int count; + void *res=NULL, *msg=NULL; + ADS_STRUCT *ads = NULL; + char *exp, *dn = NULL; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + *num_names = 0; + + ads = ads_cached_connection(domain); + if (!ads) goto done; + + sid_from_rid(domain, group_rid, &group_sid); + status = sid_to_distinguished_name(domain, mem_ctx, &group_sid, &dn); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3,("Failed to find distinguishedName for %s\n", sid_string_static(&group_sid))); + return status; + } + + /* search for all users who have that group sid as primary group or as member */ + asprintf(&exp, "(&(objectCategory=user)(|(primaryGroupID=%d)(memberOf=%s)))", + group_rid, dn); + rc = ads_search_retry(ads, &res, exp, attrs); + free(exp); + 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; + } + + (*rid_mem) = talloc_zero(mem_ctx, sizeof(uint32) * count); + (*name_types) = talloc_zero(mem_ctx, sizeof(uint32) * count); + (*names) = talloc_zero(mem_ctx, sizeof(char *) * count); + + for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) { + uint32 atype, rid; + DOM_SID sid; + + (*names)[*num_names] = ads_pull_string(ads, mem_ctx, msg, "sAMAccountName"); + if (!ads_pull_uint32(ads, msg, "sAMAccountType", &atype)) { + continue; + } + (*name_types)[*num_names] = ads_atype_map(atype); + if (!ads_pull_sid(ads, msg, "objectSid", &sid)) { + DEBUG(1,("No sid for %s !?\n", (*names)[*num_names])); + continue; + } + if (!sid_peek_rid(&sid, &rid)) { + DEBUG(1,("No rid for %s !?\n", (*names)[*num_names])); + continue; + } + (*rid_mem)[*num_names] = rid; + (*num_names)++; + } + + status = NT_STATUS_OK; + DEBUG(3,("ads lookup_groupmem for rid=%d\n", group_rid)); +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, + 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, 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); +} + +/* the ADS backend methods are exposed via this structure */ +struct winbindd_methods ads_methods = { + True, + query_user_list, + enum_dom_groups, + name_to_sid, + sid_to_name, + query_user, + lookup_usergroups, + lookup_groupmem, + sequence_number, + trusted_domains, + domain_sid +}; + +#endif diff --git a/source3/nsswitch/winbindd_cache.c b/source3/nsswitch/winbindd_cache.c new file mode 100644 index 0000000000..9bd95fdd86 --- /dev/null +++ b/source3/nsswitch/winbindd_cache.c @@ -0,0 +1,882 @@ +/* + 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" + +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_DEFAULT, O_RDWR | O_CREAT | O_TRUNC, 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; +} + +/* 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; + + time_diff = time(NULL) - domain->last_seq_check; + + /* see if we have to refetch the domain sequence number */ + if (!force && (time_diff < lp_winbind_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, ...) +{ + 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)) { + 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; +} + +/* + 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, ...) +{ + 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); +} + +/* form a sid from the domain plus rid */ +static DOM_SID *form_sid(struct winbindd_domain *domain, uint32 rid) +{ + static DOM_SID sid; + sid_copy(&sid, &domain->sid); + sid_append_rid(&sid, rid); + return &sid; +} + +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; + + centry = centry_start(domain, status); + if (!centry) return; + len = sid_size(sid); + centry_expand(centry, len); + centry_put_uint32(centry, type); + sid_linearize(centry->data + centry->ofs, len, sid); + centry->ofs += len; + centry_end(centry, "NS/%s/%s", domain->name, name); + 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, uint32 rid) +{ + struct cache_entry *centry; + + 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/%d", domain->name, rid); + centry_free(centry); +} + + +static void wcache_save_user(struct winbindd_domain *domain, NTSTATUS status, WINBIND_USERINFO *info) +{ + struct cache_entry *centry; + + centry = centry_start(domain, status); + if (!centry) return; + centry_put_string(centry, info->acct_name); + centry_put_string(centry, info->full_name); + centry_put_uint32(centry, info->user_rid); + centry_put_uint32(centry, info->group_rid); + centry_end(centry, "U/%s/%x", domain->name, info->user_rid); + 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; + 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_rid = centry_uint32(centry); + (*info)[i].group_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->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_uint32(centry, (*info)[i].user_rid); + centry_put_uint32(centry, (*info)[i].group_rid); + 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, + form_sid(domain, (*info)[i].user_rid), + SID_NAME_USER); + wcache_save_sid_to_name(domain, NT_STATUS_OK, + form_sid(domain, (*info)[i].user_rid), + (*info)[i].acct_name, + SID_NAME_USER, (*info)[i].user_rid); + 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; + int i; + + if (!cache->tdb) goto do_query; + + centry = wcache_fetch(cache, domain, "GL/%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("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->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, + 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; + + if (!cache->tdb) goto do_query; + + centry = wcache_fetch(cache, domain, "NS/%s/%s", domain->name, name); + if (!centry) goto do_query; + *type = centry_uint32(centry); + sid_parse(centry->data + centry->ofs, centry->len - centry->ofs, sid); + + 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, name, sid, type); + + /* and save it */ + wcache_save_name_to_sid(domain, status, name, sid, *type); + + 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; + uint32 rid = 0; + + sid_peek_rid(sid, &rid); + + if (!cache->tdb) goto do_query; + + centry = wcache_fetch(cache, domain, "SN/%s/%d", domain->name, rid); + 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, rid); + + return status; +} + + +/* Lookup user information from a rid */ +static NTSTATUS query_user(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 user_rid, + WINBIND_USERINFO *info) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + + if (!cache->tdb) goto do_query; + + centry = wcache_fetch(cache, domain, "U/%s/%x", domain->name, user_rid); + if (!centry) goto do_query; + + info->acct_name = centry_string(centry, mem_ctx); + info->full_name = centry_string(centry, mem_ctx); + info->user_rid = centry_uint32(centry); + info->group_rid = centry_uint32(centry); + 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_rid, 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, + uint32 user_rid, + uint32 *num_groups, uint32 **user_gids) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + int i; + + if (!cache->tdb) goto do_query; + + centry = wcache_fetch(cache, domain, "UG/%s/%x", domain->name, user_rid); + 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_uint32(centry); + } + +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_rid, 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_uint32(centry, (*user_gids)[i]); + } + centry_end(centry, "UG/%s/%x", domain->name, user_rid); + centry_free(centry); + +skip_save: + return status; +} + + +static NTSTATUS lookup_groupmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32 group_rid, uint32 *num_names, + uint32 **rid_mem, char ***names, + uint32 **name_types) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + int i; + + if (!cache->tdb) goto do_query; + + centry = wcache_fetch(cache, domain, "GM/%s/%x", domain->name, group_rid); + if (!centry) goto do_query; + + *num_names = centry_uint32(centry); + + if (*num_names == 0) goto do_cached; + + (*rid_mem) = talloc(mem_ctx, sizeof(**rid_mem) * (*num_names)); + (*names) = talloc(mem_ctx, sizeof(**names) * (*num_names)); + (*name_types) = talloc(mem_ctx, sizeof(**name_types) * (*num_names)); + + if (! (*rid_mem) || ! (*names) || ! (*name_types)) { + smb_panic("lookup_groupmem out of memory"); + } + + for (i=0; i<(*num_names); i++) { + (*rid_mem)[i] = centry_uint32(centry); + (*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; + (*rid_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_rid, num_names, + rid_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_uint32(centry, (*rid_mem)[i]); + centry_put_string(centry, (*names)[i]); + centry_put_uint32(centry, (*name_types)[i]); + } + centry_end(centry, "GM/%s/%x", domain->name, group_rid); + 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, + 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, 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); +} + +/* the ADS backend methods are exposed via this structure */ +struct winbindd_methods cache_methods = { + True, + query_user_list, + enum_dom_groups, + name_to_sid, + sid_to_name, + query_user, + lookup_usergroups, + lookup_groupmem, + sequence_number, + trusted_domains, + domain_sid +}; + + diff --git a/source3/nsswitch/winbindd_cm.c b/source3/nsswitch/winbindd_cm.c new file mode 100644 index 0000000000..ce484795f8 --- /dev/null +++ b/source3/nsswitch/winbindd_cm.c @@ -0,0 +1,856 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon connection manager + + Copyright (C) Tim Potter 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. +*/ + +/* + 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" + +/* 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; + 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; +}; + +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 *ip_list, dc_ip; + int count, i; + + /* 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); + + /* Lookup domain controller name. Try the real PDC first to avoid + SAM sync delays */ + if (!get_dc_list(True, domain, &ip_list, &count)) { + if (!get_dc_list(False, domain, &ip_list, &count)) { + DEBUG(3, ("Could not look up dc's for domain %s\n", domain)); + return False; + } + } + + /* Pick a nice close server */ + /* Look for DC on local net */ + + for (i = 0; i < count; i++) { + if (!is_local_net(ip_list[i])) + continue; + + if (name_status_find(domain, 0x1c, 0x20, ip_list[i], srv_name)) { + dc_ip = ip_list[i]; + goto done; + } + zero_ip(&ip_list[i]); + } + + /* + * Secondly try and contact a random PDC/BDC. + */ + + i = (sys_random() % count); + + if (!is_zero_ip(ip_list[i]) && + name_status_find(domain, 0x1c, 0x20, + ip_list[i], srv_name)) { + dc_ip = ip_list[i]; + goto done; + } + zero_ip(&ip_list[i]); /* Tried and failed. */ + + /* Finally return first DC that we can contact */ + + for (i = 0; i < count; i++) { + if (is_zero_ip(ip_list[i])) + continue; + + if (name_status_find(domain, 0x1c, 0x20, ip_list[i], srv_name)) { + dc_ip = ip_list[i]; + goto done; + } + } + + /* No-one to talk to )-: */ + return False; /* Boo-hoo */ + + done: + /* We have the netbios name and IP address of a domain controller. + Ideally we should sent a SAMLOGON request to determine whether + the DC is alive and kicking. If we can catch a dead DC before + performing a cli_connect() we can avoid a 30-second timeout. */ + + /* 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; + + SAFE_FREE(ip_list); + + 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()); + } + + 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)); + + /* 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 char *pipe_name, + struct winbindd_cm_conn *new_conn) +{ + struct failed_connection_cache *fcc; + extern pstring global_myname; + NTSTATUS result; + char *ipc_username, *ipc_domain, *ipc_password; + struct in_addr dc_ip; + + ZERO_STRUCT(dc_ip); + + fstrcpy(new_conn->domain, domain); + fstrcpy(new_conn->pipe_name, pipe_name); + + /* 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, global_myname, ipc_domain, ipc_username)); + + result = cli_full_connection(&(new_conn->cli), global_myname, new_conn->controller, + &dc_ip, 0, "IPC$", + "IPC", ipc_username, ipc_domain, + ipc_password, strlen(ipc_password)); + + SAFE_FREE(ipc_username); + SAFE_FREE(ipc_domain); + SAFE_FREE(ipc_password); + + if (!NT_STATUS_IS_OK(result)) { + add_failed_connection_entry(new_conn, result); + return result; + } + + if (!cli_nt_session_open (new_conn->cli, pipe_name)) { + result = NT_STATUS_PIPE_NOT_AVAILABLE; + add_failed_connection_entry(new_conn, result); + cli_shutdown(new_conn->cli); + return result; + } + + 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) +{ + 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 { + 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, pipe_name, conn))) { + 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; +} + +/* Return a LSA policy handle on a domain */ + +CLI_POLICY_HND *cm_get_lsa_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_LSARPC, &conn))) { + 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))) { + 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))) { + 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))) { + 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(char *domain, unsigned char *trust_passwd, + struct cli_state **cli) +{ + NTSTATUS result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + struct winbindd_cm_conn *conn; + + if (!cli) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* Open an initial conection */ + + if (!NT_STATUS_IS_OK(result = get_connection_from_cache(domain, PIPE_NETLOGON, &conn))) { + return result; + } + + result = new_cli_nt_setup_creds(conn->cli, (lp_server_role() == ROLE_DOMAIN_MEMBER) ? + SEC_CHAN_WKSTA : SEC_CHAN_BDC, trust_passwd); + + 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))) { + return result; + } + + /* Try again */ + result = new_cli_nt_setup_creds(conn->cli, (lp_server_role() == ROLE_DOMAIN_MEMBER) ? + SEC_CHAN_WKSTA : SEC_CHAN_BDC, trust_passwd); + } + + 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/source3/nsswitch/winbindd_group.c b/source3/nsswitch/winbindd_group.c new file mode 100644 index 0000000000..4ef57513bb --- /dev/null +++ b/source3/nsswitch/winbindd_group.c @@ -0,0 +1,842 @@ +/* + 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" + +/*************************************************************** + 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_rid */ + +static BOOL fill_grent_mem(struct winbindd_domain *domain, + uint32 group_rid, + enum SID_NAME_USE group_name_type, + int *num_gr_mem, char **gr_mem, int *gr_mem_len) +{ + uint32 *rid_mem = NULL, num_names = 0; + uint32 *name_types = NULL; + int buf_len, buf_ndx, i; + char **names = NULL, *buf; + BOOL result = False; + TALLOC_CTX *mem_ctx; + NTSTATUS status; + + if (!(mem_ctx = talloc_init_named("fill_grent_mem(%s)", domain->name))) + return False; + + /* Initialise group membership information */ + + DEBUG(10, ("group %s rid 0x%x\n", domain ? domain->name : "NULL", + group_rid)); + + *num_gr_mem = 0; + + if (group_name_type != SID_NAME_DOM_GRP) { + DEBUG(1, ("rid %d in domain %s isn't a " "domain group\n", + group_rid, domain->name)); + goto done; + } + + /* Lookup group members */ + status = domain->methods->lookup_groupmem(domain, mem_ctx, group_rid, &num_names, + &rid_mem, &names, &name_types); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("could not lookup membership for group rid %d in domain %s\n", + group_rid, domain->name)); + + 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 %x %d\n", names[i], rid_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; + uint32 group_rid; + fstring name_domain, name_group; + char *tmp, *gr_mem; + gid_t gid; + int gr_mem_len; + + 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; + } + + /* Fill in group structure */ + sid_peek_rid(&group_sid, &group_rid); + + 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_rid, 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; + uint32 group_rid; + 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_rid_from_gid(state->request.data.gid, + &group_rid, &domain)) { + DEBUG(1, ("could not convert gid %d to rid\n", + state->request.data.gid)); + return WINBINDD_ERROR; + } + + /* Get sid from gid */ + + sid_copy(&group_sid, &domain->sid); + sid_append_rid(&group_sid, group_rid); + + 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 */ + + if (!fill_grent(&state->response.data.gr, dom_name, group_name, + state->request.data.gid) || + !fill_grent_mem(domain, group_rid, 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; + + /* Skip domains other than WINBINDD_DOMAIN environment + variable */ + + if ((strcmp(state->request.domain, "") != 0) && + !check_domain_env(state->request.domain, domain->name)) + continue; + + /* 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->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; + 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_named("get_sam_group_entries(%s)", + ent->domain_name))) + 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; + } + + status = domain->methods->enum_dom_groups(domain, + mem_ctx, + &num_entries, + &sam_grp_entries); + + if (!NT_STATUS_IS_OK(status)) { + result = False; + goto done; + } + + /* Copy entries into return buffer */ + + if (num_entries) { + name_list = malloc(sizeof(struct acct_info) * num_entries); + memcpy(name_list, 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; + + /* 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; + + /* Lookup group info */ + + if (!winbindd_idmap_get_gid_from_rid( + ent->domain_name, + name_list[ent->sam_entry_index].rid, + &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) { + struct winbindd_domain *domain; + + 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; + } + + /* Get group membership */ + + result = fill_grent_mem( + domain, + name_list[ent->sam_entry_index].rid, + 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; + 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); + + /* Skip domains other than WINBINDD_DOMAIN environment + variable */ + if ((strcmp(state->request.domain, "") != 0) && + !check_domain_env(state->request.domain, domain->name)) + continue; + + /* 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 user_rid, num_groups, num_gids; + NTSTATUS status; + uint32 *user_gids; + struct winbindd_domain *domain; + enum winbindd_result result = WINBINDD_ERROR; + gid_t *gid_list; + int i; + TALLOC_CTX *mem_ctx; + + DEBUG(3, ("[%5d]: getgroups %s\n", state->pid, + state->request.data.username)); + + if (!(mem_ctx = talloc_init_named("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; + } + + sid_split_rid(&user_sid, &user_rid); + + status = domain->methods->lookup_usergroups(domain, mem_ctx, user_rid, &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_rid(domain->name, + user_gids[i], + &gid_list[num_gids])) { + + DEBUG(1, ("unable to convert group rid %d to gid\n", + 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/source3/nsswitch/winbindd_idmap.c b/source3/nsswitch/winbindd_idmap.c new file mode 100644 index 0000000000..bae61449ee --- /dev/null +++ b/source3/nsswitch/winbindd_idmap.c @@ -0,0 +1,519 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - user related function + + 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" + +/* 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; + +/* Allocate either a user or group id from the pool */ + +static BOOL 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 an id from a rid */ +static BOOL 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 && 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; +} + +/* Get a uid from a user sid */ +BOOL winbindd_idmap_get_uid_from_sid(DOM_SID *sid, uid_t *uid) +{ + return get_id_from_sid(sid, uid, False); +} + +/* Get a gid from a group sid */ +BOOL winbindd_idmap_get_gid_from_sid(DOM_SID *sid, gid_t *gid) +{ + return get_id_from_sid(sid, gid, True); +} + +/* Get a uid from a user rid */ +BOOL winbindd_idmap_get_uid_from_rid(const char *dom_name, uint32 rid, uid_t *uid) +{ + struct winbindd_domain *domain; + DOM_SID sid; + + if (!(domain = find_domain_from_name(dom_name))) { + return False; + } + + sid_copy(&sid, &domain->sid); + sid_append_rid(&sid, rid); + + return get_id_from_sid(&sid, uid, False); +} + +/* Get a gid from a group rid */ +BOOL winbindd_idmap_get_gid_from_rid(const char *dom_name, uint32 rid, gid_t *gid) +{ + struct winbindd_domain *domain; + DOM_SID sid; + + if (!(domain = find_domain_from_name(dom_name))) { + return False; + } + + sid_copy(&sid, &domain->sid); + sid_append_rid(&sid, rid); + + return get_id_from_sid(&sid, gid, True); +} + + +BOOL 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 a sid from a uid */ +BOOL winbindd_idmap_get_sid_from_uid(uid_t uid, DOM_SID *sid) +{ + return get_sid_from_id((int)uid, sid, False); +} + +/* Get a sid from a gid */ +BOOL winbindd_idmap_get_sid_from_gid(gid_t gid, DOM_SID *sid) +{ + return get_sid_from_id((int)gid, sid, True); +} + +/* Get a user rid from a uid */ +BOOL winbindd_idmap_get_rid_from_uid(uid_t uid, uint32 *user_rid, + struct winbindd_domain **domain) +{ + DOM_SID sid; + + if (!get_sid_from_id((int)uid, &sid, False)) { + return False; + } + + *domain = find_domain_from_sid(&sid); + if (! *domain) return False; + + sid_split_rid(&sid, user_rid); + + return True; +} + +/* Get a group rid from a gid */ + +BOOL winbindd_idmap_get_rid_from_gid(gid_t gid, uint32 *group_rid, + struct winbindd_domain **domain) +{ + DOM_SID sid; + + if (!get_sid_from_id((int)gid, &sid, True)) { + return False; + } + + *domain = find_domain_from_sid(&sid); + if (! *domain) return False; + + sid_split_rid(&sid, group_rid); + + return True; +} + +/* convert one record to the new format */ +static int 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: convert_fn : Unable to find domain %s\n", dom_name )); + DEBUG(0,("winbindd: 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: convert_fn : Unable to update record %s\n", key2.dptr )); + DEBUG(0,("winbindd: convert_fn : conversion failed - idmap corrupt ?\n")); + return -1; + } + + if (tdb_store(idmap_tdb, data, key2, TDB_REPLACE) != 0) { + /* not good! */ + DEBUG(0,("winbindd: convert_fn : Unable to update record %s\n", data.dptr )); + DEBUG(0,("winbindd: convert_fn : conversion failed - idmap corrupt ?\n")); + return -1; + } + + tdb_delete(idmap_tdb, key); + + return 0; +} + +#if 0 +/***************************************************************************** + Make a backup copy of the old idmap just to be safe.... JRA. +*****************************************************************************/ + +static BOOL backup_old_idmap(const char *idmap_name) +{ + pstring new_name; + int outfd = -1; + SMB_OFF_T size; + struct stat st; + + pstrcpy(new_name, idmap_name); + pstrcat(new_name, ".bak"); + + DEBUG(10,("backup_old_idmap: backing up %s to %s before upgrade.\n", + idmap_name, new_name )); + + if (tdb_lockall(idmap_tdb) == -1) { + DEBUG(10,("backup_old_idmap: failed to lock %s. Error %s\n", + idmap_name, tdb_errorstr(idmap_tdb) )); + return False; + } + if ((outfd = open(new_name, O_CREAT|O_EXCL|O_RDWR, 0600)) == -1) { + DEBUG(10,("backup_old_idmap: failed to open %s. Error %s\n", + new_name, strerror(errno) )); + goto fail; + } + + if (fstat(idmap_tdb->fd, &st) == -1) { + DEBUG(10,("backup_old_idmap: failed to fstat %s. Error %s\n", + idmap_name, strerror(errno) )); + goto fail; + } + + size = (SMB_OFF_T)st.st_size; + + if (transfer_file(idmap_tdb->fd, outfd, size) != size ) { + DEBUG(10,("backup_old_idmap: failed to copy %s. Error %s\n", + idmap_name, strerror(errno) )); + goto fail; + } + + if (close(outfd) == -1) { + DEBUG(10,("backup_old_idmap: failed to close %s. Error %s\n", + idmap_name, strerror(errno) )); + outfd = -1; + goto fail; + } + tdb_unlockall(idmap_tdb); + return True; + +fail: + + if (outfd != -1) + close(outfd); + tdb_unlockall(idmap_tdb); + return False; +} +#endif + +/***************************************************************************** + Convert the idmap database from an older version. +*****************************************************************************/ + +static BOOL idmap_convert(const char *idmap_name) +{ + int32 vers = tdb_fetch_int32(idmap_tdb, "IDMAP_VERSION"); + + if (vers == IDMAP_VERSION) + return True; + +#if 0 + /* Make a backup copy before doing anything else.... */ + if (!backup_old_idmap(idmap_name)) + return False; +#endif + + if (IREV(vers) == IDMAP_VERSION) { + /* Arrggghh ! Bytereversed - make order independent ! */ + 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, server_state.uid_low) == -1) { + DEBUG(0, ("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, server_state.gid_low) == -1) { + DEBUG(0, ("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, convert_fn, NULL); + + if (tdb_store_int32(idmap_tdb, "IDMAP_VERSION", IDMAP_VERSION) == -1) { + DEBUG(0, ("idmap_convert: Unable to byteswap group hwm in idmap database\n")); + return False; + } + + return True; +} + +/***************************************************************************** + Initialise idmap database. +*****************************************************************************/ + +BOOL winbindd_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 (!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; +} + +BOOL winbindd_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 + +void winbindd_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 */ +} diff --git a/source3/nsswitch/winbindd_misc.c b/source3/nsswitch/winbindd_misc.c new file mode 100644 index 0000000000..5678bdaa5a --- /dev/null +++ b/source3/nsswitch/winbindd_misc.c @@ -0,0 +1,224 @@ +/* + 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" + +extern pstring global_myname; + +/************************************************************************ + Routine to get the trust account password for a domain +************************************************************************/ +static BOOL _get_trust_account_password(char *domain, unsigned char *ret_pwd, + time_t *pass_last_set_time) +{ + if (!secrets_fetch_trust_account_password(domain, ret_pwd, pass_last_set_time)) { + return False; + } + + return True; +} + +/* 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 (!_get_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.num_entries = NT_STATUS_V(result); + + return WINBINDD_OK; +} + +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. */ + + init_domain_list(); + + 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; + state->response.length += strlen(extra_data); + + 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; +} diff --git a/source3/nsswitch/winbindd_nss.h b/source3/nsswitch/winbindd_nss.h new file mode 100644 index 0000000000..023d72306b --- /dev/null +++ b/source3/nsswitch/winbindd_nss.h @@ -0,0 +1,224 @@ +/* + 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 4 + +/* 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, + + /* Placeholder for end of cmd list */ + + WINBINDD_NUM_CMDS +}; + +/* 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 { + 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; + } 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; + fstring domain; /* {set,get,end}{pw,gr}ent() */ +}; + +/* 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; + + struct auth_reply { + uint32 nt_status; + fstring nt_status_string; + fstring error_string; + int pam_error; + } auth; + } data; + + /* Variable length return data */ + + void *extra_data; /* getgrnam, getgrgid, getgrent */ +}; + +#endif diff --git a/source3/nsswitch/winbindd_pam.c b/source3/nsswitch/winbindd_pam.c new file mode 100644 index 0000000000..f7959c2feb --- /dev/null +++ b/source3/nsswitch/winbindd_pam.c @@ -0,0 +1,276 @@ +/* + 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" + +/* 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; + int passlen; + 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; + + extern pstring global_myname; + + DEBUG(3, ("[%5d]: pam auth %s\n", state->pid, + state->request.data.auth.user)); + + if (!(mem_ctx = talloc_init_named("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; + } + + passlen = strlen(state->request.data.auth.pass); + + { + unsigned char local_lm_response[24]; + unsigned char local_nt_response[24]; + + generate_random_buffer(chal, 8, False); + SMBencrypt( (const uchar *)state->request.data.auth.pass, chal, local_lm_response); + + SMBNTencrypt((const uchar *)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, + global_myname, 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, nt_errstr(result)); + state->response.data.auth.pam_error = nt_status_to_pam(result); + + DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, ("Plain-text authenticaion 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; + const char *domain = NULL; + + DATA_BLOB lm_resp, nt_resp; + + extern pstring global_myname; + + DEBUG(3, ("[%5d]: pam auth crap domain: %s user: %s\n", state->pid, + state->request.data.auth_crap.domain, state->request.data.auth_crap.user)); + + if (!(mem_ctx = talloc_init_named("winbind pam auth crap for %s", state->request.data.auth.user))) { + DEBUG(0, ("winbindd_pam_auth_crap: could not talloc_init()!\n")); + result = NT_STATUS_NO_MEMORY; + goto done; + } + + if (*state->request.data.auth_crap.domain) { + domain = talloc_strdup(mem_ctx, state->request.data.auth_crap.domain); + } else if (lp_winbind_use_default_domain()) { + domain = talloc_strdup(mem_ctx, lp_workgroup()); + } else { + DEBUG(5,("no domain specified with username (%s) - failing auth\n", state->request.data.auth.user)); + result = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + if (!domain) { + DEBUG(0,("winbindd_pam_auth_crap: talloc_strdup failed!\n")); + result = NT_STATUS_NO_MEMORY; + 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 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; + } + + 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 (error: %s)\n", nt_errstr(result))); + goto done; + } + + result = cli_netlogon_sam_network_logon(cli, mem_ctx, + state->request.data.auth_crap.user, domain, + global_myname, 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); + } + +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, ("NTLM CRAP authenticaion for user [%s]\\[%s] returned %s (PAM: %d)\n", + state->request.data.auth_crap.domain, + state->request.data.auth_crap.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); + + return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR; +} diff --git a/source3/nsswitch/winbindd_proto.h b/source3/nsswitch/winbindd_proto.h new file mode 100644 index 0000000000..f3830cd63c --- /dev/null +++ b/source3/nsswitch/winbindd_proto.h @@ -0,0 +1,133 @@ +#ifndef _WINBINDD_PROTO_H_ +#define _WINBINDD_PROTO_H_ + +/* This file is automatically generated with "make proto". DO NOT EDIT */ + + +/* The following definitions come from nsswitch/winbindd.c */ + +int main(int argc, char **argv); + +/* The following definitions come from nsswitch/winbindd_ads.c */ + +ADS_STATUS ads_do_search_retry(ADS_STRUCT *ads, const char *bind_path, int scope, + const char *exp, + const char **attrs, void **res); +ADS_STATUS ads_search_retry(ADS_STRUCT *ads, void **res, + const char *exp, + const char **attrs); +ADS_STATUS ads_search_retry_dn(ADS_STRUCT *ads, void **res, + const char *dn, + const char **attrs); + +/* The following definitions come from nsswitch/winbindd_cache.c */ + +void wcache_flush_cache(void); +void winbindd_check_cache_size(time_t t); +struct cache_entry *centry_start(struct winbindd_domain *domain, NTSTATUS status); + +/* The following definitions come from nsswitch/winbindd_cm.c */ + +CLI_POLICY_HND *cm_get_lsa_handle(char *domain); +CLI_POLICY_HND *cm_get_sam_handle(char *domain); +CLI_POLICY_HND *cm_get_sam_dom_handle(char *domain, DOM_SID *domain_sid); +CLI_POLICY_HND *cm_get_sam_user_handle(char *domain, DOM_SID *domain_sid, + uint32 user_rid); +CLI_POLICY_HND *cm_get_sam_group_handle(char *domain, DOM_SID *domain_sid, + uint32 group_rid); +NTSTATUS cm_get_netlogon_cli(char *domain, unsigned char *trust_passwd, + struct cli_state **cli); +void winbindd_cm_status(void); + +/* The following definitions come from nsswitch/winbindd_group.c */ + +enum winbindd_result winbindd_getgrnam(struct winbindd_cli_state *state); +enum winbindd_result winbindd_getgrgid(struct winbindd_cli_state *state); +enum winbindd_result winbindd_setgrent(struct winbindd_cli_state *state); +enum winbindd_result winbindd_endgrent(struct winbindd_cli_state *state); +enum winbindd_result winbindd_getgrent(struct winbindd_cli_state *state); +enum winbindd_result winbindd_list_groups(struct winbindd_cli_state *state); +enum winbindd_result winbindd_getgroups(struct winbindd_cli_state *state); + +/* The following definitions come from nsswitch/winbindd_idmap.c */ + +BOOL winbindd_idmap_get_uid_from_sid(DOM_SID *sid, uid_t *uid); +BOOL winbindd_idmap_get_gid_from_sid(DOM_SID *sid, gid_t *gid); +BOOL winbindd_idmap_get_uid_from_rid(const char *dom_name, uint32 rid, uid_t *uid); +BOOL winbindd_idmap_get_gid_from_rid(const char *dom_name, uint32 rid, gid_t *gid); +BOOL get_sid_from_id(int id, DOM_SID *sid, BOOL isgroup); +BOOL winbindd_idmap_get_sid_from_uid(uid_t uid, DOM_SID *sid); +BOOL winbindd_idmap_get_sid_from_gid(gid_t gid, DOM_SID *sid); +BOOL winbindd_idmap_get_rid_from_uid(uid_t uid, uint32 *user_rid, + struct winbindd_domain **domain); +BOOL winbindd_idmap_get_rid_from_gid(gid_t gid, uint32 *group_rid, + struct winbindd_domain **domain); +BOOL winbindd_idmap_init(void); +BOOL winbindd_idmap_close(void); +void winbindd_idmap_status(void); + +/* The following definitions come from nsswitch/winbindd_misc.c */ + +enum winbindd_result winbindd_check_machine_acct(struct winbindd_cli_state *state); +enum winbindd_result winbindd_list_trusted_domains(struct winbindd_cli_state + *state); +enum winbindd_result winbindd_show_sequence(struct winbindd_cli_state *state); +enum winbindd_result winbindd_ping(struct winbindd_cli_state + *state); +enum winbindd_result winbindd_info(struct winbindd_cli_state *state); +enum winbindd_result winbindd_interface_version(struct winbindd_cli_state *state); +enum winbindd_result winbindd_domain_name(struct winbindd_cli_state *state); + +/* The following definitions come from nsswitch/winbindd_pam.c */ + +enum winbindd_result winbindd_pam_auth(struct winbindd_cli_state *state) ; +enum winbindd_result winbindd_pam_auth_crap(struct winbindd_cli_state *state) ; +enum winbindd_result winbindd_pam_chauthtok(struct winbindd_cli_state *state); + +/* The following definitions come from nsswitch/winbindd_rpc.c */ + + +/* The following definitions come from nsswitch/winbindd_sid.c */ + +enum winbindd_result winbindd_lookupsid(struct winbindd_cli_state *state); +enum winbindd_result winbindd_lookupname(struct winbindd_cli_state *state); +enum winbindd_result winbindd_sid_to_uid(struct winbindd_cli_state *state); +enum winbindd_result winbindd_sid_to_gid(struct winbindd_cli_state *state); +enum winbindd_result winbindd_uid_to_sid(struct winbindd_cli_state *state); +enum winbindd_result winbindd_gid_to_sid(struct winbindd_cli_state *state); + +/* The following definitions come from nsswitch/winbindd_user.c */ + +enum winbindd_result winbindd_getpwnam(struct winbindd_cli_state *state) ; +enum winbindd_result winbindd_getpwuid(struct winbindd_cli_state *state); +enum winbindd_result winbindd_setpwent(struct winbindd_cli_state *state); +enum winbindd_result winbindd_endpwent(struct winbindd_cli_state *state); +enum winbindd_result winbindd_getpwent(struct winbindd_cli_state *state); +enum winbindd_result winbindd_list_users(struct winbindd_cli_state *state); + +/* The following definitions come from nsswitch/winbindd_util.c */ + +struct winbindd_domain *domain_list(void); +void free_domain_list(void); +BOOL init_domain_list(void); +struct winbindd_domain *find_domain_from_name(const char *domain_name); +struct winbindd_domain *find_domain_from_sid(DOM_SID *sid); +BOOL winbindd_lookup_sid_by_name(struct winbindd_domain *domain, + const char *name, DOM_SID *sid, + enum SID_NAME_USE *type); +BOOL winbindd_lookup_name_by_sid(DOM_SID *sid, + fstring dom_name, + fstring name, + enum SID_NAME_USE *type); +void free_getent_state(struct getent_state *state); +BOOL winbindd_param_init(void); +BOOL check_domain_env(char *domain_env, char *domain); +BOOL parse_domain_user(const char *domuser, fstring domain, fstring user); +void fill_domain_username(fstring name, const char *domain, const char *user); + +/* The following definitions come from nsswitch/winbindd_wins.c */ + +enum winbindd_result winbindd_wins_byip(struct winbindd_cli_state *state); +enum winbindd_result winbindd_wins_byname(struct winbindd_cli_state *state); + +#endif /* _WINBINDD_PROTO_H_ */ diff --git a/source3/nsswitch/winbindd_rpc.c b/source3/nsswitch/winbindd_rpc.c new file mode 100644 index 0000000000..5af42ee041 --- /dev/null +++ b/source3/nsswitch/winbindd_rpc.c @@ -0,0 +1,612 @@ +/* + Unix SMB/CIFS implementation. + + Winbind rpc backend functions + + Copyright (C) Tim Potter 2000-2001 + 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" + +/* 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; + int i; + + *num_entries = 0; + *info = NULL; + + /* 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); + + if (!NT_STATUS_IS_OK(result)) + goto done; + + got_dom_pol = True; + + i = 0; + do { + SAM_DISPINFO_CTR ctr; + SAM_DISPINFO_1 info1; + uint32 count = 0, start=i; + int j; + TALLOC_CTX *ctx2; + + ctr.sam.info1 = &info1; + + ctx2 = talloc_init_named("winbindd dispinfo"); + if (!ctx2) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + /* Query display info level 1 */ + result = cli_samr_query_dispinfo(hnd->cli, ctx2, + &dom_pol, &start, 1, + &count, 0xFFFF, &ctr); + + if (!NT_STATUS_IS_OK(result) && + !NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) break; + + (*num_entries) += count; + + /* now map the result into the WINBIND_USERINFO structure */ + (*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<count;i++, j++) { + (*info)[i].acct_name = unistr2_tdup(mem_ctx, &info1.str[j].uni_acct_name); + (*info)[i].full_name = unistr2_tdup(mem_ctx, &info1.str[j].uni_full_name); + (*info)[i].user_rid = info1.sam[j].rid_user; + /* 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_rid = 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; + + *num_entries = 0; + *info = NULL; + + 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); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + do { + struct acct_info *info2 = NULL; + uint32 count = 0, start = *num_entries; + TALLOC_CTX *mem_ctx2; + + mem_ctx2 = talloc_init_named("enum_dom_groups[rpc]"); + + 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; +} + +/* convert a single name to a sid in a domain */ +static NTSTATUS name_to_sid(struct winbindd_domain *domain, + const char *name, + DOM_SID *sid, + enum SID_NAME_USE *type) +{ + TALLOC_CTX *mem_ctx; + CLI_POLICY_HND *hnd; + NTSTATUS status; + DOM_SID *sids = NULL; + uint32 *types = NULL; + int num_sids; + const char *full_name; + + if (!(mem_ctx = talloc_init_named("name_to_sid[rpc] for [%s]\\[%s]", domain->name, name))) { + DEBUG(0, ("talloc_init failed!\n")); + return NT_STATUS_NO_MEMORY; + } + + if (!(hnd = cm_get_lsa_handle(domain->name))) { + talloc_destroy(mem_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + full_name = talloc_asprintf(mem_ctx, "%s\\%s", domain->name, name); + + if (!full_name) { + DEBUG(0, ("talloc_asprintf failed!\n")); + talloc_destroy(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + status = cli_lsa_lookup_names(hnd->cli, mem_ctx, &hnd->pol, 1, + &full_name, &sids, &types, &num_sids); + + /* Return rid and type if lookup successful */ + if (NT_STATUS_IS_OK(status)) { + sid_copy(sid, &sids[0]); + *type = types[0]; + } + + talloc_destroy(mem_ctx); + 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; + int num_names; + NTSTATUS status; + + 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, + &num_names); + + 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, + uint32 user_rid, + WINBIND_USERINFO *user_info) +{ + CLI_POLICY_HND *hnd; + NTSTATUS result; + POLICY_HND dom_pol, user_pol; + BOOL got_dom_pol = False, got_user_pol = False; + SAM_USERINFO_CTR *ctr; + + /* 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); + + 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); + + cli_samr_close(hnd->cli, mem_ctx, &user_pol); + got_user_pol = False; + + user_info->group_rid = 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, + uint32 user_rid, + uint32 *num_groups, uint32 **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; + int i; + + *num_groups = 0; + + /* First try cached universal groups from logon */ + *user_gids = uni_group_cache_fetch(&domain->sid, user_rid, mem_ctx, num_groups); + if((*num_groups > 0) && *user_gids) { + return NT_STATUS_OK; + } else { + *user_gids = NULL; + *num_groups = 0; + } + + /* 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); + + 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, + 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)); + for (i=0;i<(*num_groups);i++) { + (*user_gids)[i] = 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, + uint32 group_rid, uint32 *num_names, + uint32 **rid_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; + + *num_names = 0; + + /* 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); + + 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)); + + 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; + + *seq = DOM_SEQUENCE_NONE; + + if (!(mem_ctx = talloc_init_named("sequence_number[rpc]"))) + return NT_STATUS_NO_MEMORY; + + /* 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); + + 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, + DOM_SID **dom_sids) +{ + CLI_POLICY_HND *hnd; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + uint32 enum_ctx = 0; + + *num_domains = 0; + + 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); +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; + + if (!(mem_ctx = talloc_init_named("domain_sid[rpc]"))) + return NT_STATUS_NO_MEMORY; + + /* 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); + +done: + talloc_destroy(mem_ctx); + return status; +} + +/* the rpc backend methods are exposed via this structure */ +struct winbindd_methods msrpc_methods = { + False, + query_user_list, + enum_dom_groups, + name_to_sid, + sid_to_name, + query_user, + lookup_usergroups, + lookup_groupmem, + sequence_number, + trusted_domains, + domain_sid +}; diff --git a/source3/nsswitch/winbindd_sid.c b/source3/nsswitch/winbindd_sid.c new file mode 100644 index 0000000000..a41bf75916 --- /dev/null +++ b/source3/nsswitch/winbindd_sid.c @@ -0,0 +1,216 @@ +/* + 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" +#include "sids.h" + +/* 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; + + 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; +} + +/* Convert a sid to a string */ + +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; + + 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; + + 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; + + 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/source3/nsswitch/winbindd_user.c b/source3/nsswitch/winbindd_user.c new file mode 100644 index 0000000000..d89717ad47 --- /dev/null +++ b/source3/nsswitch/winbindd_user.c @@ -0,0 +1,615 @@ +/* + 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" + +/* Fill a pwent structure with information we have obtained */ + +static BOOL winbindd_fill_pwent(char *dom_name, char *user_name, + uint32 user_rid, uint32 group_rid, + char *full_name, struct winbindd_pw *pw) +{ + extern userdom_struct current_user_info; + fstring output_username; + pstring homedir; + + if (!pw || !dom_name || !user_name) + return False; + + /* Resolve the uid number */ + + if (!winbindd_idmap_get_uid_from_rid(dom_name, user_rid, + &pw->pw_uid)) { + DEBUG(1, ("error getting user id for rid %d\n", user_rid)); + return False; + } + + /* Resolve the gid number */ + + if (!winbindd_idmap_get_gid_from_rid(dom_name, group_rid, + &pw->pw_gid)) { + DEBUG(1, ("error getting group id for rid %d\n", group_rid)); + 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); + 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) +{ + uint32 user_rid; + 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; + + 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_named("winbindd_getpwnam([%s]\\[%s])", + name_domain, name_user))) { + DEBUG(1, ("out of memory\n")); + return WINBINDD_ERROR; + } + + sid_split_rid(&user_sid, &user_rid); + + status = domain->methods->query_user(domain, mem_ctx, user_rid, + &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_rid, user_info.group_rid, + 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; + uint32 user_rid; + 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_rid_from_uid(state->request.data.uid, + &user_rid, &domain)) { + DEBUG(1, ("could not convert uid %d to rid\n", + state->request.data.uid)); + return WINBINDD_ERROR; + } + + /* Get name and name type from rid */ + + sid_copy(&user_sid, &domain->sid); + sid_append_rid(&user_sid, user_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; + } + + /* Get some user info */ + + if (!(mem_ctx = talloc_init_named("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_rid, + &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_rid(domain->name, user_info.group_rid, &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_rid, user_info.group_rid, + 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; + + /* + * Skip domains other than WINBINDD_DOMAIN environment + * variable. + */ + + if ((strcmp(state->request.domain, "") != 0) && + !check_domain_env(state->request.domain, + domain->name)) + continue; + + /* 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; + int i; + + if (ent->num_sam_entries) + return False; + + if (!(mem_ctx = talloc_init_named("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 */ + name_list[ent->num_sam_entries+i].user_rid = info[i].user_rid; + name_list[ent->num_sam_entries+i].group_rid = info[i].group_rid; + } + + 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_rid, + name_list[ent->sam_entry_index].group_rid, + 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_named("winbindd_list_users"))) + return WINBINDD_ERROR; + + /* Enumerate over trusted domains */ + + for (domain = domain_list(); domain; domain = domain->next) { + NTSTATUS status; + struct winbindd_methods *methods; + int i; + + /* Skip domains other than WINBINDD_DOMAIN environment + variable */ + + if ((strcmp(state->request.domain, "") != 0) && + !check_domain_env(state->request.domain, domain->name)) + continue; + + 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/source3/nsswitch/winbindd_util.c b/source3/nsswitch/winbindd_util.c new file mode 100644 index 0000000000..06804b3b43 --- /dev/null +++ b/source3/nsswitch/winbindd_util.c @@ -0,0 +1,393 @@ +/* + 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" +#include "sids.h" + +/** + * @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(char *domain_name, + struct winbindd_methods *methods) +{ + 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 (strcmp(domain_name, domain->name) == 0) { + DEBUG(3, ("domain %s already in domain list\n", + domain_name)); + return domain; + } + } + + /* Create new domain entry */ + + if ((domain = (struct winbindd_domain *) + malloc(sizeof(*domain))) == NULL) + return NULL; + + /* Fill in fields */ + + ZERO_STRUCTP(domain); + + fstrcpy(domain->name, domain_name); + domain->methods = methods; + domain->sequence_number = DOM_SEQUENCE_NONE; + domain->last_seq_check = 0; + + /* Link to domain list */ + + DLIST_ADD(_domain_list, domain); + + return domain; +} + +/* Look up global info for the winbind daemon */ + +BOOL init_domain_list(void) +{ + NTSTATUS result; + TALLOC_CTX *mem_ctx; + extern struct winbindd_methods cache_methods; + struct winbindd_domain *domain; + DOM_SID *dom_sids; + char **names; + int num_domains = 0; + + if (!(mem_ctx = talloc_init_named("init_domain_list"))) + return False; + + /* Free existing list */ + + free_domain_list(); + + /* Add ourselves as the first entry */ + + domain = add_trusted_domain(lp_workgroup(), &cache_methods); + + /* Now we *must* get the domain sid for our primary domain. Go into + a holding pattern until that is available */ + + result = cache_methods.domain_sid(domain, &domain->sid); + while (!NT_STATUS_IS_OK(result)) { + sleep(10); + DEBUG(1,("Retrying startup domain sid fetch for %s\n", + domain->name)); + result = cache_methods.domain_sid(domain, &domain->sid); + } + + DEBUG(1,("Added domain %s (%s)\n", + domain->name, + sid_string_static(&domain->sid))); + + DEBUG(1, ("getting trusted domain list\n")); + + result = cache_methods.trusted_domains(domain, mem_ctx, &num_domains, + &names, &dom_sids); + + /* Add each domain to the trusted domain list */ + if (NT_STATUS_IS_OK(result)) { + int i; + for(i = 0; i < num_domains; i++) { + domain = add_trusted_domain(names[i], &cache_methods); + if (!domain) continue; + sid_copy(&domain->sid, &dom_sids[i]); + DEBUG(1,("Added domain %s (%s)\n", + domain->name, + sid_string_static(&domain->sid))); + + /* this primes the connection */ + cache_methods.domain_sid(domain, &domain->sid); + } + } + + talloc_destroy(mem_ctx); + 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) || + strequal(domain_name, domain->full_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; + + /* Don't bother with machine accounts */ + + if (name[strlen(name) - 1] == '$') + return False; + + /* Lookup name */ + result = domain->methods->name_to_sid(domain, name, sid, type); + + /* 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_named("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; + } +} + +/* Initialise trusted domain info */ + +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; + 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 */ +extern fstring global_myworkgroup; + +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, global_myworkgroup); + } 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 + global_myworkgroup + +*/ +void fill_domain_username(fstring name, const char *domain, const char *user) +{ + if(lp_winbind_use_default_domain() && + !strcmp(global_myworkgroup, domain)) { + strlcpy(name, user, sizeof(fstring)); + } else { + slprintf(name, sizeof(fstring) - 1, "%s%s%s", + domain, lp_winbind_separator(), + user); + } +} diff --git a/source3/nsswitch/winbindd_wins.c b/source3/nsswitch/winbindd_wins.c new file mode 100644 index 0000000000..af624170eb --- /dev/null +++ b/source3/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" + +/* 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; + struct in_addr p; + int j; + + *count = 0; + + fd = wins_lookup_open_socket_in(); + if (fd == -1) + return NULL; + + p = wins_srv_ip(); + if( !is_zero_ip(p) ) { + ret = name_query(fd,name,0x20,False,True, p, count); + goto out; + } + + if (lp_wins_support()) { + /* we are our own WINS server */ + ret = name_query(fd,name,0x20,False,True, *interpret_addr2("127.0.0.1"), count); + goto out; + } + + /* 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); + if (ret) break; + } + + out: + + 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; + + 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; + + 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/source3/nsswitch/wins.c b/source3/nsswitch/wins.c new file mode 100644 index 0000000000..2133f817d1 --- /dev/null +++ b/source3/nsswitch/wins.c @@ -0,0 +1,323 @@ +/* + 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. + +*/ + +#define NO_SYSLOG + +#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; + + /* needed for lp_xx() functions */ + charset_initialise(); + + TimeInit(); + setup_logging("nss_wins",False); + lp_load(dyn_CONFIGFILE,True,False,False); + load_interfaces(); + codepage_initialise(lp_client_code_page()); +} + +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; + struct in_addr *ret = NULL; + struct in_addr p; + int j; + + if (!initialised) { + nss_wins_init(); + } + + *count = 0; + + fd = wins_lookup_open_socket_in(); + if (fd == -1) + return NULL; + + p = wins_srv_ip(); + if( !is_zero_ip(p) ) { + ret = name_query(fd,name,0x20,False,True, p, count); + goto out; + } + + if (lp_wins_support()) { + /* we are our own WINS server */ + ret = name_query(fd,name,0x20,False,True, *interpret_addr2("127.0.0.1"), count); + goto out; + } + + /* 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); + 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; +} +#endif + |