The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include <windows.h>
#ifndef __CYGWIN__
#   include <winsock.h>
#endif
#include <lmcons.h>    /* LAN Manager common definitions */
#include <lmerr.h>    /* LAN Manager network error definitions */
#include <lmUseFlg.h>
#include <lmAccess.h>
#include <lmAPIBuf.h>
#include <lmwksta.h>
#undef LPTSTR
#define LPTSTR LPWSTR
#include <lmServer.h>
#undef LPTSTR

#ifndef __SDDL_H__
WINADVAPI BOOL WINAPI ConvertSidToStringSidA(PSID, LPSTR*);
#endif

/* old versions of lmaccess.h don't include these Windows 2003 additions */
typedef struct {
    LPWSTR   usri4_name;
    LPWSTR   usri4_password;
    DWORD    usri4_password_age;
    DWORD    usri4_priv;
    LPWSTR   usri4_home_dir;
    LPWSTR   usri4_comment;
    DWORD    usri4_flags;
    LPWSTR   usri4_script_path;
    DWORD    usri4_auth_flags;
    LPWSTR   usri4_full_name;
    LPWSTR   usri4_usr_comment;
    LPWSTR   usri4_parms;
    LPWSTR   usri4_workstations;
    DWORD    usri4_last_logon;
    DWORD    usri4_last_logoff;
    DWORD    usri4_acct_expires;
    DWORD    usri4_max_storage;
    DWORD    usri4_units_per_week;
    PBYTE    usri4_logon_hours;
    DWORD    usri4_bad_pw_count;
    DWORD    usri4_num_logons;
    LPWSTR   usri4_logon_server;
    DWORD    usri4_country_code;
    DWORD    usri4_code_page;
    PSID     usri4_user_sid;
    DWORD    usri4_primary_group_id;
    LPWSTR   usri4_profile;
    LPWSTR   usri4_home_dir_drive;
    DWORD    usri4_password_expired;
} MY_USER_INFO_4, *PMY_USER_INFO_4;

typedef struct {
    LPWSTR   usri23_name;
    LPWSTR   usri23_full_name;
    LPWSTR   usri23_comment;
    DWORD    usri23_flags;
    PSID     usri23_user_sid;
} MY_USER_INFO_23, *PMY_USER_INFO_23;

typedef struct {
    LPWSTR   grpi3_name;
    LPWSTR   grpi3_comment;
    PSID     grpi3_group_sid;
    DWORD    grpi3_attributes;
} MY_GROUP_INFO_3, *PMY_GROUP_INFO_3;

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

#ifndef _WIN64
#  define DWORD_PTR DWORD
#endif

static int
not_here(char *s)
{
    croak("%s not implemented on this architecture", s);
    return -1;
}

static double
constant(char *name, int arg)
{
    errno = 0;
    switch (*name) {
    case 'A':
	break;
    case 'B':
	break;
    case 'C':
	break;
    case 'D':
	break;
    case 'E':
	break;
    case 'F':
	if (strEQ(name, "FILTER_TEMP_DUPLICATE_ACCOUNTS"))
#ifdef FILTER_TEMP_DUPLICATE_ACCOUNTS
	    return FILTER_TEMP_DUPLICATE_ACCOUNTS;
#else
	    goto not_there;
#endif
	if (strEQ(name, "FILTER_NORMAL_ACCOUNT"))
#ifdef FILTER_NORMAL_ACCOUNT
	    return FILTER_NORMAL_ACCOUNT;
#else
	    goto not_there;
#endif
	if (strEQ(name, "FILTER_INTERDOMAIN_TRUST_ACCOUNT"))
#ifdef FILTER_INTERDOMAIN_TRUST_ACCOUNT
	    return FILTER_INTERDOMAIN_TRUST_ACCOUNT;
#else
	    goto not_there;
#endif
	if (strEQ(name, "FILTER_WORKSTATION_TRUST_ACCOUNT"))
#ifdef FILTER_WORKSTATION_TRUST_ACCOUNT
	    return FILTER_WORKSTATION_TRUST_ACCOUNT;
#else
	    goto not_there;
#endif
	if (strEQ(name, "FILTER_SERVER_TRUST_ACCOUNT"))
#ifdef FILTER_SERVER_TRUST_ACCOUNT
	    return FILTER_SERVER_TRUST_ACCOUNT;
#else
	    goto not_there;
#endif
	break;
    case 'G':
	if (strEQ(name, "GROUP_ATTRIBUTES_PARMNUM"))
#ifdef GROUP_ATTRIBUTES_PARMNUM
	    return GROUP_ATTRIBUTES_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "GROUP_COMMENT_PARMNUM"))
#ifdef GROUP_COMMENT_PARMNUM
	    return GROUP_COMMENT_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "GROUP_NAME_PARMNUM"))
#ifdef GROUP_NAME_PARMNUM
	    return GROUP_NAME_PARMNUM;
#else
	    goto not_there;
#endif
	break;
    case 'H':
	break;
    case 'I':
	break;
    case 'J':
	break;
    case 'K':
	break;
    case 'L':
	if (strEQ(name, "LG_INCLUDE_INDIRECT"))
#ifdef LG_INCLUDE_INDIRECT
	    return LG_INCLUDE_INDIRECT;
#else
	    goto not_there;
#endif
	if (strEQ(name, "LOCALGROUP_NAME_PARMNUM"))
#ifdef LOCALGROUP_NAME_PARMNUM
	    return LOCALGROUP_NAME_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "LOCALGROUP_COMMENT_PARMNUM"))
#ifdef LOCALGROUP_COMMENT_PARMNUM
	    return LOCALGROUP_COMMENT_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "LOGON32_LOGON_BATCH"))
#ifdef LOGON32_LOGON_BATCH
	    return LOGON32_LOGON_BATCH;
#else
	    goto not_there;
#endif
	if (strEQ(name, "LOGON32_LOGON_INTERACTIVE"))
#ifdef LOGON32_LOGON_INTERACTIVE
	    return LOGON32_LOGON_INTERACTIVE;
#else
	    goto not_there;
#endif
	break;
    case 'M':
	break;
    case 'N':
	break;
    case 'O':
	break;
    case 'P':
	break;
    case 'Q':
	break;
    case 'R':
	break;
    case 'S':
	break;
    case 'T':
	break;
    case 'U':
	if (strEQ(name, "UF_TEMP_DUPLICATE_ACCOUNT"))
#ifdef UF_TEMP_DUPLICATE_ACCOUNT
	    return UF_TEMP_DUPLICATE_ACCOUNT;
#else
	    goto not_there;
#endif
	if (strEQ(name, "UF_NORMAL_ACCOUNT"))
#ifdef UF_NORMAL_ACCOUNT
	    return UF_NORMAL_ACCOUNT;
#else
	    goto not_there;
#endif
	if (strEQ(name, "UF_INTERDOMAIN_TRUST_ACCOUNT"))
#ifdef UF_INTERDOMAIN_TRUST_ACCOUNT
	    return UF_INTERDOMAIN_TRUST_ACCOUNT;
#else
	    goto not_there;
#endif
	if (strEQ(name, "UF_WORKSTATION_TRUST_ACCOUNT"))
#ifdef UF_WORKSTATION_TRUST_ACCOUNT
	    return UF_WORKSTATION_TRUST_ACCOUNT;
#else
	    goto not_there;
#endif
	if (strEQ(name, "UF_SERVER_TRUST_ACCOUNT"))
#ifdef UF_SERVER_TRUST_ACCOUNT
	    return UF_SERVER_TRUST_ACCOUNT;
#else
	    goto not_there;
#endif
	if (strEQ(name, "UF_MACHINE_ACCOUNT_MASK"))
#ifdef UF_MACHINE_ACCOUNT_MASK
	    return UF_MACHINE_ACCOUNT_MASK;
#else
	    goto not_there;
#endif
	if (strEQ(name, "UF_ACCOUNT_TYPE_MASK"))
#ifdef UF_ACCOUNT_TYPE_MASK
	    return UF_ACCOUNT_TYPE_MASK;
#else
	    goto not_there;
#endif
	if (strEQ(name, "UF_DONT_EXPIRE_PASSWD"))
#ifdef UF_DONT_EXPIRE_PASSWD
	    return UF_DONT_EXPIRE_PASSWD;
#else
	    goto not_there;
#endif
	if (strEQ(name, "UF_SETTABLE_BITS"))
#ifdef UF_SETTABLE_BITS
	    return UF_SETTABLE_BITS;
#else
	    goto not_there;
#endif
    
	if (strEQ(name, "UF_SCRIPT"))
#ifdef UF_SCRIPT
	    return UF_SCRIPT;
#else
	    goto not_there;
#endif
	if (strEQ(name, "UF_ACCOUNTDISABLE"))
#ifdef UF_ACCOUNTDISABLE
	    return UF_ACCOUNTDISABLE;
#else
	    goto not_there;
#endif
	if (strEQ(name, "UF_HOMEDIR_REQUIRED"))
#ifdef UF_HOMEDIR_REQUIRED
	    return UF_HOMEDIR_REQUIRED;
#else
	    goto not_there;
#endif
	if (strEQ(name, "UF_LOCKOUT"))
#ifdef UF_LOCKOUT
	    return UF_LOCKOUT;
#else
	    goto not_there;
#endif
	if (strEQ(name, "UF_PASSWD_NOTREQD"))
#ifdef UF_PASSWD_NOTREQD
	    return UF_PASSWD_NOTREQD;
