/* 
   Unix SMB/Netbios implementation.
   Version 2.0

   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"

/* 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);


/* 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. */

#ifdef strchr /* Aargh! This points at multibyte_strchr(). )-: */
#undef strchr
#endif

static char *last_ptr = NULL;

BOOL next_token(char **ptr, char *buff, char *sep, size_t bufsize)
{
    char *s;
    BOOL quoted;
    size_t len=1;
    
    if (!ptr) ptr = &last_ptr;
    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;
    last_ptr = *ptr;
  
    return(True);
}


/* handle simple types of requests */
static enum nss_status generic_request(int req_type, 
				       struct winbindd_request *request,
				       struct winbindd_response *response)
{
	struct winbindd_request lrequest;
	struct winbindd_response lresponse;

	if (!response) response = &lresponse;
	if (!request) 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;
	}
	
	/* Wait for reply */
	if (read_reply(response) == -1) {
		return NSS_STATUS_UNAVAIL;
	}

	/* Copy reply data from socket */
	if (response->result != WINBINDD_OK) {
		return NSS_STATUS_NOTFOUND;
	}
	
	return NSS_STATUS_SUCCESS;
}

/* 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 errno = ERANGE and NSS_STATUS_TRYAGAIN if we run out of
   memory. */

static enum nss_status fill_pwent(struct passwd *result,
				  struct winbindd_response *response,
				  char **buffer, int *buflen, int *errnop)
{
    struct winbindd_pw *pw = &response->data.pw;

    /* User name */

    if ((result->pw_name = 
         get_static(buffer, buflen, strlen(pw->pw_name) + 1)) == NULL) {

        /* Out of memory */

        *errnop = ERANGE;
        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 */

        *errnop = ERANGE;
        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 */

        *errnop = ERANGE;
        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 */

        *errnop = ERANGE;
        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 */

        *errnop = ERANGE;
        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 errno = ERANGE and NSS_STATUS_TRYAGAIN if we run out of
   memory. */

static int fill_grent(struct group *result, 
                      struct winbindd_response *response,
                      char **buffer, int *buflen, int *errnop)
{
    struct winbindd_gr *gr = &response->data.gr;
    fstring name;
    int i;

    /* Group name */

    if ((result->gr_name =
         get_static(buffer, buflen, strlen(gr->gr_name) + 1)) == NULL) {

        /* Out of memory */

        *errnop = ERANGE;
        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 */

        *errnop = ERANGE;
        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) || !response->extra_data) {
        gr->num_gr_mem = 0;
    }

    if ((result->gr_mem = 
         (char **)get_static(buffer, buflen, (gr->num_gr_mem + 1) * 
                             sizeof(char *))) == NULL) {

        /* Out of memory */

        *errnop = ERANGE;
        return NSS_STATUS_TRYAGAIN;
    }

    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(&response->extra_data, name, ",", sizeof(fstring))) {
        
        /* Allocate space for member */
        
        if (((result->gr_mem)[i] = 
             get_static(buffer, buflen, strlen(name) + 1)) == NULL) {
            
            /* Out of memory */
            
            *errnop = ERANGE;
            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
 */

/* Rewind "file pointer" to start of ntdom password database */

enum nss_status
_nss_winbind_setpwent(void)
{
	return generic_request(WINBINDD_SETPWENT, NULL, NULL);
}

/* Close ntdom password database "file pointer" */

enum nss_status
_nss_winbind_endpwent(void)
{
	return generic_request(WINBINDD_ENDPWENT, NULL, NULL);
}

/* Fetch the next password entry from ntdom password database */

enum nss_status
_nss_winbind_getpwent_r(struct passwd *result, char *buffer, 
                      size_t buflen, int *errnop)
{
	enum nss_status ret;
	struct winbindd_response response;

	ret = generic_request(WINBINDD_GETPWENT, NULL, &response);
	if (ret != NSS_STATUS_SUCCESS) return ret;

	return fill_pwent(result, &response, &buffer, &buflen, errnop);
}

/* Return passwd struct from uid */

enum nss_status
_nss_winbind_getpwuid_r(uid_t uid, struct passwd *result, char *buffer,
                      size_t buflen, int *errnop)
{
	enum nss_status ret;
	struct winbindd_response response;
	struct winbindd_request request;

	request.data.uid = uid;

	ret = generic_request(WINBINDD_GETPWNAM_FROM_UID, &request, &response);
	if (ret != NSS_STATUS_SUCCESS) return ret;

	return fill_pwent(result, &response, &buffer, &buflen, errnop);
}

/* Return passwd struct from username */

enum nss_status
_nss_winbind_getpwnam_r(const char *name, struct passwd *result, char *buffer,
                      size_t buflen, int *errnop)
{
	enum nss_status ret;
	struct winbindd_response response;
	struct winbindd_request request;

	strncpy(request.data.username, name, sizeof(request.data.username) - 1);
	request.data.username[sizeof(request.data.username) - 1] = '\0';

	ret = generic_request(WINBINDD_GETPWNAM_FROM_USER, &request, &response);
	if (ret != NSS_STATUS_SUCCESS) return ret;

	return fill_pwent(result, &response, &buffer, &buflen, errnop);
}

/*
 * NSS group functions
 */

/* Rewind "file pointer" to start of ntdom group database */

enum nss_status
_nss_winbind_setgrent(void)
{
	return generic_request(WINBINDD_SETGRENT, NULL, NULL);
}

/* Close "file pointer" for ntdom group database */

enum nss_status
_nss_winbind_endgrent(void)
{
	return generic_request(WINBINDD_ENDGRENT, NULL, NULL);
}



/* Get next entry from ntdom group database */

enum nss_status
_nss_winbind_getgrent_r(struct group *result,
                      char *buffer, size_t buflen, int *errnop)
{
	enum nss_status ret;
	struct winbindd_response response;

	ret = generic_request(WINBINDD_GETGRENT, NULL, &response);
	if (ret != NSS_STATUS_SUCCESS) return ret;

	return fill_grent(result, &response, &buffer, &buflen, errnop);
}

/* Return group struct from group name */

enum nss_status
_nss_winbind_getgrnam_r(const char *name,
                      struct group *result, char *buffer,
                      size_t buflen, int *errnop)
{
	enum nss_status ret;
	struct winbindd_response response;
	struct winbindd_request request;

	strncpy(request.data.groupname, name, sizeof(request.data.groupname));
	request.data.groupname[sizeof(request.data.groupname) - 1] = '\0';

	ret = generic_request(WINBINDD_GETGRNAM_FROM_GROUP, &request, &response);
	if (ret != NSS_STATUS_SUCCESS) return ret;

	return fill_grent(result, &response, &buffer, &buflen, errnop);
}

/* Return group struct from gid */

enum nss_status
_nss_winbind_getgrgid_r(gid_t gid,
                      struct group *result, char *buffer,
                      size_t buflen, int *errnop)
{
	enum nss_status ret;
	struct winbindd_response response;
	struct winbindd_request request;

	request.data.gid = gid;

	ret = generic_request(WINBINDD_GETGRNAM_FROM_GID, &request, &response);
	if (ret != NSS_STATUS_SUCCESS) return ret;

	return fill_grent(result, &response, &buffer, &buflen, errnop);
}