/*
 *               Windows to Linux user mapping for ntfs-3g
 *
 *
 * Copyright (c) 2007-2016 Jean-Pierre Andre
 *
 *    A quick'n dirty program scanning owners of files in
 *      "c:\Documents and Settings" (and "c:\Users")
 *      and asking user to map them to Linux accounts
 *
 *          History
 *
 *  Sep 2007
 *     - first version, limited to Win32
 *
 *  Oct 2007
 *     - ported to Linux (rewritten would be more correct)
 *
 *  Nov 2007 Version 1.0.0
 *     - added more defaults
 *
 *  Nov 2007 Version 1.0.1
 *     - avoided examining files whose name begin with a '$'
 *
 *  Jan 2008 Version 1.0.2
 *     - moved user mapping file to directory .NTFS-3G (hidden for Linux)
 *     - fixed an error case in Windows version
 *
 *  Nov 2008 Version 1.1.0
 *     - fixed recursions for account in Linux version
 *     - searched owner in c:\Users (standard location for Vista)
 *
 *  May 2009 Version 1.1.1
 *     - reordered mapping records to limit usage of same SID for user and group
 *     - fixed decoding SIDs on 64-bit systems
 *     - fixed a pointer to dynamic data in mapping tables
 *     - fixed default mapping on Windows
 *     - fixed bug for renaming UserMapping on Windows
 *
 *  May 2009 Version 1.1.2
 *     - avoided selecting DOS names on Linux
 *
 *  Nov 2009 Version 1.1.3
 *     - silenced compiler warnings for unused parameters
 *
 *  Jan 2010 Version 1.1.4
 *     - fixed compilation problems for Mac OSX (Erik Larsson)
 *
 *  Apr 2014 Version 1.1.5
 *     - displayed the parent directory of selected files
 *
 *  May 2014 Version 1.1.6
 *     - fixed a wrong function header
 *
 *  Mar 2016 Version 1.2.0
 *     - reorganized to rely on libntfs-3g even on Windows
 */

/*
 * 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 (in the main directory of the NTFS-3G
 * distribution in the file COPYING); if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 *		General parameters which may have to be adapted to needs
 */

#define USERMAPVERSION "1.2.0"
#define MAPDIR ".NTFS-3G"
#define MAPFILE "UserMapping"
#define MAXATTRSZ 2048
#define MAXSIDSZ 80
#define MAXNAMESZ 256
#define OWNERS1 "Documents and Settings"
#define OWNERS2 "Users"

#include "config.h"

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#include "types.h"
#include "endians.h"
#include "support.h"
#include "layout.h"
#include "param.h"
#include "ntfstime.h"
#include "device_io.h"
#include "device.h"
#include "logging.h"
#include "runlist.h"
#include "mft.h"
#include "inode.h"
#include "attrib.h"
#include "bitmap.h"
#include "index.h"
#include "volume.h"
#include "unistr.h"
#include "mst.h"
#include "security.h"
#include "utils.h"
#include "misc.h"

#ifdef HAVE_WINDOWS_H
/*
 *	Including <windows.h> leads to numerous conflicts with layout.h
 *	so define a few needed Windows calls unrelated to ntfs-3g
 */
BOOL WINAPI LookupAccountNameA(const char*, const char*, void*,
		u32*, char*, u32*, s32*);
BOOL WINAPI GetUserNameA(char*, u32*);
#endif

#ifdef HAVE_WINDOWS_H
#define DIRSEP "\\"
#else
#define DIRSEP "/"
#endif

#ifdef HAVE_WINDOWS_H
#define BANNER "Generated by ntfsusermap for Windows, v " USERMAPVERSION
#else
#define BANNER "Generated by ntfsusermap for Linux, v " USERMAPVERSION
#endif

typedef enum { DENIED, AGREED } boolean;

enum STATES { STATE_USERS, STATE_HOMES, STATE_BASE } ;

struct CALLBACK {
	const char *accname;
	const char *dir;
	int levels;
	enum STATES docset;
} ;

