1 /* 2 Copyright (c) 2019, DUNEX Contributors 3 Use, modification and distribution are subject to the 4 Boost Software License, Version 1.0. (See accompanying file 5 COPYING or copy at http://www.boost.org/LICENSE_1_0.txt) 6 7 dunex-auth: D toolkit to manage UNIX authentication 8 9 Author: Clipsey 10 */ 11 module dunex.auth.fdb; 12 import std.conv; 13 import std.file : fremove = remove, exists, write, readText; 14 import core.thread : Thread; 15 import std.datetime; 16 import std.format; 17 import std.array : split; 18 19 public import std.stdio : StdioException; 20 21 /** 22 Exception thrown when the database is locked 23 */ 24 class LockException : Exception { 25 this(string file) { 26 super("%s is locked".format(file)); 27 } 28 } 29 30 /** 31 Exception thrown when an entry does not exist 32 */ 33 class DoesNotExistException : Exception { 34 this(string type, string entryTag) { 35 super("%s %s not found".format(type, entryTag)); 36 } 37 } 38 39 /** 40 The UNIX file database 41 */ 42 class UnixFDB(EntryT, SubType, string dbFile, string lockFile) { 43 static assert(__traits(hasMember, EntryT, "parseEntry"), "Invalid database format"); 44 alias ThisT = typeof(this); 45 46 private: 47 48 bool readonly = true; 49 50 static void waitOnLock() { 51 while(locked) { 52 Thread.sleep(100.msecs); 53 } 54 } 55 56 protected: 57 EntryT[] entries; 58 59 package(dunex.auth): 60 61 this(string data, bool readonly) { 62 this.readonly = readonly; 63 foreach(line; data.split("\n")) { 64 if (line.length == 0) continue; 65 entries ~= EntryT.parseEntry(line); 66 } 67 } 68 69 public: 70 71 ~this() { 72 if (readonly) return; 73 unlock(); 74 } 75 76 /** 77 Unlock the password db 78 */ 79 void unlock() { 80 if (readonly) return; 81 if (lockFile.exists) fremove(lockFile); 82 } 83 84 /** 85 Save changes to password db 86 */ 87 void save() { 88 if (readonly) throw new Exception("Saving only allowed in read/write mode"); 89 string data; 90 foreach(entry; entries) { 91 data ~= entry.toString() ~ "\n"; 92 } 93 write(dbFile, data); 94 } 95 96 /** 97 Gets wether the password db is locked 98 */ 99 static bool locked() { 100 return lockFile.exists; 101 } 102 103 /** 104 Gets entry at index 105 */ 106 ref EntryT opIndex(size_t index) { 107 return entries[index]; 108 } 109 110 /** 111 Assigns entry at index 112 */ 113 void opIndexAssign(EntryT entry, size_t index) { 114 entries[index] = entry; 115 } 116 117 /** 118 Assigns entry 119 */ 120 void opOpAssign(string op = "~=")(EntryT entry) { 121 entries ~= entry; 122 } 123 124 /** 125 Gets the index of an entry 126 127 Throws an exception if not found 128 */ 129 size_t indexOf(EntryT xentry) { 130 foreach(i, entry; entries) { 131 if (entry == xentry) return i; 132 } 133 throw new Exception("%s not found!".format(xentry)); 134 } 135 136 /** 137 Remove index in array 138 */ 139 void remove(size_t index) { 140 import std.algorithm.mutation : arrrem = remove; 141 entries = arrrem(entries, index); 142 } 143 144 /** 145 Wether the password db has the specified entry 146 */ 147 bool has(EntryT xentry) { 148 foreach(i, entry; entries) { 149 if (entry == xentry) return true; 150 } 151 return false; 152 } 153 154 /** 155 Locks the password db 156 */ 157 static void lock(bool throwOnLock = false)() { 158 synchronized { 159 if (locked) { 160 static if (throwOnLock) { 161 throw new LockException(dbFile); 162 } else { 163 waitOnLock(); 164 } 165 } 166 write(lockFile, "\0"); 167 } 168 } 169 170 /** 171 Opens up the password database 172 */ 173 static SubType open(bool throwOnLock = false)() { 174 lock!(throwOnLock)(); 175 return new SubType(readText(dbFile), false); 176 } 177 178 /** 179 Opens up the password database as read-only non locked 180 */ 181 static SubType openro() { 182 return new SubType(readText(dbFile), true); 183 } 184 }