#else
	    goto not_there;
#endif
	if (strEQ(name, "UF_PASSWD_CANT_CHANGE"))
#ifdef UF_PASSWD_CANT_CHANGE
	    return UF_PASSWD_CANT_CHANGE;
#else
	    goto not_there;
#endif
    
	if (strEQ(name, "USE_FORCE"))
#ifdef USE_FORCE
	    return USE_FORCE;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USE_LOTS_OF_FORCE"))
#ifdef USE_LOTS_OF_FORCE
	    return USE_LOTS_OF_FORCE;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USE_NOFORCE"))
#ifdef USE_NOFORCE
	    return USE_NOFORCE;
#else
	    goto not_there;
#endif
/* PRIV MASKS */
	if (strEQ(name, "USER_PRIV_MASK"))
#ifdef USER_PRIV_MASK
	    return USER_PRIV_MASK;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_PRIV_GUEST"))
#ifdef USER_PRIV_GUEST
	    return USER_PRIV_GUEST;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_PRIV_USER"))
#ifdef USER_PRIV_USER
	    return USER_PRIV_USER;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_PRIV_ADMIN"))
#ifdef USER_PRIV_ADMIN
	    return USER_PRIV_ADMIN;
#else
	    goto not_there;
#endif
/* USER_XXX_PARMNUM FIELDS */
	if (strEQ(name, "USER_NAME_PARMNUM"))
#ifdef USER_NAME_PARMNUM
	    return USER_NAME_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_PASSWORD_PARMNUM"))
#ifdef USER_PASSWORD_PARMNUM
	    return USER_PASSWORD_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_PASSWORD_AGE_PARMNUM"))
#ifdef USER_PASSWORD_AGE_PARMNUM
	    return USER_PASSWORD_AGE_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_PRIV_PARMNUM"))
#ifdef USER_PRIV_PARMNUM
	    return USER_PRIV_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_HOME_DIR_PARMNUM"))
#ifdef USER_HOME_DIR_PARMNUM
	    return USER_HOME_DIR_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_COMMENT_PARMNUM"))
#ifdef USER_COMMENT_PARMNUM
	    return USER_COMMENT_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_FLAGS_PARMNUM"))
#ifdef USER_FLAGS_PARMNUM
	    return USER_FLAGS_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_SCRIPT_PATH_PARMNUM"))
#ifdef USER_SCRIPT_PATH_PARMNUM
	    return USER_SCRIPT_PATH_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_AUTH_FLAGS_PARMNUM"))
#ifdef USER_AUTH_FLAGS_PARMNUM
	    return USER_AUTH_FLAGS_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_FULL_NAME_PARMNUM"))
#ifdef USER_FULL_NAME_PARMNUM
	    return USER_FULL_NAME_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_USR_COMMENT_PARMNUM"))
#ifdef USER_USR_COMMENT_PARMNUM
	    return USER_USR_COMMENT_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_PARMS_PARMNUM"))
#ifdef USER_PARMS_PARMNUM
	    return USER_PARMS_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_WORKSTATIONS_PARMNUM"))
#ifdef USER_WORKSTATIONS_PARMNUM
	    return USER_WORKSTATIONS_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_LAST_LOGON_PARMNUM"))
#ifdef USER_LAST_LOGON_PARMNUM
	    return USER_LAST_LOGON_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_LAST_LOGOFF_PARMNUM"))
#ifdef USER_LAST_LOGOFF_PARMNUM
	    return USER_LAST_LOGOFF_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_ACCT_EXPIRES_PARMNUM"))
#ifdef USER_ACCT_EXPIRES_PARMNUM
	    return USER_ACCT_EXPIRES_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_MAX_STORAGE_PARMNUM"))
#ifdef USER_MAX_STORAGE_PARMNUM
	    return USER_MAX_STORAGE_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_UNITS_PER_WEEK_PARMNUM"))
#ifdef USER_UNITS_PER_WEEK_PARMNUM
	    return USER_UNITS_PER_WEEK_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_LOGON_HOURS_PARMNUM"))
#ifdef USER_LOGON_HOURS_PARMNUM
	    return USER_LOGON_HOURS_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_PAD_PW_COUNT_PARMNUM"))
#ifdef USER_PAD_PW_COUNT_PARMNUM
	    return USER_PAD_PW_COUNT_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_NUM_LOGONS_PARMNUM"))
#ifdef USER_NUM_LOGONS_PARMNUM
	    return USER_NUM_LOGONS_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_LOGON_SERVER_PARMNUM"))
#ifdef USER_LOGON_SERVER_PARMNUM
	    return USER_LOGON_SERVER_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_COUNTRY_CODE_PARMNUM"))
#ifdef USER_COUNTRY_CODE_PARMNUM
	    return USER_COUNTRY_CODE_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_CODE_PAGE_PARMNUM"))
#ifdef USER_CODE_PAGE_PARMNUM
	    return USER_CODE_PAGE_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_PRIMARY_GROUP_PARMNUM"))
#ifdef USER_PRIMARY_GROUP_PARMNUM
	    return USER_PRIMARY_GROUP_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_PROFILE_PARMNUM"))
#ifdef USER_PROFILE_PARMNUM
	    return USER_PROFILE_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_PROFILE_PARMNUM"))
#ifdef USER_PROFILE_PARMNUM
	    return USER_PROFILE_PARMNUM;
#else
	    goto not_there;
#endif
	if (strEQ(name, "USER_HOME_DIR_DRIVE_PARMNUM"))
#ifdef USER_HOME_DIR_DRIVE_PARMNUM
	    return USER_HOME_DIR_DRIVE_PARMNUM;
#else
	    goto not_there;
#endif
	break;
    case 'V':
	break;
    case 'W':
	break;
    case 'X':
	break;
    case 'Y':
	break;
    case 'Z':
	break;
    }
    errno = EINVAL;
    return 0;
    
not_there:
    errno = ENOENT;
    return 0;
}

#define PREFLEN 0x20000		/* for *Enum() */

/* MultiByte 2 WideChar */

LPWSTR
MBTWC(char* name)
{
    int length;
    LPWSTR lpPtr = NULL;
    
    if (name) { // && *name != '\0') {
	length = (int)strlen(name)+1;
	Newz(0, lpPtr, length, WCHAR);
	MultiByteToWideChar(CP_ACP, 0, name, -1, lpPtr,
				length * sizeof(WCHAR));
    }
    return lpPtr;
}

/* The following MACROs assume that the following are in scope
 * hv        - HV*, the hv into/from which information is placed/extracted
 * svPtr    - temporary SV** for intermediate SV ** values
 * uiX        - LPBYTE alloc'ed appropriate to CAST
 * tmpBuf    - char * for temporary string values
 */

#define HV_GET_PV(CAST, field, name) \
    STMT_START {							\
	if ((svPtr = hv_fetch((HV*)hv, name, (I32)strlen(name), 0)) == NULL)	\
	    croak("Required argument not supplied (%s),", name);	\
	if (SvOK(*svPtr))						\
	    ((CAST)uiX)->field = MBTWC(SvPV_nolen(*svPtr));		\
	else /* fields set to "undef" pass NULL to underlying API */	\
	    ((CAST)uiX)->field = (LPWSTR)NULL;				\
    } STMT_END

#define HV_GET_IV(CAST, field, name) \
    STMT_START {							\
	if ((svPtr = hv_fetch((HV*)hv, name, (I32)strlen(name), 0)) == NULL)	\
	    croak("Required argument not supplied (%s),", name);	\
	if (!SvIOK(*svPtr))						\
	    croak("Bad data for %s, ", name);				\
	((CAST)uiX)->field = (DWORD)SvIV(*svPtr);			\
    } STMT_END

#define HV_GET_AV(CAST, field, name, n) \
    STMT_START {							\
	int i = 0;							\
	SV **svTmp, *svPtrIndirect;					\
	Newz(0, ((CAST)uiX)->field, n, BYTE);				\
	if ((svPtr = hv_fetch((HV*)hv, name, (I32)strlen(name), 0)) == NULL)	\
	    croak("Required argument not supplied (%s), ", name);	\
	if (!(SvROK(*svPtr) && (svPtrIndirect = SvRV(*svPtr))		\
		&& SvTYPE(svPtrIndirect) == SVt_PVAV))			\
	    croak("Value in logonHours should be an array reference,");	\
	while (i < n) {							\
	    if ((svTmp = av_fetch((AV*)svPtrIndirect, i, 0)) != NULL)	\
		(((CAST)uiX)->field)[i] = (BYTE)SvIV(*svTmp);		\
	    else							\
		(((CAST)uiX)->field)[i] = 0;				\
	    i++;							\
	}								\
    } STMT_END

#define uiX_INIT(t) \
    STMT_START {							\
	if (!uiX) {							\
	    Newc(0,uiX,1,t,LPBYTE);					\
	    memzero((char*)uiX,sizeof(t));				\
	}								\
    } STMT_END

