fix permissions so user can write mounted volumes on linux
[nemesis.git] / Core / Unix / CoreUnix.cpp
1 /*
2  Copyright (c) 2016 nemesis project/mrn@sdf.org. All rights reserved.
3  http://mrn.sixbit.org/
4
5  Governed by the BSD 2 Clause license, the full text of which is contained in
6  the file License.txt included in nemesis binary and source code distribution
7  packages.
8  Based on TrueCrypt 7.1a, which was governed by the TrueCrypt license, which
9  is also made available with nemesis.
10 */
11
12 #include "CoreUnix.h"
13 #include <errno.h>
14 #include <iostream>
15 #include <signal.h>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #include <stdio.h>
19 #include <unistd.h>
20 #include "Platform/FileStream.h"
21 #include "Driver/Fuse/FuseService.h"
22 #include "Volume/VolumePasswordCache.h"
23
24 namespace nemesis
25 {
26         CoreUnix::CoreUnix ()
27         {
28                 signal (SIGPIPE, SIG_IGN);
29                 
30                 char *loc = setlocale (LC_ALL, "");
31                 if (!loc || string (loc) == "C")
32                         setlocale (LC_ALL, "en_US.UTF-8");
33         }
34
35         CoreUnix::~CoreUnix ()
36         {
37         }
38         
39         void CoreUnix::CheckFilesystem (shared_ptr <VolumeInfo> mountedVolume, bool repair) const
40         {
41                 if (!mountedVolume->MountPoint.IsEmpty())
42                         DismountFilesystem (mountedVolume->MountPoint, false);
43
44                 list <string> args;
45
46                 args.push_back ("-T");
47                 args.push_back ("fsck");
48
49                 args.push_back ("-e");
50
51                 string xargs = "fsck ";
52
53 #ifdef TC_LINUX
54                 if (!repair)
55                         xargs += "-n ";
56                 else
57                         xargs += "-r ";
58 #endif
59
60                 xargs += string (mountedVolume->VirtualDevice) + "; echo '[Done]'; read W";
61                 args.push_back (xargs);
62
63                 try
64                 {
65                         Process::Execute ("xterm", args, 1000);
66                 } catch (TimeOut&) { }
67         }
68
69         void CoreUnix::DismountFilesystem (const DirectoryPath &mountPoint, bool force) const
70         {
71                 list <string> args;
72
73 #ifdef TC_MACOSX
74                 if (force)
75                         args.push_back ("-f");
76 #endif
77                 args.push_back ("--");
78                 args.push_back (mountPoint);
79
80                 Process::Execute ("umount", args);
81         }
82
83         shared_ptr <VolumeInfo> CoreUnix::DismountVolume (shared_ptr <VolumeInfo> mountedVolume, bool ignoreOpenFiles, bool syncVolumeInfo)
84         {
85                 if (!mountedVolume->MountPoint.IsEmpty())
86                 {
87                         DismountFilesystem (mountedVolume->MountPoint, ignoreOpenFiles);
88
89                         // Delete mount directory if a default path has been used
90                         if (string (mountedVolume->MountPoint).find (GetDefaultMountPointPrefix()) == 0)
91                                 mountedVolume->MountPoint.Delete();
92                 }
93
94                 try
95                 {
96                         DismountNativeVolume (mountedVolume);
97                 }
98                 catch (NotApplicable &) { }
99
100                 if (!mountedVolume->LoopDevice.IsEmpty())
101                 {
102                         try
103                         {
104                                 DetachLoopDevice (mountedVolume->LoopDevice);
105                         }
106                         catch (ExecutedProcessFailed&) { }
107                 }
108
109                 if (syncVolumeInfo || mountedVolume->Protection == VolumeProtection::HiddenVolumeReadOnly)
110                 {
111                         sync();
112                         VolumeInfoList ml = GetMountedVolumes (mountedVolume->Path);
113
114                         if (ml.size() > 0)
115                                 mountedVolume = ml.front();
116                 }
117
118                 list <string> args;
119                 args.push_back ("--");
120                 args.push_back (mountedVolume->AuxMountPoint);
121
122                 for (int t = 0; true; t++)
123                 {
124                         try
125                         {
126                                 Process::Execute ("umount", args);
127                                 break;
128                         }
129                         catch (ExecutedProcessFailed&)
130                         {
131                                 if (t > 10)
132                                         throw;
133                                 Thread::Sleep (200);
134                         }
135                 }
136
137                 try
138                 {
139                         mountedVolume->AuxMountPoint.Delete();
140                 }
141                 catch (...)     { }
142
143                 VolumeEventArgs eventArgs (mountedVolume);
144                 VolumeDismountedEvent.Raise (eventArgs);
145
146                 return mountedVolume;
147         }
148
149         bool CoreUnix::FilesystemSupportsLargeFiles (const FilePath &filePath) const
150         {
151                 string path = filePath;
152                 size_t pos;
153                 
154                 while ((pos = path.find_last_of ('/')) != string::npos)
155                 {
156                         path = path.substr (0, pos);
157
158                         if (path.empty())
159                                 break;
160
161                         try
162                         {
163                                 MountedFilesystemList filesystems = GetMountedFilesystems (DevicePath(), path);
164                                 if (!filesystems.empty())
165                                 {
166                                         const MountedFilesystem &fs = *filesystems.front();
167
168                                         if (fs.Type == "fat"
169                                                 || fs.Type == "fat32"
170                                                 || fs.Type == "vfat"
171                                                 || fs.Type == "fatfs"
172                                                 || fs.Type == "msdos"
173                                                 || fs.Type == "msdosfs"
174                                                 || fs.Type == "umsdos"
175                                                 || fs.Type == "dos"
176                                                 || fs.Type == "dosfs"
177                                                 || fs.Type == "pcfs"
178                                                 )
179                                         {
180                                                 return false;
181                                         }
182
183                                         return true;
184                                 }
185                         }
186                         catch (...) { }
187                 }
188
189                 return true;    // Prevent errors if the filesystem cannot be identified
190         }
191
192         bool CoreUnix::FilesystemSupportsUnixPermissions (const DevicePath &devicePath) const
193         {
194                 File device;
195                 device.Open (devicePath);
196
197                 Buffer bootSector (device.GetDeviceSectorSize());
198                 device.SeekAt (0);
199                 device.ReadCompleteBuffer (bootSector);
200
201                 byte *b = bootSector.Ptr(); 
202
203                 return memcmp (b + 3,  "NTFS", 4) != 0
204                         && memcmp (b + 54, "FAT", 3) != 0
205                         && memcmp (b + 82, "FAT32", 5) != 0
206                         && memcmp (b + 3,  "EXFAT", 5) != 0;
207         }
208
209         string CoreUnix::GetDefaultMountPointPrefix () const
210         {
211                 const char *envPrefix = getenv ("TRUECRYPT_MOUNT_PREFIX");
212                 if (envPrefix && !string (envPrefix).empty())
213                         return envPrefix;
214                 
215                 if (FilesystemPath ("/media").IsDirectory())
216                         return "/media/truecrypt";
217                 
218                 if (FilesystemPath ("/mnt").IsDirectory())
219                         return "/mnt/truecrypt";
220                 
221                 return GetTempDirectory() + "/truecrypt_mnt";
222         }
223
224         uint32 CoreUnix::GetDeviceSectorSize (const DevicePath &devicePath) const
225         {
226                 File dev;
227                 dev.Open (devicePath);
228                 return dev.GetDeviceSectorSize();
229         }
230
231         uint64 CoreUnix::GetDeviceSize (const DevicePath &devicePath) const
232         {
233                 File dev;
234                 dev.Open (devicePath);
235                 return dev.Length();
236         }
237
238         DirectoryPath CoreUnix::GetDeviceMountPoint (const DevicePath &devicePath) const
239         {
240                 DevicePath devPath = devicePath;
241 #ifdef TC_MACOSX
242                 if (string (devPath).find ("/dev/rdisk") != string::npos)
243                         devPath = string ("/dev/") + string (devicePath).substr (6);
244 #endif
245                 MountedFilesystemList mountedFilesystems = GetMountedFilesystems (devPath);
246
247                 if (mountedFilesystems.size() < 1)
248                         return DirectoryPath();
249
250                 return mountedFilesystems.front()->MountPoint;
251         }
252
253         VolumeInfoList CoreUnix::GetMountedVolumes (const VolumePath &volumePath) const
254         {
255                 VolumeInfoList volumes;
256
257                 foreach_ref (const MountedFilesystem &mf, GetMountedFilesystems ())
258                 {
259                         if (string (mf.MountPoint).find (GetFuseMountDirPrefix()) == string::npos)
260                                 continue;
261
262                         shared_ptr <VolumeInfo> mountedVol;
263                         try
264                         {
265                                 shared_ptr <File> controlFile (new File);
266                                 controlFile->Open (string (mf.MountPoint) + FuseService::GetControlPath());
267
268                                 shared_ptr <Stream> controlFileStream (new FileStream (controlFile));
269                                 mountedVol = Serializable::DeserializeNew <VolumeInfo> (controlFileStream);
270                         }
271                         catch (...)
272                         {
273                                 continue;
274                         }
275                         
276                         if (!volumePath.IsEmpty() && wstring (mountedVol->Path).compare (volumePath) != 0)
277                                 continue;
278
279                         mountedVol->AuxMountPoint = mf.MountPoint;
280
281                         if (!mountedVol->VirtualDevice.IsEmpty())
282                         {
283                                 MountedFilesystemList mpl = GetMountedFilesystems (mountedVol->VirtualDevice);
284
285                                 if (mpl.size() > 0)
286                                         mountedVol->MountPoint = mpl.front()->MountPoint;
287                         }
288
289                         volumes.push_back (mountedVol);
290
291                         if (!volumePath.IsEmpty())
292                                 break;
293                 }
294
295                 return volumes;
296         }
297         
298         gid_t CoreUnix::GetRealGroupId () const
299         {
300                 const char *env = getenv ("SUDO_GID");
301                 if (env)
302                 {
303                         try
304                         {
305                                 string s (env);
306                                 return static_cast <gid_t> (StringConverter::ToUInt64 (s));
307                         }
308                         catch (...) { }
309                 }
310
311                 return getgid();
312         }
313
314         uid_t CoreUnix::GetRealUserId () const
315         {
316                 const char *env = getenv ("SUDO_UID");
317                 if (env)
318                 {
319                         try
320                         {
321                                 string s (env);
322                                 return static_cast <uid_t> (StringConverter::ToUInt64 (s));
323                         }
324                         catch (...) { }
325                 }
326
327                 return getuid();
328         }
329         
330         string CoreUnix::GetTempDirectory () const
331         {
332                 char *envDir = getenv ("TMPDIR");
333                 return envDir ? envDir : "/tmp";
334         }
335
336         bool CoreUnix::IsMountPointAvailable (const DirectoryPath &mountPoint) const
337         {
338                 return GetMountedFilesystems (DevicePath(), mountPoint).size() == 0;
339         }
340
341         void CoreUnix::MountFilesystem (const DevicePath &devicePath, const DirectoryPath &mountPoint, const string &filesystemType, bool readOnly, const string &systemMountOptions) const
342         {
343                 if (GetMountedFilesystems (DevicePath(), mountPoint).size() > 0)
344                         throw MountPointUnavailable (SRC_POS);
345
346                 list <string> args;
347                 string options;
348
349                 if (!filesystemType.empty())
350                 {
351 #ifdef TC_SOLARIS
352                         args.push_back ("-F");
353 #else
354                         args.push_back ("-t");
355 #endif
356                         args.push_back (filesystemType);
357                 }
358
359                 if (readOnly)
360                         options = "-oro";
361                 
362                 if (!systemMountOptions.empty())
363                 {
364                         if (options.empty())
365                                 options = "-o";
366                         else
367                                 options += ",";
368
369                         options += systemMountOptions;
370                 }
371                 printf("debug: %s %s \n", options.c_str(), filesystemType.c_str());
372                 if (!options.empty())
373                         args.push_back (options);
374
375                 args.push_back ("--");
376                 args.push_back (devicePath);
377                 args.push_back (mountPoint);
378                 Process::Execute ("mount", args);
379         }
380
381         VolumeSlotNumber CoreUnix::MountPointToSlotNumber (const DirectoryPath &mountPoint) const
382         {
383                 string mountPointStr (mountPoint);
384                 if (mountPointStr.find (GetDefaultMountPointPrefix()) == 0)
385                 {
386                         try
387                         {
388                                 return StringConverter::ToUInt32 (StringConverter::GetTrailingNumber (mountPointStr));
389                         }
390                         catch (...) { }
391                 }
392                 return GetFirstFreeSlotNumber();
393         }
394
395         shared_ptr <VolumeInfo> CoreUnix::MountVolume (MountOptions &options)
396         {
397                 CoalesceSlotNumberAndMountPoint (options);
398
399                 if (IsVolumeMounted (*options.Path))
400                         throw VolumeAlreadyMounted (SRC_POS);
401
402                 Cipher::EnableHwSupport (!options.NoHardwareCrypto);
403
404                 shared_ptr <Volume> volume;
405
406                 while (true)
407                 {
408                         try
409                         {
410                                 volume = OpenVolume (
411                                         options.Path,
412                                         options.PreserveTimestamps,
413                                         options.Password,
414                                         options.Keyfiles,
415                                         options.Protection,
416                                         options.ProtectionPassword,
417                                         options.ProtectionKeyfiles,
418                                         options.SharedAccessAllowed,
419                                         VolumeType::Unknown,
420                                         options.UseBackupHeaders,
421                                         options.PartitionInSystemEncryptionScope
422                                         );
423
424                                 options.Password.reset();
425                         }
426                         catch (SystemException &e)
427                         {
428                                 if (options.Protection != VolumeProtection::ReadOnly
429                                         && (e.GetErrorCode() == EROFS || e.GetErrorCode() == EACCES || e.GetErrorCode() == EPERM))
430                                 {
431                                         // Read-only filesystem
432                                         options.Protection = VolumeProtection::ReadOnly;
433                                         continue;
434                                 }
435
436                                 throw;
437                         }
438
439                         break;
440                 }
441
442                 if (options.Path->IsDevice())
443                 {
444                         if (volume->GetFile()->GetDeviceSectorSize() != volume->GetSectorSize())
445                                 throw ParameterIncorrect (SRC_POS);
446
447 #if defined (TC_LINUX)
448                         if (volume->GetSectorSize() != TC_SECTOR_SIZE_LEGACY)
449                         {
450                                 if (options.Protection == VolumeProtection::HiddenVolumeReadOnly)
451                                         throw UnsupportedSectorSizeHiddenVolumeProtection();
452
453                                 if (options.NoKernelCrypto) // how about no kernel crypto ever
454                                         throw UnsupportedSectorSizeNoKernelCrypto();
455                         }
456 #endif
457                 }
458
459                 // Find a free mount point for FUSE service
460                 MountedFilesystemList mountedFilesystems = GetMountedFilesystems ();
461                 string fuseMountPoint;
462                 for (int i = 1; true; i++)
463                 {
464                         stringstream path;
465                         path << GetTempDirectory() << "/" << GetFuseMountDirPrefix() << i;
466                         FilesystemPath fsPath (path.str());
467
468                         bool inUse = false;
469
470                         foreach_ref (const MountedFilesystem &mf, mountedFilesystems)
471                         {
472                                 if (mf.MountPoint == path.str())
473                                 {
474                                         inUse = true;
475                                         break;
476                                 }
477                         }
478
479                         if (!inUse)
480                         {
481                                 try
482                                 {
483                                         if (fsPath.IsDirectory())
484                                                 fsPath.Delete();
485
486                                         throw_sys_sub_if (mkdir (path.str().c_str(), S_IRUSR | S_IXUSR) == -1, path.str());
487
488                                         fuseMountPoint = fsPath;
489                                         break;
490                                 }
491                                 catch (...)
492                                 {
493                                         if (i > 255)
494                                                 throw TemporaryDirectoryFailure (SRC_POS, StringConverter::ToWide (path.str()));
495                                 }
496                         }
497                 }
498
499                 try
500                 {
501                         FuseService::Mount (volume, options.SlotNumber, fuseMountPoint);
502                 }
503                 catch (...)
504                 {
505                         try
506                         {
507                                 DirectoryPath (fuseMountPoint).Delete();
508                         }
509                         catch (...) { }
510                         throw;
511                 }
512
513                 try
514                 {
515                         // Create a mount directory if a default path has been specified
516                         bool mountDirCreated = false;
517                         string mountPoint;
518                         if (!options.NoFilesystem && options.MountPoint)
519                         {
520                                 mountPoint = *options.MountPoint;
521
522 #ifndef TC_MACOSX
523                                 if (mountPoint.find (GetDefaultMountPointPrefix()) == 0 && !options.MountPoint->IsDirectory())
524                                 {
525                                         Directory::Create (*options.MountPoint);
526                                         try
527                                         {
528                                                 throw_sys_sub_if (chown (mountPoint.c_str(), GetRealUserId(), GetRealGroupId()) == -1, mountPoint);
529                                         } catch (ParameterIncorrect&) { }
530
531                                         mountDirCreated = true;
532                                 }
533 #endif
534                         }
535
536                         try
537                         {
538                                 try
539                                 {
540                                         MountVolumeNative (volume, options, fuseMountPoint);
541                                 }
542                                 catch (NotApplicable&)
543                                 {
544                                         MountAuxVolumeImage (fuseMountPoint, options);
545                                 }
546                         }
547                         catch (...)
548                         {
549                                 if (mountDirCreated)
550                                         remove (mountPoint.c_str());
551                                 throw;
552                         }
553
554 #ifdef TC_LINUX                 
555                         // set again correct ownership of the mount point to avoid any issues
556                         // thanks to veracrypt for this useful bit
557                         if (!options.NoFilesystem && options.MountPoint)
558                         {
559                                 mountPoint = *options.MountPoint;
560
561                                 if (mountPoint.find (GetDefaultMountPointPrefix()) == 0)
562                                 {
563                                         try
564                                         {
565                                                 chown (mountPoint.c_str(), GetRealUserId(), GetRealGroupId());
566                                         } catch (...) { }
567                                 }
568                         }
569 #endif
570                 }
571                 catch (...)
572                 {
573                         try
574                         {
575                                 VolumeInfoList mountedVolumes = GetMountedVolumes (*options.Path);
576                                 if (mountedVolumes.size() > 0)
577                                 {
578                                         shared_ptr <VolumeInfo> mountedVolume (mountedVolumes.front());
579                                         DismountVolume (mountedVolume);
580                                 }
581                         }
582                         catch (...) { }
583                         throw;
584                 }
585
586                 VolumeInfoList mountedVolumes = GetMountedVolumes (*options.Path);
587                 if (mountedVolumes.size() != 1)
588                         throw ParameterIncorrect (SRC_POS);
589
590                 VolumeEventArgs eventArgs (mountedVolumes.front());
591                 VolumeMountedEvent.Raise (eventArgs);
592
593                 return mountedVolumes.front();
594         }
595
596         void CoreUnix::MountAuxVolumeImage (const DirectoryPath &auxMountPoint, const MountOptions &options) const
597         {
598                 DevicePath loopDev = AttachFileToLoopDevice (string (auxMountPoint) + FuseService::GetVolumeImagePath(), options.Protection == VolumeProtection::ReadOnly);
599
600                 try
601                 {
602                         FuseService::SendAuxDeviceInfo (auxMountPoint, loopDev, loopDev);
603                 }
604                 catch (...)
605                 {
606                         try
607                         {
608                                 DetachLoopDevice (loopDev);
609                         }
610                         catch (...) { }
611                         throw;
612                 }
613
614                 if (!options.NoFilesystem && options.MountPoint && !options.MountPoint->IsEmpty())
615                 {
616                         MountFilesystem (loopDev, *options.MountPoint,
617                                 StringConverter::ToSingle (options.FilesystemType),
618                                 options.Protection == VolumeProtection::ReadOnly,
619                                 StringConverter::ToSingle (options.FilesystemOptions));
620                 }
621         }
622
623         void CoreUnix::SetFileOwner (const FilesystemPath &path, const UserId &owner) const
624         {
625                 throw_sys_if (chown (string (path).c_str(), owner.SystemId, (gid_t) -1) == -1);
626         }
627
628         DirectoryPath CoreUnix::SlotNumberToMountPoint (VolumeSlotNumber slotNumber) const
629         {
630                 if (slotNumber < GetFirstSlotNumber() || slotNumber > GetLastSlotNumber())
631                         throw ParameterIncorrect (SRC_POS);
632
633                 stringstream s;
634                 s << GetDefaultMountPointPrefix() << slotNumber;
635                 return s.str();
636         }
637 }
This page took 0.043072 seconds and 3 git commands to generate. Download a nemesis OSX (sierra+high sierra, tested/working) binary, with fuse-ext3 via e2fsprogs, at this link. application and installer are signed by screwjack, llc. must install fuse with macFUSE layer first.