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 /**
20     The UNIX file database
21 */
22 class UnixFDB(EntryT, SubType, string dbFile, string lockFile) {
23     static assert(__traits(hasMember, EntryT, "parseEntry"), "Invalid database format");
24     alias ThisT = typeof(this);
25 
26 private:
27 
28     bool readonly = true;
29 
30     static void waitOnLock() {
31         while(locked) {
32             Thread.sleep(100.msecs);
33         }
34     }
35 
36 protected:
37     EntryT[] entries;
38 
39 package(dunex.auth):
40 
41     this(string data, bool readonly) {
42         this.readonly = readonly;
43         foreach(line; data.split("\n")) {
44             if (line.length == 0) continue;
45             entries ~= EntryT.parseEntry(line);
46         }
47     }
48 
49 public:
50 
51     ~this() {
52         if (readonly) return;
53         unlock();
54     }
55 
56     /**
57         Unlock the password db
58     */
59     void unlock() {
60         if (readonly) return;
61         if (lockFile.exists) fremove(lockFile);
62     }
63 
64     /**
65         Save changes to password db
66     */
67     void save() {
68         if (readonly) throw new Exception("Saving only allowed in read/write mode");
69         string data;
70         foreach(entry; entries) {
71             data ~= entry.toString() ~ "\n";
72         }
73         write(dbFile, data);
74     }
75 
76     /**
77         Gets wether the password db is locked
78     */
79     static bool locked() {
80         return lockFile.exists;
81     }
82 
83     /**
84         Gets entry at index
85     */
86     ref EntryT opIndex(size_t index) {
87         return entries[index];
88     }
89 
90     /**
91         Assigns entry at index
92     */
93     void opIndexAssign(EntryT entry, size_t index) {
94         entries[index] = entry;
95     }
96 
97     /**
98         Assigns entry
99     */
100     void opOpAssign(string op = "~=")(EntryT entry) {
101         entries ~= entry;
102     }
103 
104     /**
105         Gets the index of an entry
106 
107         Throws an exception if not found
108     */
109     size_t indexOf(EntryT xentry) {
110         foreach(i, entry; entries) {
111             if (entry == xentry) return i;
112         }
113         throw new Exception("%s not found!".format(xentry));
114     }
115 
116     /**
117         Remove index in array
118     */
119     void remove(size_t index) {
120         import std.algorithm.mutation : arrrem = remove;
121         entries = arrrem(entries, index);
122     }
123 
124     /**
125         Wether the password db has the specified entry
126     */
127     bool has(EntryT xentry) {
128         foreach(i, entry; entries) {
129             if (entry == xentry) return true;
130         }
131         return false;
132     }
133 
134     /**
135         Locks the password db
136     */
137     static void lock(bool throwOnLock = false)() {
138         synchronized {
139             if (locked) {
140                 static if (throwOnLock) {
141                     throw new Exception(dbFile~" is locked");
142                 } else {
143                     waitOnLock();
144                 }
145             }
146             write(lockFile, "");
147         }
148     }
149 
150     /**
151         Opens up the password database
152     */
153     static SubType open(bool throwOnLock = false)() {
154         lock!(throwOnLock)();
155         return new SubType(readText(dbFile), false);
156     }
157 
158     /**
159         Opens up the password database as read-only non locked
160     */
161     static SubType openro() {
162         return new SubType(readText(dbFile), true);
163     }
164 }