typedef int (*dircallback)(struct CALLBACK *context, char *ntfsname,
	int length, int type, long long pos, unsigned long long mft_ref,
	unsigned int dt_type);


struct USERMAPPING {
	struct USERMAPPING *next;
	const char *uidstr;
	const char *gidstr;
	const char *sidstr;
	const unsigned char *sid;
	const char *login;
	boolean defined;
};

struct USERMAPPING *firstmapping;
struct USERMAPPING *lastmapping;

#ifdef HAVE_WINDOWS_H
char *currentwinname;
char *currentdomain;
unsigned char *currentsid;
#endif

void *ntfs_handle;
void *ntfs_context = (void*)NULL;

/*
 *		Open and close a volume in read-only mode
 *	assuming a single volume needs to be opened at any time
 */

static boolean open_volume(const char *volume)
{
	boolean ok;

	ok = DENIED;
	if (!ntfs_context) {
		ntfs_context = ntfs_initialize_file_security(volume,
						NTFS_MNT_RDONLY);
		if (ntfs_context) {
			fprintf(stderr,"\"%s\" opened\n",volume);
			ok = AGREED;
		} else {
			fprintf(stderr,"Could not open \"%s\"\n",volume);
#ifdef HAVE_WINDOWS_H
			if (errno == EACCES)
				fprintf(stderr,"Make sure you have"
					" Administrator rights\n");
#else
			fprintf(stderr,"Make sure \"%s\" is not mounted\n",
				volume);
#endif
		}
	} else
		fprintf(stderr,"A volume is already open\n");
	return (ok);
}

static boolean close_volume(const char *volume)
{
	boolean r;

	r = ntfs_leave_file_security(ntfs_context) ? AGREED : DENIED;
	if (r)
		fprintf(stderr,"\"%s\" closed\n",volume);
	else
		fprintf(stderr,"Could not close \"%s\"\n",volume);
	ntfs_context = (void*)NULL;
	return (r);
}

/*
 *		A poor man's conversion of Unicode to UTF8
 *	We are assuming outputs to terminal expect UTF8
 */

static void to_utf8(char *dst, const char *src, unsigned int cnt)
{
	unsigned int ch;
	unsigned int i;

	for (i=0; i<cnt; i++) {
		ch = *src++ & 255;
		ch += (*src++ & 255) << 8;
		if (ch < 0x80)
			*dst++ = ch;
		else
			if (ch < 0x1000) {
				*dst++ = 0xc0 + (ch >> 6);
				*dst++ = 0x80 + (ch & 63);
			} else {
				*dst++ = 0xe0 + (ch >> 12);
				*dst++ = 0x80 + ((ch >> 6) & 63);
				*dst++ = 0x80 + (ch & 63);
			}
	}
	*dst = 0;
}

static int utf8_size(const char *src, unsigned int cnt)
{
	unsigned int ch;
	unsigned int i;
	int size;

	size = 0;
	for (i=0; i<cnt; i++) {
		ch = *src++ & 255;
		ch += (*src++ & 255) << 8;
		if (ch < 0x80)
			size++;
		else
			if (ch < 0x1000)
				size += 2;
			else
				size += 3;
	}
	return (size);
}

static void welcome(void)
{
	printf("\nThis tool will help you to build a mapping of"
			" Windows users\n");
	printf("to Linux users.\n");
	printf("Be prepared to give Linux user id (uid) and group id (gid)\n");
	printf("for owners of files which will be selected.\n");
}

static unsigned int get2l(const unsigned char *attr, int p)
{
	int i;
	unsigned int v;

	v = 0;
	for (i = 0; i < 2; i++)
		v += (attr[p + i] & 255) << (8 * i);
	return (v);
}

static unsigned long get4l(const unsigned char *attr, int p)
{
	int i;
	unsigned long v;

	v = 0;
	for (i = 0; i < 4; i++)
		v += (attr[p + i] & 255L) << (8 * i);
	return (v);
}

