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 }