clone url: git://git.m455.casa/bbt
main.fnl
1 | #!/usr/bin/env fennel |
2 |
|
3 | (local fennel (require :fennel)) |
4 | (local lume (require :lume)) |
5 | (local posix (require :posix)) |
6 | (local unistd (require :posix.unistd)) |
7 |
|
8 | (local db-file "db.fnl") |
9 | (local config-file "config.fnl") |
10 |
|
11 | (local privileges [:create |
12 | :delete |
13 | :give-privilege |
14 | :show-privileges |
15 | :show-hash |
16 | :show-salt |
17 | :show-users]) |
18 |
|
19 | (fn pp [data] (print (fennel.view data))) |
20 |
|
21 | ;;(fn get-char [] |
22 | ;; ;(os.execute "stty raw -echo") |
23 | ;; (os.execute "/bin/stty -icanon") |
24 | ;; (let [char (io.read 1)] |
25 | ;; ;(os.execute "stty -raw echo") |
26 | ;; (os.execute "/bin/stty icanon") |
27 | ;; char)) |
28 |
|
29 | ;; this is soley for using before io.reads |
30 | (fn prompt [str] |
31 | (io.write str) |
32 | (io.flush)) |
33 |
|
34 | (fn prompt-get [str] |
35 | (prompt str) |
36 | (io.read)) |
37 |
|
38 | (fn write-config [data] |
39 | (with-open [fh (io.open config-file :w)] |
40 | (fh:write (fennel.view data)))) |
41 |
|
42 | (fn read-config [] |
43 | (fennel.dofile config-file)) |
44 |
|
45 | (fn write-db [data] |
46 | (with-open [fh (io.open db-file :w)] |
47 | (fh:write (fennel.view data)))) |
48 |
|
49 | (fn read-db [] |
50 | (fennel.dofile db-file)) |
51 |
|
52 | (fn generate-salt [] |
53 | ;; run this seed function before the math.randoms below so the same thing |
54 | ;; isn't generated each time the program runs |
55 | (math.randomseed (os.time)) |
56 | (with-open [fh (io.open "/dev/random" "rb")] |
57 | ;; allowed chars according to crypt() docs: |
58 | ;; https://luaposix.github.io/luaposix/modules/posix.unistd.html#crypt |
59 | ;; table used can also be found here, if you search for "Base64Code": |
60 | ;; https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libc/crypt/bcrypt.c?rev=1.1&content-type=text/x-cvsweb-markup |
61 | (let [random-valid-chars (pick-values 1 (string.gsub (fh:read) "[^a-zA-Z0-9./]" "")) |
62 | ;; bcrypt requires the salt+hash to be 53 long: |
63 | ;; https://man.archlinux.org/man/crypt.5.en#bcrypt |
64 | ;; length of salt+hash can also be seen here: |
65 | ;; https://en.wikipedia.org/wiki/Bcrypt#Description |
66 | salt-length 22] |
67 | (if (or (= random-valid-chars "") |
68 | (< (length random-valid-chars) salt-length)) |
69 | (generate-salt) |
70 | (let [method "2b" ;; bcrypt |
71 | rounds "15" |
72 | salt (string.sub random-valid-chars 1 salt-length)] |
73 | (.. :$ method :$ rounds :$ salt)))))) |
74 |
|
75 | (fn authenticate [db user] |
76 | (let [attempted-password (prompt-get "enter your password: ") |
77 | salt (. db user :password-salt) |
78 | hashed-attempt (do (print "checking password...") |
79 | (unistd.crypt attempted-password salt)) |
80 | hashed-password (. db user :password-hash)] |
81 | (if (= hashed-attempt hashed-password) |
82 | user |
83 | :invalid-password))) |
84 |
|
85 | (fn login [username] |
86 | (if (not username) |
87 | (login (prompt-get "enter a username: ")) |
88 | (let [db (read-db)] |
89 | (if (?. db username) |
90 | (authenticate db username) |
91 | :invalid-user)))) |
92 |
|
93 | (fn has-privilege? [db user privilege] |
94 | (lume.find (. db user :privileges) privilege)) |
95 |
|
96 | (fn create-user [caller db user] |
97 | (if (has-privilege? db caller :create) |
98 | (if (?. db user) |
99 | (print "woops, that user already exists.") |
100 | (let [password (prompt-get "enter password for the new user: ") |
101 | password-salt (generate-salt)] |
102 | (set (. db user) {:privileges [:show-users] |
103 | : password-salt |
104 | :password-hash (do (print "wait one moment...") |
105 | (unistd.crypt password password-salt))}) |
106 | (write-db db) |
107 | (print (string.format "created %s." user)))) |
108 | (print "woops: permission denied."))) |
109 |
|
110 | (fn give-user [caller db user privilege] |
111 | (if (has-privilege? db caller :give-privilege) |
112 | (if (?. db user) |
113 | (do (if (lume.find privileges privilege) |
114 | (do (table.insert (. db user :privileges) privilege) |
115 | (write-db db) |
116 | (print (string.format "gave %s the %s privilege." user privilege))) |
117 | (print "woops that's not a valid privilege"))) |
118 | (print "woops that user doesn't exist.")) |
119 | (print "woops: permission denied."))) |
120 |
|
121 | (fn delete-user [caller db user] |
122 | (if (has-privilege? db caller :delete) |
123 | (if (and (?. db user) |
124 | (not (= :admin user)) |
125 | (not (= caller user))) ;; lol |
126 | (let [answer (prompt-get "are you sure you want to delete this user? (yes/no): ")] |
127 | (if (= (string.lower answer) :yes) |
128 | (do (tset db user nil) |
129 | (write-db db) |
130 | (print (string.format "deleted %s." user))) |
131 | (print "user deletion cancelled."))) |
132 | (print "woops: not a valid user.")) |
133 | (print "woops: permission denied."))) |
134 |
|
135 | (fn show-user-privileges [caller db user] |
136 | (if (= user caller) |
137 | (each [_ priv (ipairs (. db caller :privileges))] |
138 | (print priv)) |
139 | (if (has-privilege? db caller :show-privileges) |
140 | (if (?. db user) |
141 | (each [_ priv (ipairs (. db user :privileges))] |
142 | (print priv)) |
143 | (print "woops, that user doesn't exist.")) |
144 | (print "woops: permission denied.")))) |
145 |
|
146 | (fn show-user-hash [caller db user] |
147 | (if (= user caller) |
148 | (print (. db caller :password-hash)) |
149 | (if (has-privilege? db caller :show-hash) |
150 | (if (?. db user) |
151 | (print (. db user :password-hash)) |
152 | (print "woops, that user doesn't exist.")) |
153 | (print "woops: permission denied.")))) |
154 |
|
155 | (fn show-user-salt [caller db user] |
156 | (if (= user caller) |
157 | (print (. db user :password-salt)) |
158 | (if (has-privilege? db caller :show-salt) |
159 | (if (?. db user) |
160 | (print (. db user :password-salt)) |
161 | (print "woops, that user doesn't exist.")) |
162 | (print "woops: permission denied.")))) |
163 |
|
164 | (fn show-users [caller db] |
165 | (if (has-privilege? db caller :show-users) |
166 | (each [user _ (pairs db)] |
167 | (print user)) |
168 | (print "woops: permission denied."))) |
169 |
|
170 | (fn help [] |
171 | (print "=== privileges ============") |
172 | (each [_ priv (ipairs privileges)] |
173 | (print priv)) |
174 | (print "") |
175 |
|
176 | (print "=== commands ==============") |
177 | (print "create <user>") |
178 | (print "delete <user>") |
179 | (print "give <privilege> to <user>") |
180 | (print "show privileges <user>") |
181 | (print "show hash <user>") |
182 | (print "show salt <user>") |
183 | (print "show users") |
184 | (print "switch to <user>") |
185 | (print "help") |
186 | (print "exit") |
187 | (print "===========================")) |
188 |
|
189 | (fn interface-loop [caller] |
190 | (let [input (prompt-get (.. caller " > ")) |
191 | input-split (lume.split input " ")] |
192 | (match input-split |
193 | [:give privilege :to user] (give-user caller (read-db) user privilege) |
194 |
|
195 | ;; just here for testing |
196 | [:show :privileges user] (show-user-privileges caller (read-db) user) |
197 |
|
198 | ;; just here for testing |
199 | [:show :hash user] (show-user-hash caller (read-db) user) |
200 |
|
201 | ;; just here for testing |
202 | [:show :salt user] (show-user-salt caller (read-db) user) |
203 |
|
204 | [:switch :to user] (let [login-result (login user)] |
205 | (case login-result |
206 | :invalid-user (print "invalid user.") |
207 | :invalid-password (print "invalid password.") |
208 | user (interface-loop user))) |
209 |
|
210 | [:show :users] (show-users caller (read-db)) |
211 |
|
212 | [:create user] (create-user caller (read-db) user) |
213 |
|
214 | [:delete user] (delete-user caller (read-db) user) |
215 |
|
216 | [:help] (help) |
217 |
|
218 | [:exit] (os.exit) |
219 |
|
220 | _ (print (string.format "woops, '%s' isn't a valid command." input))) |
221 |
|
222 | (interface-loop caller))) |
223 |
|
224 | (fn initialize-config [config title] |
225 | (tset config :title title)) |
226 |
|
227 | (fn initialize-db [db password] |
228 | (let [password-salt (generate-salt)] |
229 | (set db.admin {: privileges |
230 | : password-salt |
231 | :password-hash (do (print "wait one moment...") |
232 | (unistd.crypt password password-salt))}))) |
233 |
|
234 | (fn setup [db config] |
235 | (let [password (prompt-get "enter an admin password: ") |
236 | title (prompt-get "enter a title for your forum: ")] |
237 | (initialize-db db password title) |
238 | (initialize-config config title) |
239 | (write-db db) |
240 | (write-config config) |
241 | (print (.. "done setting up your forum! next time you run this program, " |
242 | "your forum will start :)")))) |
243 |
|
244 | (fn main [] |
245 | (if (and (posix.stat db-file) |
246 | (posix.stat config-file) |
247 | (posix.access db-file :rw) |
248 | (posix.access config-file :rw)) |
249 | (let [login-result (login nil)] |
250 | (case login-result |
251 | :invalid-user (print "invalid user.") |
252 | :invalid-password (print "invalid password.") |
253 | user (interface-loop user))) |
254 | (setup {} {}))) |
255 |
|
256 | (main) |