/*
 * Copyright (C) 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/.
 *
 * See the COPYRIGHT file distributed with this work for additional
 * information regarding copyright ownership.
 */

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

#define CHECK(op) \
	do { result = (op); if (result != ISC_R_SUCCESS) { goto cleanup; } } while (0)

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];
	isc_result_t result;

	*totalGroups = 0;

	retlen = mbstowcs(user, username, MAX_NAME_LENGTH);
	if (retlen == (size_t) (-1)) {
		return (ISC_R_FAILURE);
	}

	/*
	 * 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) {
				CHECK(ISC_R_NOMEMORY);
			}

			retlen = wcstombs(GroupList[*totalGroups],
					  pTmpLBuf->lgrui0_name, retlen);
			if (retlen == (size_t) (-1)) {
				free(GroupList[*totalGroups]);
				CHECK(ISC_R_FAILURE);
			}
			GroupList[*totalGroups][retlen] = '\0';
			if (strcmp(GroupList[*totalGroups], "None") == 0) {
				free(GroupList[*totalGroups]);
			} else {
				(*totalGroups)++;
			}
			pTmpLBuf++;
		}
	}
	/* Free the allocated memory. */
	/* cppcheck-suppress duplicateCondition */
	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) {
				CHECK(ISC_R_NOMEMORY);
			}

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

	return (ISC_R_SUCCESS);

 cleanup:
	while (--(*totalGroups) > 0) {
		free(GroupList[*totalGroups]);
	}
	return (result);
}
