/* * 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 #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_TIME_H #include #endif #ifdef HAVE_GETOPT_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_STAT_H #include #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 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); 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> 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; inext = (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= '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 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); }