static unsigned long long get6h(const unsigned char *attr, int p)
{
	int i;
	unsigned long long v;

	v = 0;
	for (i = 0; i < 6; i++)
		v = (v << 8) + (attr[p + i] & 255L);
	return (v);
}

static char *decodesid(const unsigned char *sid)
{
	char *str;
	int i;
	unsigned long long auth;
	unsigned long subauth;

	str = (char *)malloc(MAXSIDSZ);
	if (str) {
		strcpy(str, "S");
							/* revision */
		sprintf(&str[strlen(str)], "-%d", sid[0]);
							/* main authority */
		auth = get6h(sid, 2);
#ifdef HAVE_WINDOWS_H
		sprintf(&str[strlen(str)], "-%I64u", auth);
#else
		sprintf(&str[strlen(str)], "-%llu", auth);
#endif
		for (i = 0; (i < 8) && (i < sid[1]); i++) {
							/* sub-authority */
			subauth = get4l(sid, 8 + 4 * i);
			sprintf(&str[strlen(str)], "-%lu", subauth);
		}
	}
	return (str);
}

/*
 *        Test whether a generic group (S-1-5-21- ... -513)
 */

static boolean isgenericgroup(const char *sid)
{
	boolean yes;

	yes = !strncmp(sid,"S-1-5-21-",9)
		&& !strcmp(strrchr(sid,'-'),"-513");
	return (yes);
}

static unsigned char *makegroupsid(const unsigned char *sid)
{
	static unsigned char groupsid[MAXSIDSZ];
	int size;

	size = 8 + 4*sid[1];
	memcpy(groupsid, sid, size);
		/* replace last level by 513 */
	groupsid[size - 4] = 1;
	groupsid[size - 3] = 2;
	groupsid[size - 2] = 0;
	groupsid[size - 1] = 0;
	return (groupsid);
}

static void askmapping(const char *accname, const char *filename,
		const char *dir, const unsigned char *sid, int type,
		struct USERMAPPING *mapping, char *sidstr)
{
	char buf[81];
	char *idstr;
	char *login;
	int sidsz;
	boolean reject;
	char *p;

	do {
		reject = DENIED;
		printf("\n");
		if (accname)
			printf("Under Windows login \"%s\"\n", accname);
		if (dir) {
#ifdef HAVE_WINDOWS_H
			char *wdir;

			wdir = strdup(dir);
			if (wdir) {
				for (p=wdir; *p; p++)
					if (*p == '/')
						*p = '\\';
				printf("   in directory \"%s\"\n",wdir);
				free(wdir);
			}
#else
			printf("   in directory \"%s\"\n",dir);
#endif
		}
		printf("   file \"%s\" has no mapped %s\n",
			       filename,(type ? "group" : "owner"));
		printf("By which Linux login should this file be owned ?\n");
		printf("Enter %s of login, or just press \"enter\" if"
			" this file\n", (type ? "gid" : "uid"));
		printf("does not belong to a user, or you do not know"
			" to whom\n");
		printf("\n");
		if (type)
			printf("Group : ");
		else
			printf("User : ");
		p = fgets(buf, 80, stdin);
		if (p && p[0] && (p[strlen(p) - 1] == '\n'))
			p[strlen(p) - 1] = '\0';

		if (p && p[0]
			 && ((p[0] == '0') || !strcmp(p, "root"))) {
			printf("Please do not map users to root\n");
			printf("Administrators will be mapped automatically\n");
			reject = AGREED;
		}
		if (reject)
			printf("Please retry\n");
	} while (reject);
	if (!mapping) {
		mapping =
		    (struct USERMAPPING *)
		    malloc(sizeof(struct USERMAPPING));
		mapping->next = (struct USERMAPPING *)NULL;
		mapping->defined = DENIED;
		if (lastmapping)
			lastmapping->next = mapping;
		else
			firstmapping = mapping;
		lastmapping = mapping;
	}
	if (mapping) {
		if (p && p[0]) {
			idstr = (char *)malloc(strlen(p) + 1);
			if (idstr) {
				strcpy(idstr, p);
				if (type) {
					mapping->uidstr = "";
					mapping->gidstr = idstr;
				} else {
					mapping->uidstr = idstr;
					mapping->gidstr = idstr;
				}
				mapping->defined = AGREED;
			}
		}
		mapping->sidstr = sidstr;
		if (accname) {
			login = (char*)malloc(strlen(accname) + 1);
			if (login)
				strcpy(login,accname);
			mapping->login = login;
		} else
			mapping->login = (char*)NULL;
		sidsz = 8 + sid[1]*4;
		p = (char*)malloc(sidsz);
		if (p) {
			memcpy(p, sid, sidsz);
		}
		mapping->sid = (unsigned char*)p;
	}
}