LPBYTE *
allocUserInfoX(int level, HV *hv)
{
    LPBYTE	*uiX = NULL;
    SV		**svPtr;
    dTHX;

    switch (level) {
    case 3:
	uiX_INIT(USER_INFO_3);
	HV_GET_IV(PUSER_INFO_3, usri3_password_expired,     "passwordExpired");
	HV_GET_PV(PUSER_INFO_3, usri3_home_dir_drive,       "homeDirDrive");
	HV_GET_PV(PUSER_INFO_3, usri3_profile,              "profile");
	HV_GET_IV(PUSER_INFO_3, usri3_primary_group_id,     "primaryGroupId");
	HV_GET_IV(PUSER_INFO_3, usri3_user_id,              "userId");
	/* fall through to 2... */
    case 2:
	uiX_INIT(USER_INFO_2);
	HV_GET_IV(PUSER_INFO_2, usri2_code_page,            "codePage");
	HV_GET_IV(PUSER_INFO_2, usri2_country_code,         "countryCode");
	HV_GET_PV(PUSER_INFO_2, usri2_logon_server,         "logonServer");
	HV_GET_IV(PUSER_INFO_2, usri2_num_logons,           "numLogons");
	HV_GET_IV(PUSER_INFO_2, usri2_bad_pw_count,         "badPwCount");
	HV_GET_AV(PUSER_INFO_2, usri2_logon_hours,          "logonHours", 21);
	HV_GET_IV(PUSER_INFO_2, usri2_units_per_week,       "unitsPerWeek");
	HV_GET_IV(PUSER_INFO_2, usri2_max_storage,          "maxStorage");
	HV_GET_IV(PUSER_INFO_2, usri2_acct_expires,         "acctExpires");
	HV_GET_IV(PUSER_INFO_2, usri2_last_logoff,          "lastLogoff");
	HV_GET_IV(PUSER_INFO_2, usri2_last_logon,           "lastLogon");
	HV_GET_PV(PUSER_INFO_2, usri2_workstations,         "workstations");
	HV_GET_PV(PUSER_INFO_2, usri2_parms,                "parms");
	HV_GET_PV(PUSER_INFO_2, usri2_usr_comment,          "usrComment");
	HV_GET_PV(PUSER_INFO_2, usri2_full_name,            "fullName");
	HV_GET_IV(PUSER_INFO_2, usri2_auth_flags,           "authFlags");
	/* fall through to 1... */
    case 1:
	uiX_INIT(USER_INFO_1);
	HV_GET_PV(PUSER_INFO_1, usri1_script_path,          "scriptPath");
	HV_GET_IV(PUSER_INFO_1, usri1_flags,                "flags");
	HV_GET_PV(PUSER_INFO_1, usri1_comment,              "comment");
	HV_GET_PV(PUSER_INFO_1, usri1_home_dir,             "homeDir");
	HV_GET_IV(PUSER_INFO_1, usri1_priv,                 "priv");
	HV_GET_IV(PUSER_INFO_1, usri1_password_age,         "passwordAge");
	HV_GET_PV(PUSER_INFO_1, usri1_password,             "password");
	/* fall through to 0... */
    case 0:
	uiX_INIT(USER_INFO_0);
	HV_GET_PV(PUSER_INFO_0, usri0_name,                 "name");
	break;
    case 11:
	uiX_INIT(USER_INFO_11);
	HV_GET_IV(PUSER_INFO_11, usri11_code_page,          "codePage");
	HV_GET_AV(PUSER_INFO_11, usri11_logon_hours,        "logonHours", 21);
	HV_GET_IV(PUSER_INFO_11, usri11_units_per_week,     "unitsPerWeek");
	HV_GET_IV(PUSER_INFO_11, usri11_max_storage,        "maxStorage");
	HV_GET_PV(PUSER_INFO_11, usri11_workstations,       "workstations");
	HV_GET_IV(PUSER_INFO_11, usri11_country_code,       "countryCode");
	HV_GET_PV(PUSER_INFO_11, usri11_logon_server,       "logonServer");
	HV_GET_IV(PUSER_INFO_11, usri11_num_logons,         "numLogons");
	HV_GET_IV(PUSER_INFO_11, usri11_bad_pw_count,       "badPwCount");
	HV_GET_IV(PUSER_INFO_11, usri11_last_logoff,        "lastLogoff");
	HV_GET_IV(PUSER_INFO_11, usri11_last_logon,         "lastLogon");
	HV_GET_PV(PUSER_INFO_11, usri11_parms,              "parms");
	HV_GET_PV(PUSER_INFO_11, usri11_home_dir,           "homeDir");
	HV_GET_IV(PUSER_INFO_11, usri11_password_age,       "passwordAge");
	HV_GET_IV(PUSER_INFO_11, usri11_auth_flags,         "authFlags");
	HV_GET_IV(PUSER_INFO_11, usri11_priv,               "priv");
	/* fall through to 10... */
    case 10:
	uiX_INIT(USER_INFO_10);
	HV_GET_PV(PUSER_INFO_10, usri10_full_name,          "fullName");
	HV_GET_PV(PUSER_INFO_10, usri10_usr_comment,        "usrComment");
	HV_GET_PV(PUSER_INFO_10, usri10_comment,            "comment");
	HV_GET_PV(PUSER_INFO_10, usri10_name,               "name");
	break;
    case 20:
	uiX_INIT(USER_INFO_20);
	HV_GET_IV(PUSER_INFO_20, usri20_user_id,            "userId");
	HV_GET_IV(PUSER_INFO_20, usri20_flags,              "flags");
	HV_GET_PV(PUSER_INFO_20, usri20_comment,            "comment");
	HV_GET_PV(PUSER_INFO_20, usri20_full_name,          "fullName");
	HV_GET_PV(PUSER_INFO_20, usri20_name,               "name");
	break;
    case 21:
    case 22:
	croak("level %d not yet implemented for User*() functions\n");
    case 1003:
	/* need to put a value in (can't rely on Get) */
	uiX_INIT(USER_INFO_1003);
	HV_GET_PV(PUSER_INFO_1003, usri1003_password,       "password");
	break;
    case 1005:
	uiX_INIT(USER_INFO_1005);
	HV_GET_IV(PUSER_INFO_1005, usri1005_priv,           "priv");
	break;
    case 1006:
	uiX_INIT(USER_INFO_1006);
	HV_GET_PV(PUSER_INFO_1006, usri1006_home_dir,       "homeDir");
	break;
    case 1007:
	uiX_INIT(USER_INFO_1007);
	HV_GET_PV(PUSER_INFO_1007, usri1007_comment, "comment");
	break;
    case 1008:
	uiX_INIT(USER_INFO_1008);
	HV_GET_IV(PUSER_INFO_1008, usri1008_flags, "flags");
	break;
    case 1009:
	uiX_INIT(USER_INFO_1009);
	HV_GET_PV(PUSER_INFO_1009, usri1009_script_path, "scriptPath");
	break;
    case 1010:
	uiX_INIT(USER_INFO_1010);
	HV_GET_IV(PUSER_INFO_1010, usri1010_auth_flags, "authFlags");
	break;
    case 1011:
	uiX_INIT(USER_INFO_1011);
	HV_GET_PV(PUSER_INFO_1011, usri1011_full_name, "fullName");
	break;
    case 1012:
	uiX_INIT(USER_INFO_1012);
	HV_GET_PV(PUSER_INFO_1012, usri1012_usr_comment, "usrComment");
	break;
    case 1013:
	uiX_INIT(USER_INFO_1013);
	HV_GET_PV(PUSER_INFO_1013, usri1013_parms, "parms");
	break;
    case 1014:
	uiX_INIT(USER_INFO_1014);
	HV_GET_PV(PUSER_INFO_1014, usri1014_workstations, "workstations");
	break;
    case 1017:
	uiX_INIT(USER_INFO_1017);
	HV_GET_IV(PUSER_INFO_1017, usri1017_acct_expires, "acctExpires");
	break;
    case 1018:
	uiX_INIT(USER_INFO_1018);
	HV_GET_IV(PUSER_INFO_1018, usri1018_max_storage, "maxStorage");
	break;
    case 1020:
	uiX_INIT(USER_INFO_1020);
	HV_GET_IV(PUSER_INFO_1020, usri1020_units_per_week, "unitsPerWeek");
	HV_GET_AV(PUSER_INFO_1020, usri1020_logon_hours, "logonHours", 21);
	break;
    case 1023:
	uiX_INIT(USER_INFO_1023);
	HV_GET_PV(PUSER_INFO_1023, usri1023_logon_server, "logonServer");
	break;
    case 1024:
	uiX_INIT(USER_INFO_1024);
	HV_GET_IV(PUSER_INFO_1024, usri1024_country_code, "countryCode");
	break;
    case 1025:
	uiX_INIT(USER_INFO_1025);
	HV_GET_IV(PUSER_INFO_1025, usri1025_code_page, "codePage");
	break;
    case 1051:
	uiX_INIT(USER_INFO_1051);
	HV_GET_IV(PUSER_INFO_1051, usri1051_primary_group_id, "primaryGroupId");
	break;
    case 1052:
	uiX_INIT(USER_INFO_1052);
	HV_GET_PV(PUSER_INFO_1052, usri1052_profile, "profile");
	break;
    case 1053:
	uiX_INIT(USER_INFO_1053);
	HV_GET_PV(PUSER_INFO_1053, usri1053_home_dir_drive, "homeDirDrive");
	break;
    default:
	croak("Level %d is not a valid level for NetUser functions\n", level);
	break;
    }
    
    return uiX;
}

void
freeWideName(LPWSTR lpPtr)
{
    Safefree(lpPtr);
}

#define WCFREE(CAST, field) freeWideName(((CAST)uiX)->field)
#define PBFREE(CAST, field) Safefree(((CAST)uiX)->field)

