/*
 * Copyright (C) 2001, 2004, 2006, 2007, 2009, 2013, 2016  Internet Systems Consortium, Inc. ("ISC")
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/* $Id: ntgroups.c,v 1.12 2009/09/29 23:48:04 tbox Exp $ */

/*
 * The NT Groups have two groups that are not well documented and are
 * not normally seen: None and Everyone.  A user account belongs to
 * any number of groups, but if it is not a member of any group then
 * it is a member of the None Group. The None group is not listed
 * anywhere. You cannot remove an account from the none group except
 * by making it a member of some other group, The second group is the
 * Everyone group.  All accounts, no matter how many groups that they
 * belong to, also belong to the Everyone group. You cannot remove an
 * account from the Everyone group.
 */

#ifndef UNICODE
#define UNICODE
#endif /* UNICODE */

/*
 * Silence warnings.
 */
#define _CRT_SECURE_NO_DEPRECATE 1

#include <windows.h>
#include <assert.h>
#include <lm.h>

#include <isc/ntgroups.h>
#include <isc/result.h>

#define MAX_NAME_LENGTH 256

isc_result_t
isc_ntsecurity_getaccountgroups(char *username, char **GroupList,
				unsigned int maxgroups,
				unsigned int *totalGroups) {
	LPGROUP_USERS_INFO_0 pTmpBuf;
	LPLOCALGROUP_USERS_INFO_0 pTmpLBuf;
	DWORD i;
	LPLOCALGROUP_USERS_INFO_0 pBuf = NULL;
	LPGROUP_USERS_INFO_0 pgrpBuf = NULL;
	DWORD dwLevel = 0;
	DWORD dwFlags = LG_INCLUDE_INDIRECT;
	DWORD dwPrefMaxLen = MAX_PREFERRED_LENGTH;
	DWORD dwEntriesRead = 0;
	DWORD dwTotalEntries = 0;
	NET_API_STATUS nStatus;
	size_t retlen;
	wchar_t user[MAX_NAME_LENGTH];

	retlen = mbstowcs(user, username, MAX_NAME_LENGTH);

	*totalGroups = 0;
	/*
	 * Call the NetUserGetLocalGroups function
	 * specifying information level 0.
	 *
	 * The LG_INCLUDE_INDIRECT flag specifies that the
	 * function should also return the names of the local
	 * groups in which the user is indirectly a member.
	 */
	nStatus = NetUserGetLocalGroups(NULL,
				   user,
				   dwLevel,
				   dwFlags,
				   (LPBYTE *) &pBuf,
				   dwPrefMaxLen,
				   &dwEntriesRead,
				   &dwTotalEntries);
	/*
	 * See if the call succeeds,
	 */
	if (nStatus != NERR_Success) {
		if (nStatus == ERROR_ACCESS_DENIED)
			return (ISC_R_NOPERM);
		if (nStatus == ERROR_MORE_DATA)
			return (ISC_R_NOSPACE);
		if (nStatus == NERR_UserNotFound)
			dwEntriesRead = 0;
	}

	if (pBuf != NULL) {
		pTmpLBuf = pBuf;
		/*
		 * Loop through the entries
		 */
		 for (i = 0;
		     (i < dwEntriesRead && *totalGroups < maxgroups); i++) {
			assert(pTmpLBuf != NULL);
			if (pTmpLBuf == NULL)
				break;
			retlen = wcslen(pTmpLBuf->lgrui0_name);
			GroupList[*totalGroups] = (char *) malloc(retlen +1);
			if (GroupList[*totalGroups] == NULL)
				return (ISC_R_NOMEMORY);

			retlen = wcstombs(GroupList[*totalGroups],
				 pTmpLBuf->lgrui0_name, retlen);
			GroupList[*totalGroups][retlen] = '\0';
			if (strcmp(GroupList[*totalGroups], "None") == 0)
				free(GroupList[*totalGroups]);
			else
				(*totalGroups)++;
			pTmpLBuf++;
		}
	}
	/* Free the allocated memory. */
	if (pBuf != NULL)
		NetApiBufferFree(pBuf);


	/*
	 * Call the NetUserGetGroups function, specifying level 0.
	 */
	nStatus = NetUserGetGroups(NULL,
			      user,
			      dwLevel,
			      (LPBYTE*)&pgrpBuf,
			      dwPrefMaxLen,
			      &dwEntriesRead,
			      &dwTotalEntries);
	/*
	 * See if the call succeeds,
	 */
	if (nStatus != NERR_Success) {
		if (nStatus == ERROR_ACCESS_DENIED)
			return (ISC_R_NOPERM);
		if (nStatus == ERROR_MORE_DATA)
			return (ISC_R_NOSPACE);
		if (nStatus == NERR_UserNotFound)
			dwEntriesRead = 0;
	}

	if (pgrpBuf != NULL) {
		pTmpBuf = pgrpBuf;
		/*
		 * Loop through the entries
		 */
		 for (i = 0;
		     (i < dwEntriesRead && *totalGroups < maxgroups); i++) {
			assert(pTmpBuf != NULL);

			if (pTmpBuf == NULL)
				break;
			retlen = wcslen(pTmpBuf->grui0_name);
			GroupList[*totalGroups] = (char *) malloc(retlen +1);
			if (GroupList[*totalGroups] == NULL)
				return (ISC_R_NOMEMORY);

			retlen = wcstombs(GroupList[*totalGroups],
				 pTmpBuf->grui0_name, retlen);
			GroupList[*totalGroups][retlen] = '\0';
			if (strcmp(GroupList[*totalGroups], "None") == 0)
				free(GroupList[*totalGroups]);
			else
				(*totalGroups)++;
			pTmpBuf++;
		}
	}
	/*
	 * Free the allocated memory.
	 */
	if (pgrpBuf != NULL)
		NetApiBufferFree(pgrpBuf);

	return (ISC_R_SUCCESS);
}