static void domapping(const char *accname, const char *filename,
		const char *dir, const unsigned char *sid, int type)
{
	char *sidstr;
	struct USERMAPPING *mapping;

	if ((get6h(sid, 2) == 5) && (get4l(sid, 8) == 21)) {
		sidstr = decodesid(sid);
		mapping = firstmapping;
		while (mapping && strcmp(mapping->sidstr, sidstr))
			mapping = mapping->next;
		if (mapping
		    && (mapping->defined
			 || !accname
			 || !strcmp(mapping->login, accname)))
			free(sidstr);	/* decision already known */
		else {
			askmapping(accname, filename, dir, sid, type,
					mapping, sidstr);
		}
	}
}

static void listaclusers(const char *accname, const unsigned char *attr,
				int off)
{
	int i;
	int cnt;
	int x;

	cnt = get2l(attr, off + 4);
	x = 8;
	for (i = 0; i < cnt; i++) {
		domapping(accname, (char *)NULL, (char*)NULL,
                                       &attr[off + x + 8], 2);
		x += get2l(attr, off + x + 2);
	}
}

static void account(const char *accname, const char *dir,
			const char *name, int type)
{
	unsigned char attr[MAXATTRSZ];
	u32 attrsz;
	char *fullname;

	fullname = (char *)malloc(strlen(dir) + strlen(name) + 2);
	if (fullname) {
		strcpy(fullname, dir);
		strcat(fullname, "/");
		strcat(fullname, name);
		if (ntfs_get_file_security(ntfs_context,
				fullname, OWNER_SECURITY_INFORMATION,
				(char*)attr, MAXATTRSZ, &attrsz)) {
			domapping(accname, name, dir, &attr[20], 0);
			attrsz = 0;
			if (ntfs_get_file_security(ntfs_context,
			     fullname, GROUP_SECURITY_INFORMATION,
			     (char*)attr, MAXATTRSZ, &attrsz))
				domapping(accname, name, dir, &attr[20], 1);
			else
				printf("   No group SID\n");
			attrsz = 0;
			if (ntfs_get_file_security(ntfs_context,
			     fullname, DACL_SECURITY_INFORMATION,
			     (char*)attr, MAXATTRSZ, &attrsz)) {
				if (type == 0)
					listaclusers(accname, attr, 20);
			} else
				printf("   No discretionary access control"
					" list for %s !\n", dir);
		}
	free(fullname);
	}
}

/*
 *		recursive search of file owners and groups in a directory
 */

static boolean recurse(const char *accname, const char *dir,
			int levels, enum STATES docset);