void
freeUserInfoX(LPBYTE *uiX, int level)
{
    dTHX;
    switch (level) {
    case 3:
	WCFREE(PUSER_INFO_3, usri3_home_dir_drive);
	WCFREE(PUSER_INFO_3, usri3_profile);
	/* fall through to 2... */
    case 2:
	WCFREE(PUSER_INFO_2, usri2_logon_server);
	PBFREE(PUSER_INFO_2, usri2_logon_hours);
	WCFREE(PUSER_INFO_2, usri2_workstations);
	WCFREE(PUSER_INFO_2, usri2_parms);
	WCFREE(PUSER_INFO_2, usri2_usr_comment);
	WCFREE(PUSER_INFO_2, usri2_full_name);
	/* fall through to 1... */
    case 1:
	WCFREE(PUSER_INFO_1, usri1_script_path);
	WCFREE(PUSER_INFO_1, usri1_comment);
	WCFREE(PUSER_INFO_1, usri1_home_dir);
	WCFREE(PUSER_INFO_1, usri1_password);
    case 0:
	WCFREE(PUSER_INFO_0, usri0_name);
	break;
    case 11:
	PBFREE(PUSER_INFO_11, usri11_logon_hours);
	WCFREE(PUSER_INFO_11, usri11_workstations);
	WCFREE(PUSER_INFO_11, usri11_logon_server);
	WCFREE(PUSER_INFO_11, usri11_parms);
	WCFREE(PUSER_INFO_11, usri11_home_dir);
	/* fall through to 10... */
    case 10:
	WCFREE(PUSER_INFO_10, usri10_full_name);
	WCFREE(PUSER_INFO_10, usri10_usr_comment);
	WCFREE(PUSER_INFO_10, usri10_comment);
	WCFREE(PUSER_INFO_10, usri10_name);
	break;
    case 20:
	WCFREE(PUSER_INFO_20, usri20_comment);
	WCFREE(PUSER_INFO_20, usri20_full_name);
	WCFREE(PUSER_INFO_20, usri20_name);
	break;
    case 1003:
	/* need to put a value in (can't rely on Get) */
	WCFREE(PUSER_INFO_1003, usri1003_password);
	break;
    case 1005:
	break;
    case 1006:
	WCFREE(PUSER_INFO_1006, usri1006_home_dir);
	break;
    case 1007:
	WCFREE(PUSER_INFO_1007, usri1007_comment);
    case 1008:
	break;
    case 1009:
	WCFREE(PUSER_INFO_1009, usri1009_script_path);
	break;
    case 1010:
	break;
    case 1011:
	WCFREE(PUSER_INFO_1011, usri1011_full_name);
	break;
    case 1012:
	WCFREE(PUSER_INFO_1012, usri1012_usr_comment);
	break;
    case 1013:
	WCFREE(PUSER_INFO_1013, usri1013_parms);
	break;
    case 1014:
	WCFREE(PUSER_INFO_1014, usri1014_workstations);
	break;
    case 1017:
	break;
    case 1018:
	break;
    case 1020:
	PBFREE(PUSER_INFO_1020, usri1020_logon_hours);
	break;
    case 1023:
	WCFREE(PUSER_INFO_1023, usri1023_logon_server);
	break;
    case 1024:
	break;
    case 1025:
	break;
    case 1051:
	break;
    case 1052:
	WCFREE(PUSER_INFO_1052, usri1052_profile);
	break;
    case 1053:
	WCFREE(PUSER_INFO_1053, usri1053_home_dir_drive);
	break;
    case 21:
    default:
	croak("unhandled level in free     CODE\n");
    }
}

LPBYTE *
allocGroupInfoX(int level, HV *hv)
{
    LPBYTE	*uiX = NULL;
    SV		**svPtr;
    dTHX;
    
    switch (level) {
    case 2:
	uiX_INIT(GROUP_INFO_2);
	HV_GET_IV(PGROUP_INFO_2, grpi2_attributes,    "attributes");
	HV_GET_IV(PGROUP_INFO_2, grpi2_group_id,        "group_id");
	/* fall through to 1 */
    case 1:
	uiX_INIT(GROUP_INFO_1);
	HV_GET_PV(PGROUP_INFO_1, grpi1_comment,     "comment");
	/* fall through to 0 */
    case 0:
	uiX_INIT(GROUP_INFO_0);
	HV_GET_PV(PGROUP_INFO_0, grpi0_name,            "name");
	break;
    case 1002:
	uiX_INIT(GROUP_INFO_1002);
	HV_GET_PV(PGROUP_INFO_1002, grpi1002_comment,    "comment");
	break;
    case 1005:
	uiX_INIT(GROUP_INFO_1005);
	HV_GET_IV(PGROUP_INFO_1005, grpi1005_attributes,    "attributes");
	break;
    default:
	break;
    }
    
    return uiX;
}

void
freeGroupInfoX(int level, LPBYTE *uiX)
{
    switch (level) {
    case 2:
	/* fall through to 1 */
    case 1:
	WCFREE(PGROUP_INFO_1, grpi1_comment);
	/* fall through to 0 */
    case 0:
	WCFREE(PGROUP_INFO_0, grpi0_name);
	break;
    case 1002:
	WCFREE(PGROUP_INFO_1002, grpi1002_comment);
	break;
    case 1005:
	break;
    default:
	break;
    }
}

int
WCTMB(LPWSTR lpwStr, LPSTR lpStr, int size)
{
    *lpStr = '\0';
    return WideCharToMultiByte(CP_ACP, 0, lpwStr, -1, lpStr, size,
			       NULL, NULL);
}

/* The following MACROs assume that the following are in scope
 * hv      - HV*, the hashRef into/from which information is placed/extracted
 * sv      - temporary SV* for intermediate SV * values
 * uiX     - LPBYTE alloc'ed appropriate to CAST
 * tmpBuf  - char * for temporary string values
 */

#define HV_STORE_PV(CAST, field, name) \
    STMT_START {							\
	WCTMB(((CAST)uiX)->field, tmpBuf, sizeof(tmpBuf));		\
	sv = newSVpv(tmpBuf, (I32)strlen(tmpBuf));			\
	hv_store((HV*)hv, name, (I32)strlen(name), sv, 0);		\
    } STMT_END

#define HV_STORE_IV(CAST, field, name) \
    STMT_START {							\
	sv = newSViv(((CAST)uiX)->field);				\
	hv_store((HV*)hv, name, (I32)strlen(name), sv, 0);		\
    } STMT_END

#define HV_STORE_AV(CAST, field, name, n) \
    STMT_START {							\
	int i = 0;							\
	AV *av;								\
	av = newAV();							\
	while (i < n) {							\
	    sv = newSViv((BYTE)(((CAST)uiX)->field)[i++]);		\
	    av_push(av, sv);						\
	}								\
	hv_store((HV*)hv, name, (I32)strlen(name), newRV_noinc((SV*)av), 0);	\
    } STMT_END