static int callback(void *ctx, const ntfschar *ntfsname,
		const int length, const int type,
		const s64 pos __attribute__((unused)),
		const MFT_REF mft_ref __attribute__((unused)),
		const unsigned int dt_type __attribute__((unused)))
{
	struct CALLBACK *context;
	char *fullname;
	char *accname;
	char *name;

	context = (struct CALLBACK*)ctx;
	fullname = (char *)malloc(strlen(context->dir)
			 + utf8_size((const char*)ntfsname, length) + 2);
	if (fullname) {
			/* No "\\" when interfacing libntfs-3g */
		if (strcmp(context->dir,"/")) {
			strcpy(fullname, context->dir);
			strcat(fullname, "/");
		} else
			strcpy(fullname,"/");
			/* Unicode to ascii conversion by a lazy man */
		name = &fullname[strlen(fullname)];
		to_utf8(name, (const char*)ntfsname, length);
			/* ignore special files and DOS names */
		if ((type != 2)
		   && strcmp(name,".")
		   && strcmp(name,"..")
		   && (name[0] != '$')) {
			switch (context->docset) {
			case STATE_USERS :
					/*
					 * only "Documents and Settings"
					 * or "Users"
					 */
				if (!strcmp(name,OWNERS1)
				   || !strcmp(name,OWNERS2)) {
					recurse((char*)NULL, fullname, 2,
							STATE_HOMES);
				}
				break;
					/*
					 * within "Documents and Settings"
					 * or "Users"
					 */
			case STATE_HOMES :
				accname = (char*)malloc(strlen(name) + 1);
				if (accname) {
					strcpy(accname, name);
					if (context->levels > 0)
						recurse(name, fullname,
							context->levels - 1,
							STATE_BASE);
				}
				break;
					/*
					 * not related to "Documents and
					 * Settings" or "Users"
					 */
			case STATE_BASE :
				account(context->accname, context->dir,
					name, 1);
				if (context->levels > 0)
					recurse(context->accname, fullname,
						context->levels - 1,
						STATE_BASE);
				break;
			}
		}
		free(fullname);
	}
/* check expected return value */
	return (0);
}

static boolean recurse(const char *accname, const char *dir,
			int levels, enum STATES docset)
{
	struct CALLBACK context;
	boolean err;

	err = DENIED;
	context.dir = dir;
	context.accname = accname;
	context.levels = levels;
	context.docset = docset;
	ntfs_read_directory(ntfs_context,dir,callback,&context);
	return (!err);
}

/*
 *          Search directory "Documents and Settings" for user accounts
 */

static boolean getusers(const char *dir, int levels)
{
	boolean err;
	struct CALLBACK context;

	printf("* Search for \"" OWNERS1 "\" and \"" OWNERS2 "\"\n");
	err = DENIED;
	context.dir = dir;
	context.accname = (const char*)NULL;
	context.levels = levels;
	context.docset = STATE_USERS;
	ntfs_read_directory(ntfs_context,dir,callback,&context);
	printf("* Search for other directories %s\n",dir);
	context.docset = STATE_BASE;
	ntfs_read_directory(ntfs_context,dir,callback,&context);
	return (!err);
}

#ifdef HAVE_WINDOWS_H
/*
 *		Get the current login name (Win32 only)
 */

static void loginname(boolean silent)
{
	char *winname;
	char *domain;
	unsigned char *sid;
	u32 namesz;
	u32 sidsz;
	u32 domainsz;
	s32 nametype;
	boolean ok;
	int r;

	ok = FALSE;
	winname = (char*)malloc(MAXNAMESZ);
	domain = (char*)malloc(MAXNAMESZ);
	sid = (char*)malloc(MAXSIDSZ);

	namesz = MAXNAMESZ;
	domainsz = MAXNAMESZ;
	sidsz = MAXSIDSZ;
	if (winname
	    && domain
	    && sid
	    && GetUserNameA(winname,&namesz)) {
		winname[namesz] = '\0';
		if (!silent)
			printf("Your current user name is %s\n",winname);
		nametype = 1;
		r = LookupAccountNameA((char*)NULL,winname,sid,&sidsz,
			domain,&domainsz,&nametype);
		if (r) {
			domain[domainsz] = '\0';
			if (!silent)
				printf("Your account domain is %s\n",domain);
			ok = AGREED;
		}
	   }
	if (ok) {
		currentwinname = winname;
		currentdomain = domain;
		currentsid = sid;
	} else {
		currentwinname = (char*)NULL;
		currentdomain = (char*)NULL;
		currentsid = (unsigned char*)NULL;
	}
}

/*
 *		Minimal output on stdout
 */

static boolean minimal(unsigned char *sid)
{
	const unsigned char *groupsid;
	boolean ok;

	ok = DENIED;
	if (sid) {
		groupsid = makegroupsid(sid);
		printf("# %s\n",BANNER);
		printf("# For Windows account \"%s\" in domain \"%s\"\n",
			currentwinname, currentdomain);
		printf("# Replace \"user\" and \"group\" hereafter by"
			" matching Linux login\n");
		printf("user::%s\n",decodesid(sid));
		printf(":group:%s\n",decodesid(groupsid));
		ok = AGREED;
	}
	return (ok);
}

#endif

/*
 *		Create a user mapping file
 *
 *	From now on, partitions which were opened through ntfs-3g
 *	are closed, and we use the system drivers to create the file.
 *	On Windows, we can write on a partition which was analyzed.
 */

static boolean outputmap(const char *volume, const char *dir)
{
	char buf[256];
	int fn;
	char *fullname;
	char *backup;
	struct USERMAPPING *mapping;
	boolean done;
	boolean err;
	boolean undecided;
	struct stat st;
	int s;

	done = DENIED;
	fullname = (char *)malloc(strlen(MAPFILE) + 1
				+ strlen(volume) + 1
				+ (dir ? strlen(dir) + 1 : 0));
	if (fullname) {
#ifdef HAVE_WINDOWS_H
		strcpy(fullname, volume);
		if (dir && dir[0]) {
			strcat(fullname, DIRSEP);
			strcat(fullname,dir);
		}

			/* build directory, if not present */
		if (stat(fullname,&st) && (errno == ENOENT)) {
			printf("* Creating directory %s\n", fullname);
			mkdir(fullname);
		}

		strcat(fullname, DIRSEP);
		strcat(fullname, MAPFILE);
		printf("\n");
#else
		strcpy(fullname, MAPFILE);
		printf("\n");
#endif

		s = stat(fullname,&st);
		if (!s) {
			backup = (char*)malloc(strlen(fullname + 5));
			strcpy(backup,fullname);
			strcat(backup,".bak");
#ifdef HAVE_WINDOWS_H
			unlink(backup);
#endif
			if (rename(fullname,backup))
				printf("* Old mapping file moved to %s\n",
					backup);
		}

		printf("* Creating file %s\n", fullname);
		err = DENIED;
#ifdef HAVE_WINDOWS_H
		fn = open(fullname,O_CREAT + O_TRUNC + O_WRONLY + O_BINARY,
			S_IREAD + S_IWRITE);
#else
		fn = open(fullname,O_CREAT + O_TRUNC + O_WRONLY,
			S_IREAD + S_IWRITE);
#endif
		if (fn > 0) {
			sprintf(buf,"# %s\n",BANNER);
			if (!write(fn,buf,strlen(buf)))
				err = AGREED;
			printf("%s",buf);
			undecided = DENIED;
				/* records for owner only or group only */
			for (mapping = firstmapping; mapping && !err;
			     mapping = mapping->next)
				if (mapping->defined
				    && (!mapping->uidstr[0]
					    || !mapping->gidstr[0])) {
					sprintf(buf,"%s:%s:%s\n",
						mapping->uidstr,
						mapping->gidstr,
						mapping->sidstr);
					if (!write(fn,buf,strlen(buf)))
						err = AGREED;
					printf("%s",buf);
				} else
					undecided = AGREED;
				/* records for both owner and group */
			for (mapping = firstmapping; mapping && !err;
			     mapping = mapping->next)
				if (mapping->defined
				    && mapping->uidstr[0]
				    && mapping->gidstr[0]) {
					sprintf(buf,"%s:%s:%s\n",
						mapping->uidstr,
						mapping->gidstr,
						mapping->sidstr);
					if (!write(fn,buf,strlen(buf)))
						err = AGREED;
					printf("%s",buf);
				} else
					undecided = AGREED;
			done = !err;
			close(fn);
			if (undecided) {
				printf("Undecided :\n");
				for (mapping = firstmapping; mapping;
				     mapping = mapping->next)
					if (!mapping->defined) {
						printf("   %s\n",
						mapping->sidstr);
					}
			}
#ifndef HAVE_WINDOWS_H
			printf("\n* You will have to move the file \""
					MAPFILE "\"\n");
			printf("  to directory \"" MAPDIR "\" after"
					" mounting\n");
#endif
		}
	}
	if (!done)
		fprintf(stderr, "* Could not create mapping file \"%s\"\n",
				fullname);
	return (done);
}