void
fillUserHash(HV *hv, int level, LPBYTE *uiX)
{
    SV *sv;
    char tmpBuf[UNLEN+1];
    dTHX;
    
    switch (level) {
    case 3:
    case 4:
    if( 3 == level )
    {
        HV_STORE_IV(PUSER_INFO_3, usri3_password_expired,    "passwordExpired");
        HV_STORE_PV(PUSER_INFO_3, usri3_home_dir_drive,      "homeDirDrive");
        HV_STORE_PV(PUSER_INFO_3, usri3_profile,             "profile");
        HV_STORE_IV(PUSER_INFO_3, usri3_primary_group_id,    "primaryGroupId");
        HV_STORE_IV(PUSER_INFO_3, usri3_user_id,             "userId");
    }
    else
    {
        LPTSTR sStringSid = NULL;
        if( ConvertSidToStringSidA( ((PMY_USER_INFO_4)uiX)->usri4_user_sid, &sStringSid ) )
        {
            sv = newSVpv( sStringSid, (I32)(strlen(sStringSid)) );
            hv_store( hv, "userSid", (I32)(strlen("userSid")), sv, 0 );
            LocalFree(sStringSid);
        }
        HV_STORE_IV(PMY_USER_INFO_4, usri4_password_expired, "passwordExpired");
        HV_STORE_PV(PMY_USER_INFO_4, usri4_home_dir_drive,   "homeDirDrive");
        HV_STORE_PV(PMY_USER_INFO_4, usri4_profile,          "profile");
        HV_STORE_IV(PMY_USER_INFO_4, usri4_primary_group_id, "primaryGroupId");
    }
	/* fall through to 2... */
    case 2:
	HV_STORE_IV(PUSER_INFO_2, usri2_code_page,           "codePage");
	HV_STORE_IV(PUSER_INFO_2, usri2_country_code,        "countryCode");
	HV_STORE_PV(PUSER_INFO_2, usri2_logon_server,        "logonServer");
	HV_STORE_IV(PUSER_INFO_2, usri2_num_logons,          "numLogons");
	HV_STORE_IV(PUSER_INFO_2, usri2_bad_pw_count,        "badPwCount");
	HV_STORE_AV(PUSER_INFO_2, usri2_logon_hours,         "logonHours", 21);
	HV_STORE_IV(PUSER_INFO_2, usri2_units_per_week,      "unitsPerWeek");
	HV_STORE_IV(PUSER_INFO_2, usri2_max_storage,         "maxStorage");
	HV_STORE_IV(PUSER_INFO_2, usri2_acct_expires,        "acctExpires");
	HV_STORE_IV(PUSER_INFO_2, usri2_last_logoff,         "lastLogoff");
	HV_STORE_IV(PUSER_INFO_2, usri2_last_logon,          "lastLogon");
	HV_STORE_PV(PUSER_INFO_2, usri2_workstations,        "workstations");
	HV_STORE_PV(PUSER_INFO_2, usri2_parms,               "parms");
	HV_STORE_PV(PUSER_INFO_2, usri2_usr_comment,         "usrComment");
	HV_STORE_PV(PUSER_INFO_2, usri2_full_name,           "fullName");
	HV_STORE_IV(PUSER_INFO_2, usri2_auth_flags,          "authFlags");
	/* fall through to 1... */
    case 1:
	HV_STORE_PV(PUSER_INFO_1, usri1_script_path,         "scriptPath");
	HV_STORE_IV(PUSER_INFO_1, usri1_flags,               "flags");
	HV_STORE_PV(PUSER_INFO_1, usri1_comment,             "comment");
	HV_STORE_PV(PUSER_INFO_1, usri1_home_dir,            "homeDir");
	HV_STORE_IV(PUSER_INFO_1, usri1_priv,                "priv");
	HV_STORE_IV(PUSER_INFO_1, usri1_password_age,        "passwordAge");
	HV_STORE_PV(PUSER_INFO_1, usri1_password,            "password");
	/* fall through to 0... */
    case 0:
	HV_STORE_PV(PUSER_INFO_0, usri0_name,                "name");
	break;
    case 11:
	HV_STORE_IV(PUSER_INFO_11, usri11_code_page,         "codePage");
	HV_STORE_AV(PUSER_INFO_11, usri11_logon_hours,       "logonHours", 21);
	HV_STORE_IV(PUSER_INFO_11, usri11_units_per_week,    "unitsPerWeek");
	HV_STORE_IV(PUSER_INFO_11, usri11_max_storage,       "maxStorage");
	HV_STORE_PV(PUSER_INFO_11, usri11_workstations,      "workstations");
	HV_STORE_IV(PUSER_INFO_11, usri11_country_code,      "countryCode");
	HV_STORE_PV(PUSER_INFO_11, usri11_logon_server,      "logonServer");
	HV_STORE_IV(PUSER_INFO_11, usri11_num_logons,        "numLogons");
	HV_STORE_IV(PUSER_INFO_11, usri11_bad_pw_count,      "badPwCount");
	HV_STORE_IV(PUSER_INFO_11, usri11_last_logoff,       "lastLogoff");
	HV_STORE_IV(PUSER_INFO_11, usri11_last_logon,        "lastLogon");
	HV_STORE_PV(PUSER_INFO_11, usri11_parms,             "parms");
	HV_STORE_PV(PUSER_INFO_11, usri11_home_dir,          "homeDir");
	HV_STORE_IV(PUSER_INFO_11, usri11_password_age,      "passwordAge");
	HV_STORE_IV(PUSER_INFO_11, usri11_auth_flags,        "authFlags");
	HV_STORE_IV(PUSER_INFO_11, usri11_priv,              "priv");
	/* fall through to 10... */
    case 10:
	HV_STORE_PV(PUSER_INFO_10, usri10_full_name,         "fullName");
	HV_STORE_PV(PUSER_INFO_10, usri10_usr_comment,       "usrComment");
	HV_STORE_PV(PUSER_INFO_10, usri10_comment,           "comment");
	HV_STORE_PV(PUSER_INFO_10, usri10_name,              "name");
	break;
    case 20:
	HV_STORE_IV(PUSER_INFO_20, usri20_user_id,           "userId");
	HV_STORE_IV(PUSER_INFO_20, usri20_flags,             "flags");
	HV_STORE_PV(PUSER_INFO_20, usri20_comment,           "comment");
	HV_STORE_PV(PUSER_INFO_20, usri20_full_name,         "fullName");
	HV_STORE_PV(PUSER_INFO_20, usri20_name,              "name");
	break;
    case 21:
	HV_STORE_AV(PUSER_INFO_21, usri21_password,          "password", ENCRYPTED_PWLEN);
    break;
    case 22:
	HV_STORE_PV(PUSER_INFO_22, usri22_name,              "name");
	HV_STORE_AV(PUSER_INFO_22, usri22_password,          "password", ENCRYPTED_PWLEN);
	HV_STORE_IV(PUSER_INFO_22, usri22_password_age,      "passwordAge");
	HV_STORE_IV(PUSER_INFO_22, usri22_priv,              "priv");
	HV_STORE_PV(PUSER_INFO_22, usri22_home_dir,          "homeDir");
	HV_STORE_PV(PUSER_INFO_22, usri22_comment,           "comment");
	HV_STORE_IV(PUSER_INFO_22, usri22_flags,             "flags");
	HV_STORE_PV(PUSER_INFO_22, usri22_script_path,       "scriptPath");
	HV_STORE_IV(PUSER_INFO_22, usri22_auth_flags,        "authFlags");
	HV_STORE_PV(PUSER_INFO_22, usri22_full_name,         "fullName");
	HV_STORE_PV(PUSER_INFO_22, usri22_usr_comment,       "usrComment");
	HV_STORE_PV(PUSER_INFO_22, usri22_parms,             "parms");
	HV_STORE_PV(PUSER_INFO_22, usri22_workstations,      "workstations");
	HV_STORE_IV(PUSER_INFO_22, usri22_last_logon,        "lastLogon");
	HV_STORE_IV(PUSER_INFO_22, usri22_last_logoff,       "lastLogoff");
	HV_STORE_IV(PUSER_INFO_22, usri22_acct_expires,      "acctExpires");
	HV_STORE_IV(PUSER_INFO_22, usri22_max_storage,       "maxStorage");
	HV_STORE_IV(PUSER_INFO_22, usri22_units_per_week,    "unitsPerWeek");
	HV_STORE_AV(PUSER_INFO_22, usri22_logon_hours,       "logonHours", 21);
	HV_STORE_IV(PUSER_INFO_22, usri22_bad_pw_count,      "badPwCount");
	HV_STORE_IV(PUSER_INFO_22, usri22_num_logons,        "numLogons");
	HV_STORE_PV(PUSER_INFO_22, usri22_logon_server,      "logonServer");
	HV_STORE_IV(PUSER_INFO_22, usri22_country_code,      "countryCode");
	HV_STORE_IV(PUSER_INFO_22, usri22_code_page,         "codePage");
    break;
    case 23:
    {
        LPTSTR sStringSid = NULL;
        if( ConvertSidToStringSidA( ((PMY_USER_INFO_23)uiX)->usri23_user_sid, &sStringSid ) )
        {
            sv = newSVpv( sStringSid, (I32)(strlen(sStringSid)) );
            hv_store( hv, "userSid", (I32)(strlen("userSid")), sv, 0 );
            LocalFree(sStringSid);
        }
    }
	HV_STORE_IV(PMY_USER_INFO_23, usri23_flags,          "flags");
	HV_STORE_PV(PMY_USER_INFO_23, usri23_comment,        "comment");
	HV_STORE_PV(PMY_USER_INFO_23, usri23_full_name,      "fullName");
	HV_STORE_PV(PMY_USER_INFO_23, usri23_name,           "name");
	break;
    case 1003:
    case 1005:
    case 1006:
    case 1007:
    case 1008:
    case 1009:
    case 1010:
    case 1011:
    case 1012:
    case 1013:
    case 1014:
    case 1017:
    case 1018:
    case 1020:
    case 1023:
    case 1024:
    case 1025:
    case 1051:
    case 1052:
    case 1053:
    default:
	croak("fillUserHash: Level %d not implemented!\n", level);
	break;
    }
}

void
fillGroupHash(HV *hv, int level, LPBYTE *uiX)
{
    SV *sv;
    char tmpBuf[UNLEN+1];
    dTHX;
    
    switch (level) {
	case 3:
	case 2:
    if( 2 == level )
    {
        HV_STORE_IV(PGROUP_INFO_2, grpi2_group_id,	 "groupId");
        HV_STORE_IV(PGROUP_INFO_2, grpi2_attributes, "attributes");
    }
    else
    {
        LPTSTR sStringSid = NULL;
        if( ConvertSidToStringSidA( ((PMY_GROUP_INFO_3)uiX)->grpi3_group_sid, &sStringSid ) )
        {
            sv = newSVpv( sStringSid, (I32)(strlen(sStringSid)) );
            hv_store( hv, "groupSid", (I32)(strlen("groupSid")), sv, 0 );
            LocalFree(sStringSid);
        }
        HV_STORE_IV(PMY_GROUP_INFO_3, grpi3_attributes, "attributes");
    }
	
	/* fall through to 1 */
    case 1:
	HV_STORE_PV(PGROUP_INFO_1, grpi1_comment,    "comment");
	/* fall through to 0 */
    case 0:
	HV_STORE_PV(PGROUP_INFO_0, grpi0_name,        "name");
	break;
    case 1002:
    case 1005:
    default:
	croak("fillGroupHash: Level %d not implemented!\n", level);
    }
}

LPBYTE *
allocLocalGroupInfoX(int level, HV *hv)
{
    LPBYTE	*uiX = NULL;
    SV		**svPtr;
    dTHX;
    
    switch (level) {
    case 1:
	uiX_INIT(LOCALGROUP_INFO_1);
	HV_GET_PV(PLOCALGROUP_INFO_1, lgrpi1_comment,        "comment");
	/* fall through to 0 */
    case 0:
	uiX_INIT(LOCALGROUP_INFO_0);
	HV_GET_PV(PLOCALGROUP_INFO_0, lgrpi0_name,            "name");
	break;
    case 1002:
	uiX_INIT(LOCALGROUP_INFO_1002);
	HV_GET_PV(PLOCALGROUP_INFO_1002, lgrpi1002_comment,    "comment");
	break;
    default:
	break;
    }
    
    return uiX;
}