static boolean sanitize(void)
{
	char buf[81];
	boolean ok;
	int ownercnt;
	int groupcnt;
	struct USERMAPPING *mapping;
	struct USERMAPPING *firstowner;
	struct USERMAPPING *genericgroup;
	struct USERMAPPING *group;
	char *sidstr;

		/* count owners and groups */
		/* and find first user, and a generic group */
	ownercnt = 0;
	groupcnt = 0;
	firstowner = (struct USERMAPPING*)NULL;
	genericgroup = (struct USERMAPPING*)NULL;
	for (mapping=firstmapping; mapping; mapping=mapping->next) {
		if (mapping->defined && mapping->uidstr[0]) {
			if (!ownercnt)
				firstowner = mapping;
			ownercnt++;
		}
		if (mapping->defined && mapping->gidstr[0]
					&& !mapping->uidstr[0]) {
			groupcnt++;
		}
		if (!mapping->defined && isgenericgroup(mapping->sidstr)) {
			genericgroup = mapping;
		}
	}
#ifdef HAVE_WINDOWS_H
		/* no user defined, on Windows, suggest a mapping */
		/* based on account currently used */
	if (!ownercnt && currentwinname && currentsid) {
		char *owner;
		char *p;

		printf("\nYou have defined no file owner,\n");
		printf("   please enter the Linux login which should"
			" be mapped\n");
		printf("   to account you are currently using\n");
		printf("   Linux user ? ");
		p = fgets(buf, 80, stdin);
		if (p && p[0] && (p[strlen(p) - 1] == '\n'))
			p[strlen(p) - 1] = '\0';
		if (p && p[0]) {
			firstowner = (struct USERMAPPING*)malloc(
						sizeof(struct USERMAPPING));
			owner = (char*)malloc(strlen(p) + 1);
			if (firstowner && owner) {
				strcpy(owner, p);
				firstowner->next = firstmapping;
				firstowner->uidstr = owner;
				firstowner->gidstr = "";
				firstowner->sidstr = decodesid(currentsid);
				firstowner->sid = currentsid;
				firstmapping = firstowner;
				ownercnt++;
				/* prefer a generic group with the same
				 * authorities */
				for (mapping=firstmapping; mapping;
						mapping=mapping->next)
					if (!mapping->defined
					    && isgenericgroup(mapping->sidstr)
					    && !memcmp(firstowner->sidstr,
							mapping->sidstr,
							strlen(mapping
								->sidstr)-3))
						genericgroup = mapping;
			}
		}
	}
#endif
	if (ownercnt) {
			/*
			 *   No group was selected, but there were a generic
			 *   group, insist in using it, associated to the
			 *   first user
			 */
		if (!groupcnt) {
			printf("\nYou have defined no group,"
					" this can cause problems\n");
			printf("Do you accept defining a standard group ?\n");
			if (!fgets(buf,80,stdin)
			   || ((buf[0] != 'n')
			      && (buf[0] != 'N'))) {
				if (genericgroup) {
					genericgroup->uidstr = "";
					genericgroup->gidstr =
							firstowner->uidstr;
					genericgroup->defined = AGREED;
				} else {
					group = (struct USERMAPPING*)
						malloc(sizeof(
							struct USERMAPPING));
					sidstr = decodesid(
						makegroupsid(firstowner->sid));
					if (group && sidstr) {
						group->uidstr = "";
						group->gidstr = firstowner->
								uidstr;
						group->sidstr = sidstr;
						group->defined = AGREED;
						group->next = firstmapping;
						firstmapping = group;
					}
				}
			}
		}
		ok = AGREED;
	} else {
		printf("\nYou have defined no user, no mapping can be built\n");
		ok = DENIED;
	}

	return (ok);
}

static boolean checkoptions(int argc, char *argv[] __attribute__((unused)),
				boolean silent __attribute__((unused)))
{
	boolean err;
#ifdef HAVE_WINDOWS_H
	int xarg;
	const char *pvol;

	if (silent) {
		err = (argc != 1);
	} else {
		err = (argc < 2);
		for (xarg=1; (xarg<argc) && !err; xarg++) {
			pvol = argv[xarg];
			if (pvol[0] && (pvol[1] == ':') && !pvol[2]) {
				err = !(((pvol[0] >= 'A') && (pvol[0] <= 'Z'))
					|| ((pvol[0] >= 'a')
						&& (pvol[0] <= 'z')));
			}
		}
	}
	if (err) {
		fprintf(stderr, "Usage : ntfsusermap [vol1: [vol2: ...]]\n");
		fprintf(stderr, "    \"voln\" are the letters of the partition"
				" to share with Linux\n");
		fprintf(stderr, "        eg C:\n");
		fprintf(stderr, "    the Windows system partition should be"
				" named first\n");
		if (silent) {
			fprintf(stderr, "When outputting to file, a minimal"
					" user mapping proposal\n");
			fprintf(stderr, "is written to the file, and no"
					" partition should be mentioned\n");
		}
	}
#else
	err = (argc < 2);
	if (err) {
		fprintf(stderr, "Usage : ntfsusermap dev1 [dev2 ...]\n");
		fprintf(stderr, "    \"dev.\" are the devices to share"
				" with Windows\n");
		fprintf(stderr, "        eg /dev/sdb1\n");
		fprintf(stderr, "    the devices should not be mounted, and\n");
		fprintf(stderr, "    the Windows system partition should"
				" be named first\n");
	} else
		if (getuid()) {
			fprintf(stderr, "\nSorry, only root can start"
					" ntfsusermap\n");
			err = AGREED;
		}
#endif
	return (!err);
}

static boolean process(int argc, char *argv[])
{
	boolean ok;
	int xarg;
	int targ;

	firstmapping = (struct USERMAPPING *)NULL;
	lastmapping = (struct USERMAPPING *)NULL;
	ok = AGREED;
	for (xarg=1; (xarg<argc) && ok; xarg++)
		if (open_volume(argv[xarg])) {
			printf("\n* Scanning \"%s\" (two levels)\n",argv[xarg]);
			ok = getusers("/",2);
			close_volume(argv[xarg]);
		} else
			ok = DENIED;
	if (ok && sanitize()) {
		targ = (argc > 2 ? 2 : 1);
		if (!outputmap(argv[targ],MAPDIR)) {
			printf("Trying to write file on root directory\n");
			if (outputmap(argv[targ],(const char*)NULL)) {
				printf("\nNote : you will have to move the"
					" file to directory \"%s\" on Linux\n",
					MAPDIR);
			} else
				ok = DENIED;
		} else
			ok = DENIED;
	} else
		ok = DENIED;
	return (ok);
}

int main(int argc, char *argv[])
{
	boolean ok;
	boolean silent;

	silent = !isatty(1);
	if (!silent)
		welcome();
	if (checkoptions(argc, argv, silent)) {
#ifdef HAVE_WINDOWS_H
		loginname(silent);
		if (silent)
			ok = minimal(currentsid);
		else
			ok = process(argc,argv);
#else
		ok = process(argc,argv);
#endif
	} else
		ok = DENIED;
	if (!ok)
		exit(1);
	return (0);
}