void
fillLocalGroupHash(HV *hv, int level, LPBYTE *uiX)
{
    SV *sv;
    char tmpBuf[UNLEN+1];
    dTHX;
    
    switch (level) {
    case 1:
	HV_STORE_PV(PLOCALGROUP_INFO_1, lgrpi1_comment, "comment");
	/* fall through to 0 */
    case 0:
	HV_STORE_PV(PLOCALGROUP_INFO_0, lgrpi0_name,        "name");
	break;
    case 1002:
	HV_STORE_PV(PLOCALGROUP_INFO_1002, lgrpi1002_comment,    "comment");
	break;
    default:
	croak("fillLocalGroupHash: Level %d not implemented!\n", level);
    }
}

void
freeLocalGroupInfoX(int level, LPBYTE *uiX)
{
    dTHX;
    switch (level) {
    case 1:
	WCFREE(PLOCALGROUP_INFO_1, lgrpi1_comment);
	/* fall through to 0 */
    case 0:
	WCFREE(PLOCALGROUP_INFO_0, lgrpi0_name);
	break;
    case 1002:
	WCFREE(PLOCALGROUP_INFO_1002, lgrpi1002_comment);
	break;
    default:
	break;
    }
    Safefree(uiX);
}

MODULE = Win32API::Net        PACKAGE = Win32API::Net        PREFIX = Win32API
PROTOTYPES: ENABLE

double
constant(name, arg)
    char *name;
    int arg;

int
UserAdd(server, level, hash, fie)
    char *server;
    int    level;
    SV        *hash;
    int    fie;
PROTOTYPE: $$$$
CODE:
    {
	LPBYTE *uiX = NULL;
	DWORD  error;
	LPWSTR lpwServer = MBTWC(server);
	DWORD lastError = 0;

	if (!(hash && SvROK(hash) &&
	     (hash = SvRV(hash)) && SvTYPE(hash) == SVt_PVHV))
	    croak("Third argument to UserAdd() must be a hash reference,");
	
	uiX = allocUserInfoX(level, (HV*)hash);
	
	lastError = NetUserAdd(lpwServer, level, (LPBYTE)uiX, &error);
	fie = error;
	RETVAL = (lastError == NERR_Success);
	
	freeUserInfoX(uiX, level);
	freeWideName(lpwServer);
    }
OUTPUT:
    fie
    RETVAL

int
UserChangePassword(server, user, oldPassword, newPassword)
    char *server;
    char *user;
    char *oldPassword;
    char *newPassword;
PROTOTYPE: $$$$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwUser = MBTWC(user);
	LPWSTR lpwOldPassword = MBTWC(oldPassword);
	LPWSTR lpwNewPassword = MBTWC(newPassword);
	DWORD lastError = 0;

	lastError = NetUserChangePassword(lpwServer, lpwUser, lpwOldPassword,
					  lpwNewPassword);

	RETVAL = (lastError == NERR_Success);

	freeWideName(lpwNewPassword);
	freeWideName(lpwOldPassword);
	freeWideName(lpwUser);
	freeWideName(lpwServer);
    }
OUTPUT:
    RETVAL

int
UserDel(server, user)
    char *server;
    char *user;
PROTOTYPE: $$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwUser = MBTWC(user);
	DWORD lastError = 0;

	lastError = NetUserDel(lpwServer, lpwUser);

	RETVAL = (lastError == NERR_Success);

	freeWideName(lpwUser);
	freeWideName(lpwServer);
    }
OUTPUT:
    RETVAL

int
UserEnum(server, array, ...)
    char    *server
    SV        *array
PROTOTYPE: $$;$
PREINIT:
    int    filter = FILTER_NORMAL_ACCOUNT;
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	PUSER_INFO_0 pwzUsers = NULL;
	DWORD entriesRead = 0, totalEntries = 0, resumeHandle = 0;
	DWORD index;
	DWORD lastError = 0;
	char tmpBuf[UNLEN+1];

	if (items > 2)    filter = (int)SvIV(ST(2));
	
	if (!(array && SvROK(array) &&
	     (array = SvRV(array)) && SvTYPE(array) == SVt_PVAV))
	    croak("Second argument to UserEnum() must be an array reference,");

	av_clear((AV*)array);

	do    {
	    lastError = NetUserEnum(lpwServer, 0, filter, (LPBYTE*)&pwzUsers,
				    PREFLEN, &entriesRead, &totalEntries,
				    &resumeHandle);

	    if (lastError != ERROR_MORE_DATA && lastError != NERR_Success)
		break;			/* we have a failure */

	    if (entriesRead == 0)
		break;			/* 1st pass got them all */

	    for (index = 0; index < entriesRead; ++index) {
		WCTMB(pwzUsers[index].usri0_name,(LPSTR)tmpBuf,sizeof(tmpBuf));
		av_push((AV*)array, newSVpv(tmpBuf, 0));
	    }
	    NetApiBufferFree(pwzUsers);
	} while (entriesRead != totalEntries && resumeHandle != 0);

	freeWideName(lpwServer);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
UserGetGroups(server, user, array)
    char    *server
    char    *user
    SV        *array;
PROTOTYPE: $$$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwUser = MBTWC(user);
	PGROUP_INFO_0 pwzGroups;
	DWORD entriesRead = 0, totalEntries = 0;
        DWORD index;
	int len = PREFLEN;
	DWORD lastError = 0;
	char tmpBuf[UNLEN+1];

	if (!(array && SvROK(array) &&
	     (array = SvRV(array)) && SvTYPE(array) == SVt_PVAV))
	    croak("Third argument to UserGetGroups() must be an array reference,");

	av_clear((AV*)array);

	do {
	    lastError = NetUserGetGroups(lpwServer, lpwUser, 0,
					 (LPBYTE*)&pwzGroups, len,
					 &entriesRead,
					 &totalEntries);

	    if (lastError == ERROR_MORE_DATA) {
		len *= 2;
		NetApiBufferFree(pwzGroups);
	    }
	    else if (lastError != NERR_Success)
		 break; /* get out - something else is wrong! */
	} while (lastError == ERROR_MORE_DATA);

	if (lastError == NERR_Success) {
	    for (index = 0; index < entriesRead; index++) {
		WCTMB(pwzGroups[index].grpi0_name, tmpBuf, sizeof(tmpBuf));
		av_push((AV*)array, newSVpv(tmpBuf, 0));
	    }
	    NetApiBufferFree(pwzGroups);
	}

	freeWideName(lpwServer);
	freeWideName(lpwUser);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
UserGetInfo(server, user, level, hash)
    char    *server;
    char    *user;
    int    level;
    SV    *hash;
PROTOTYPE: $$$$
CODE:
    {
	LPBYTE *uiX = NULL;
	LPWSTR lpwServer, lpwUser;
	SV *sv;
	DWORD lastError = 0;

	if (!(hash && SvROK(hash) &&
	     (hash = SvRV(hash)) && SvTYPE(hash) == SVt_PVHV))
	    croak("Fourth argument to UserGetInfo() must be a hash reference,");

	lpwServer = MBTWC(server);
	lpwUser = MBTWC(user);

	hv_clear((HV*)hash);
	lastError = NetUserGetInfo(lpwServer, lpwUser, level, (LPBYTE*)&uiX);

	freeWideName(lpwServer);
	freeWideName(lpwUser);

	if (lastError == NERR_Success) {
	    fillUserHash((HV *)hash, level, uiX);
	    sv = newSVpv(user, (I32)strlen(user));
	    hv_store((HV*)hash, "name", 4, sv, 0);            
	    NetApiBufferFree(uiX);
	}

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
UserGetLocalGroups(server, user, array, ...)
    char    *server
    char    *user
    SV        *array
PROTOTYPE: $$$;$
PREINIT:
    int flags = 0;
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwUser = MBTWC(user);
	LPLOCALGROUP_USERS_INFO_0 pwzLocalGroupUsers=NULL;
	DWORD entriesRead = 0, totalEntries = 0;
        DWORD index;
	int len = PREFLEN;
	char tmpBuf[UNLEN+1];
	DWORD lastError = 0;

	if (items > 3) flags = (int)SvIV(ST(3));
	
	if (!(array && SvROK(array) &&
	     (array = SvRV(array)) && (SvTYPE(array) == SVt_PVAV)))
	    croak("Third argument to UserGetLocalGroups() must be an array reference,");

	av_clear((AV*)array);

	do {
	    lastError = NetUserGetLocalGroups(lpwServer, lpwUser, 0, flags,
					      (LPBYTE*)&pwzLocalGroupUsers,
					      len, &entriesRead, &totalEntries);
	    /* this could fail is PREFLEN is not big enough... */
	    if (lastError == ERROR_MORE_DATA) {
		len *= 2;
		NetApiBufferFree(pwzLocalGroupUsers);
	    }
	    else if (lastError != NERR_Success)
		 break; /* don't know whats going on - get out */
	} while (lastError == ERROR_MORE_DATA);

	if (lastError == NERR_Success) {
	    for (index = 0; index < entriesRead; index++) {
		WCTMB(pwzLocalGroupUsers[index].lgrui0_name,
		      (LPSTR)tmpBuf, sizeof(tmpBuf));
		av_push((AV*)array, newSVpv(tmpBuf, 0));
	    }
	    NetApiBufferFree(pwzLocalGroupUsers);
	}

	freeWideName(lpwServer);
	freeWideName(lpwUser); 

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
UserSetGroups(server, user, ...)
    char    *server;
    char    *user;
PROTOTYPE: $$@
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwUser = MBTWC(user);
	int i;
	DWORD lastError = 0;

	GROUP_INFO_0    *groups;

	Newz(0, groups, items, GROUP_INFO_0);

	for (i=2; i<items; i++) 
	    groups[i-2].grpi0_name = MBTWC(SvPV_nolen(ST(i)));

	lastError = NetUserSetGroups(lpwServer, lpwUser, 0,
				     (LPBYTE)groups, items-2);

	for (i=2; i<items; i++)
	    freeWideName(groups[i-2].grpi0_name);
	Safefree(groups);
	freeWideName(lpwServer);
	freeWideName(lpwUser);
	
	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
UserSetInfo(server, user, level, hash, fie)
    char    *server
    char    *user
    int    level
    SV    *hash
    int    fie
PROTOTYPE: $$$$$
CODE:
    {
	DWORD    error;
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwUser = MBTWC(user);
	LPBYTE *uiX = NULL;
	DWORD lastError = 0;

	if (!(hash && SvROK(hash) &&
	     (hash = SvRV(hash)) && SvTYPE(hash) == SVt_PVHV))
	    croak("Fourth argument to UserSetInfo() must be a hash reference,");

	uiX = allocUserInfoX(level, (HV*)hash);
	
	lastError = NetUserSetInfo(lpwServer, lpwUser, level,
				   (LPBYTE)uiX, &error);
	fie = error;

	RETVAL = (lastError == NERR_Success);

	freeUserInfoX(uiX, level);
	freeWideName(lpwServer);
	freeWideName(lpwUser);
    }
OUTPUT:
    fie
    RETVAL

int
GroupAdd(server, level, hash, fie)
    char    *server
    int    level
    SV        *hash
    int    fie
PROTOTYPE: $$$$
CODE:
    {
	DWORD    error;
	LPWSTR lpwServer = MBTWC(server);
	LPBYTE *giX;
	DWORD lastError = 0;

	if (!(hash && SvROK(hash) &&
	     (hash = SvRV(hash)) && SvTYPE(hash) == SVt_PVHV))
	    croak("Third argument to GroupAdd() must be a hash reference,");

	giX = allocGroupInfoX(level, (HV*)hash);
	lastError = NetGroupAdd(lpwServer, level, (LPBYTE)giX, &error);
	fie = error;

	freeGroupInfoX(level, giX);
	freeWideName(lpwServer);

	/*warn("lastError=%d\n", lastError);*/
	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    fie
    RETVAL
    
int
GroupAddUser(server, group, user)
    char    *server
    char    *group
    char    *user
PROTOTYPE: $$$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwGroup = MBTWC(group);
	LPWSTR lpwUser = MBTWC(user);
	DWORD lastError = 0;

	lastError = NetGroupAddUser(lpwServer, lpwGroup, lpwUser);

	freeWideName(lpwUser);
	freeWideName(lpwGroup);
	freeWideName(lpwServer);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
GroupDel(server, group)
    char    *server
    char    *group
PROTOTYPE: $$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwGroup = MBTWC(group);
	DWORD lastError = 0;

	lastError = NetGroupDel(lpwServer, lpwGroup);

	freeWideName(lpwGroup);
	freeWideName(lpwServer);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
GroupDelUser(server, group, user)
    char    *server
    char    *group
    char    *user
PROTOTYPE: $$$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwGroup = MBTWC(group);
	LPWSTR lpwUser = MBTWC(user);
	DWORD lastError = 0;

	lastError = NetGroupDelUser(lpwServer, lpwGroup, lpwUser);

	freeWideName(lpwUser);
	freeWideName(lpwGroup);
	freeWideName(lpwServer);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
GroupEnum(server, array)
    char    *server
    SV        *array
PROTOTYPE: $$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	PGROUP_INFO_0 pwzGroups;
	DWORD entriesRead = 0, totalEntries = 0;
        DWORD_PTR resumeHandle = 0;
        DWORD index;
	DWORD lastError = 0;
	char tmpBuf[UNLEN+1];
	
	if (!(array && SvROK(array) &&
	     (array = SvRV(array)) && SvTYPE(array) == SVt_PVAV))
	    croak("Third argument to GroupEnum() must be a array reference,");

	av_clear((AV*)array);
	
	do    {
	    lastError = NetGroupEnum(lpwServer, 0, (LPBYTE*)&pwzGroups,
				     PREFLEN, &entriesRead, &totalEntries,
				     &resumeHandle);

	    if (lastError != ERROR_MORE_DATA &&
		 lastError != NERR_Success) break;

	    for (index = 0; index < entriesRead; ++index) {
		WCTMB(pwzGroups[index].grpi0_name,(LPSTR)tmpBuf,sizeof(tmpBuf));
		av_push((AV*)array, newSVpv(tmpBuf, 0));
	    }
	    NetApiBufferFree(pwzGroups);
	} while (resumeHandle != 0);

	freeWideName(lpwServer);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
GroupGetInfo(server, group, level, hash)
    char    *server
    char    *group
    int    level
    SV        *hash
PROTOTYPE: $$$$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwGroup = MBTWC(group);
	LPBYTE *groupInfo = NULL;
	DWORD lastError = 0;
	
	if (!(hash && SvROK(hash) &&
	     (hash = SvRV(hash)) && SvTYPE(hash) == SVt_PVHV))
	    croak("Fourth argument to GroupGetInfo() must be a hash reference,");

	hv_clear((HV*)hash);
	
	lastError = NetGroupGetInfo(lpwServer, lpwGroup, level, (LPBYTE*)&groupInfo);

	if (lastError == NERR_Success)
	    fillGroupHash((HV*)hash, level, groupInfo);

	NetApiBufferFree(groupInfo);
	freeWideName(lpwGroup);
	freeWideName(lpwServer);
	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
GroupGetUsers(server, group, array)
    char    *server
    char    *group
    SV        *array
PROTOTYPE: $$$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwGroup = MBTWC(group);
	PGROUP_USERS_INFO_0 pwzGroupUsers;
	DWORD entriesRead = 0, totalEntries = 0;
        DWORD_PTR resumeHandle = 0;
        DWORD index;
	DWORD lastError = 0;
	char tmpBuf[UNLEN+1];
	
	if (!(array && SvROK(array) &&
	     (array = SvRV(array)) && SvTYPE(array) == SVt_PVAV))
	    croak("Third argument to GroupGetUsers() must be an array reference,");

	av_clear((AV*)array);
	
	do {
	    lastError = NetGroupGetUsers(lpwServer, lpwGroup, 0,
					 (LPBYTE*)&pwzGroupUsers, PREFLEN,
					 &entriesRead, &totalEntries,
					 &resumeHandle);

	    if (lastError != ERROR_MORE_DATA &&
		 lastError != NERR_Success) break;
		
	    for (index = 0; index < entriesRead; index++) {
		WCTMB(pwzGroupUsers[index].grui0_name, (LPSTR)tmpBuf,
		      sizeof(tmpBuf));
		if (entriesRead == 1 && (strcmp(tmpBuf, "None") == 0)) break;
		av_push((AV*)array, newSVpv(tmpBuf, 0));
	    }
	    NetApiBufferFree(pwzGroupUsers);
	} while (resumeHandle != 0);
	
	freeWideName(lpwServer);
	freeWideName(lpwGroup);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
GroupSetInfo(server, group, level, hash, fie)
    char    *server
    char    *group
    int    level
    SV        *hash
    int    fie
PROTOTYPE: $$$$$
CODE:
    {
	DWORD    error;
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwGroup = MBTWC(group);
	LPBYTE *giX;
	DWORD lastError = 0;

	if (!(hash && SvROK(hash) &&
	     (hash = SvRV(hash)) && SvTYPE(hash) == SVt_PVHV))
	    croak("Fourth argument to GroupsetInfo() must be a hash reference,");

	giX = allocGroupInfoX(level, (HV*)hash);
	lastError = NetGroupSetInfo(lpwServer, lpwGroup, level,
				    (LPBYTE)giX, &error);

	fie = error;
	freeGroupInfoX(level, giX);
	freeWideName(lpwGroup);
	freeWideName(lpwServer);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    fie
    RETVAL

int
GroupSetUsers(server, group, array)
    char    *server
    char    *group
    SV        *array
PROTOTYPE: $$$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwGroup = MBTWC(group);
	int    i, numUsers;
	STRLEN pl_na;
	GROUP_USERS_INFO_0    *users;
	SV        **svTmp;
	DWORD lastError = 0;
	
	if (!(array && SvROK(array) &&
	     (array = SvRV(array)) && SvTYPE(array) == SVt_PVAV))
	    croak("Third argument to GroupSetUsers() must be an array reference,");

	numUsers = av_len((AV*)array)+1;

	Newz(0, users, numUsers, GROUP_USERS_INFO_0);

	for (i=0; i<numUsers; i++) {
	    svTmp = av_fetch((AV*)array, i, 0);
	    if (*svTmp)
	    users[i].grui0_name = MBTWC(SvPV(*svTmp, pl_na));
	}

	lastError = NetGroupSetUsers(lpwServer, lpwGroup, 0,
				   (LPBYTE)users, numUsers);

	for (i=0; i<numUsers; i++) freeWideName(users[i].grui0_name);
	freeWideName(lpwGroup);
	freeWideName(lpwServer);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
LocalGroupAdd(server, level, hash, fie)
    char    *server
    int    level
    SV        *hash
    int    fie
PROTOTYPE: $$$$
PREINIT:
    LPBYTE *giX;
CODE:
    {
	DWORD error;
	DWORD lastError = 0;

    LPWSTR lpwServer = MBTWC(server);

	if (!(hash && SvROK(hash) &&
	     (hash = SvRV(hash)) && SvTYPE(hash) == SVt_PVHV))
	    croak("Third argument to LocalGroupAdd() must be a hash reference,");

	giX = allocLocalGroupInfoX(level, (HV*)hash);
	lastError = NetLocalGroupAdd(lpwServer, level, (LPBYTE)giX, &error);
	fie = error;

	freeLocalGroupInfoX(level, giX);
	freeWideName(lpwServer);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    fie
    RETVAL
    
int
LocalGroupAddMembers(server, group, array)
    char    *server
    char    *group
    SV        *array
PROTOTYPE: $$$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwGroup = MBTWC(group);
	LOCALGROUP_MEMBERS_INFO_3    *members;
	int i, len;
	STRLEN pl_na;
	SV    **svTmp;
	DWORD lastError = 0;

	if (!(array && SvROK(array) &&
	     (array = SvRV(array)) && SvTYPE(array) == SVt_PVAV))
	    croak("Third argument to LocalGroupAddMembers() must be an array reference,");

	len = av_len((AV*)array)+1;

	Newz(0, members, len, LOCALGROUP_MEMBERS_INFO_3);

	for (i=0; i<len; i++) {
	    svTmp = av_fetch((AV*)array, i, 0);
	    if (*svTmp)
		members[i].lgrmi3_domainandname = MBTWC(SvPV(*svTmp, pl_na));
	}
	
	lastError = NetLocalGroupAddMembers(lpwServer, lpwGroup, 3,
					    (LPBYTE)members, len);

	for (i=0; i<len; i++) freeWideName(members[i].lgrmi3_domainandname);
	freeWideName(lpwGroup);
	freeWideName(lpwServer);
	Safefree(members);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
LocalGroupDel(server, group)
    char    *server
    char    *group
PROTOTYPE: $$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwGroup = MBTWC(group);
	DWORD lastError = 0;

	lastError = NetLocalGroupDel(lpwServer, lpwGroup);

	freeWideName(lpwGroup);
	freeWideName(lpwServer);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
LocalGroupDelMembers(server, group, array)
    char    *server
    char    *group
    SV        *array
PROTOTYPE: $$$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwGroup = MBTWC(group);
	LOCALGROUP_MEMBERS_INFO_3    *members;
	int totalEntries = items-2;
	int i, len;
	STRLEN pl_na;
	SV    **svTmp;
	DWORD lastError = 0;

	if (!(array && SvROK(array) &&
	     (array = SvRV(array)) && SvTYPE(array) == SVt_PVAV))
	    croak("Third argument to LocalGroupDelMembers() must be an array reference,");

	len = av_len((AV*)array)+1;
	
	Newz(0, members, len, LOCALGROUP_MEMBERS_INFO_3);

	for (i=0; i<len; i++) {
	    svTmp = av_fetch((AV*)array, i, 0);
	    if (*svTmp)
		members[i].lgrmi3_domainandname = MBTWC(SvPV(*svTmp, pl_na));
	}
	
	lastError = NetLocalGroupDelMembers(lpwServer, lpwGroup, 3,
					    (LPBYTE)members, totalEntries);

	for (i; i<len; i++) freeWideName(members[i].lgrmi3_domainandname);
	freeWideName(lpwGroup);
	freeWideName(lpwServer);
	Safefree(members);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
LocalGroupEnum(server, array)
    char    *server
    SV        *array
PROTOTYPE: $$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	PLOCALGROUP_INFO_0 pwzLocalGroups;
	DWORD entriesRead = 0, totalEntries = 0;
        DWORD_PTR resumeHandle = 0;
	DWORD index;
	DWORD lastError = 0;
	char tmpBuf[UNLEN+1];

	if (!(SvROK(array) && (array = SvRV(array))
	      && SvTYPE(array) == SVt_PVAV))
	    croak("Second argument to LocalGroupEnum() must be an array reference,");

	av_clear((AV*)array);
	
	do {
	    lastError = NetLocalGroupEnum(lpwServer, 0,
					  (LPBYTE*)&pwzLocalGroups,
					  PREFLEN, &entriesRead,
					  &totalEntries, &resumeHandle);

	    if (lastError != ERROR_MORE_DATA && lastError != NERR_Success)
		break;

	    for (index = 0; index < entriesRead; ++index) {
		WCTMB(pwzLocalGroups[index].lgrpi0_name,
		      (LPSTR)tmpBuf, sizeof(tmpBuf));
		if (entriesRead == 1 && (strcmp(tmpBuf, "None") == 0))
		    break;
		av_push((AV*)array, newSVpv(tmpBuf, 0));
	    }
	    NetApiBufferFree(pwzLocalGroups);
	} while (resumeHandle != 0);

	freeWideName(lpwServer);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
LocalGroupGetInfo(server, group, level, hash)
    char    *server
    char    *group
    int    level
    SV        *hash
PROTOTYPE: $$$$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwGroup = MBTWC(group);
	LPBYTE *groupInfo = NULL;
	DWORD lastError = 0;
	
	if (!(hash && SvROK(hash) &&
	     (hash = SvRV(hash)) && SvTYPE(hash) == SVt_PVHV))
	    croak("Fourth argument to LocalGroupGetInfo() must be a hash reference,");

	hv_clear((HV*)hash);

	lastError = NetLocalGroupGetInfo(lpwServer, lpwGroup, level,
					 (LPBYTE*)&groupInfo);

	if (lastError == NERR_Success)
	    fillLocalGroupHash((HV*)hash, level, groupInfo);

	NetApiBufferFree(groupInfo);
	freeWideName(lpwGroup);
	freeWideName(lpwServer);
	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
LocalGroupGetMembers(server, group, array)
    char    *server
    char    *group
    SV        *array
PROTOTYPE: $$$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwGroup = MBTWC(group);
	PLOCALGROUP_MEMBERS_INFO_1 pwzMembersInfo;
	DWORD entriesRead = 0, totalEntries = 0;
        DWORD_PTR resumeHandle = 0;
	DWORD index;
	DWORD lastError = 0;
	char tmpBuf[UNLEN+1];

	if (!(array && SvROK(array) &&
	     (array = SvRV(array)) && SvTYPE(array) == SVt_PVAV))
	    croak("Third argument to LocalGroupGetMembers() must be a array reference,");

	av_clear((AV*)array);
	
	do {
	    lastError = NetLocalGroupGetMembers(lpwServer, lpwGroup, 1,
						(LPBYTE*)&pwzMembersInfo,
						PREFLEN, &entriesRead,
						&totalEntries, &resumeHandle);

	    if (lastError != ERROR_MORE_DATA && lastError != NERR_Success)
		break;        /* we have a failure */
		
	    for (index = 0; index < entriesRead; index++) {
		WCTMB(pwzMembersInfo[index].lgrmi1_name,tmpBuf,sizeof(tmpBuf));
		av_push((AV*)array, newSVpv(tmpBuf, 0));
	    }
	    NetApiBufferFree(pwzMembersInfo);
	} while (resumeHandle != 0);
	
	freeWideName(lpwServer);
	freeWideName(lpwGroup);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    RETVAL

int
LocalGroupSetInfo(server, group, level, hash, fie)
    char    *server
    char    *group
    int    level
    SV        *hash
    int    fie
PROTOTYPE: $$$$$
CODE:
    {
	DWORD    error;
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwGroup = MBTWC(group);
	LPBYTE *lgiX;
	DWORD lastError = 0;

	if (!(hash && SvROK(hash) &&
	     (hash = SvRV(hash)) && SvTYPE(hash) == SVt_PVHV))
	    croak("Third argument to LocalGroupSetInfo() must be a hash reference,");

	lgiX = allocLocalGroupInfoX(level, (HV*)hash);

	lastError = NetLocalGroupSetInfo(lpwServer, lpwGroup, level,
				       (LPBYTE)lgiX, &error);
	fie = error;
	freeGroupInfoX(level, lgiX);
	freeWideName(lpwGroup);
	freeWideName(lpwServer);

	RETVAL = (lastError == NERR_Success);
    }
OUTPUT:
    fie
    RETVAL

int
GetDCName(server, domain, primaryDC)
    char *server
    char *domain
    char *primaryDC
PROTOTYPE: $$$
CODE:
    {
	LPWSTR lpwServer = MBTWC(server);
	LPWSTR lpwDomain = MBTWC(domain);
	LPWSTR lpwPrimaryDC = NULL;
	DWORD lastError = 0;
	char tmpBuf[UNLEN+1];

	lastError = NetGetDCName(lpwServer, lpwDomain, (LPBYTE *)&lpwPrimaryDC);

	RETVAL = (lastError == NERR_Success);
	
	WCTMB(lpwPrimaryDC, (LPSTR)tmpBuf, sizeof(tmpBuf));

	primaryDC = tmpBuf;
	
	NetApiBufferFree(lpwPrimaryDC);
	freeWideName(lpwServer);
	freeWideName(lpwDomain);
    }
OUTPUT:
    primaryDC
    RETVAL