]>
Commit | Line | Data |
---|---|---|
1 | /* authd/provider.c - authentication provider framework | |
2 | * Copyright (c) 2016 Elizabeth Myers <elizabeth@interlinked.me> | |
3 | * | |
4 | * Permission to use, copy, modify, and/or distribute this software for any | |
5 | * purpose with or without fee is hereby granted, provided that the above | |
6 | * copyright notice and this permission notice is present in all copies. | |
7 | * | |
8 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |
9 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
10 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
11 | * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, | |
12 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
13 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
14 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
15 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |
16 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING | |
17 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
18 | * POSSIBILITY OF SUCH DAMAGE. | |
19 | */ | |
20 | ||
21 | /* The basic design here is to have "authentication providers" that do things | |
22 | * like query ident and blacklists and even open proxies. | |
23 | * | |
24 | * Providers are registered in the auth_providers linked list. It is planned to | |
25 | * use a bitmap to store provider ID's later. | |
26 | * | |
27 | * Providers can either return failure immediately, immediate acceptance, or do | |
28 | * work in the background (calling set_provider to signal this). | |
29 | * | |
30 | * Provider-specific data for each client can be kept in an index of the data | |
31 | * struct member (using the provider's ID). | |
32 | * | |
33 | * All providers must implement at a minimum a perform_provider function. You | |
34 | * don't have to implement the others if you don't need them. | |
35 | * | |
36 | * Providers may kick clients off by rejecting them. Upon rejection, all | |
37 | * providers are cancelled. They can also unconditionally accept them. | |
38 | * | |
39 | * When a provider is done and is neutral on accepting/rejecting a client, it | |
40 | * should call provider_done. Do NOT call this if you have accepted or rejected | |
41 | * the client. | |
42 | * | |
43 | * Eventually, stuff like *:line handling will be moved here, but that means we | |
44 | * have to talk to bandb directly first. | |
45 | * | |
46 | * --Elizafox, 9 March 2016 | |
47 | */ | |
48 | ||
49 | #include "stdinc.h" | |
50 | #include "rb_dictionary.h" | |
51 | #include "rb_lib.h" | |
52 | #include "authd.h" | |
53 | #include "provider.h" | |
54 | #include "notice.h" | |
55 | ||
56 | static EVH provider_timeout_event; | |
57 | ||
58 | rb_dictionary *auth_clients; | |
59 | rb_dlink_list auth_providers; | |
60 | ||
61 | static rb_dlink_list free_pids; | |
62 | static uint32_t allocated_pids; | |
63 | static struct ev_entry *timeout_ev; | |
64 | ||
65 | /* Set a provider's raw status */ | |
66 | static inline void | |
67 | set_provider_status(struct auth_client *auth, uint32_t provider, provider_status_t status) | |
68 | { | |
69 | auth->data[provider].status = status; | |
70 | } | |
71 | ||
72 | /* Set the provider as running */ | |
73 | static inline void | |
74 | set_provider_running(struct auth_client *auth, uint32_t provider) | |
75 | { | |
76 | auth->providers_active++; | |
77 | set_provider_status(auth, provider, PROVIDER_STATUS_RUNNING); | |
78 | } | |
79 | ||
80 | /* Provider is no longer operating on this auth client */ | |
81 | static inline void | |
82 | set_provider_done(struct auth_client *auth, uint32_t provider) | |
83 | { | |
84 | set_provider_status(auth, provider, PROVIDER_STATUS_DONE); | |
85 | auth->providers_active--; | |
86 | } | |
87 | ||
88 | /* Initalise all providers */ | |
89 | void | |
90 | init_providers(void) | |
91 | { | |
92 | auth_clients = rb_dictionary_create("pending auth clients", rb_uint32cmp); | |
93 | timeout_ev = rb_event_addish("provider_timeout_event", provider_timeout_event, NULL, 1); | |
94 | ||
95 | /* FIXME must be started before rdns/ident to receive completion notification from them */ | |
96 | load_provider(&blacklist_provider); | |
97 | load_provider(&opm_provider); | |
98 | ||
99 | /* FIXME must be started after blacklist/opm in case of early completion notifications */ | |
100 | load_provider(&rdns_provider); | |
101 | load_provider(&ident_provider); | |
102 | } | |
103 | ||
104 | /* Terminate all providers */ | |
105 | void | |
106 | destroy_providers(void) | |
107 | { | |
108 | rb_dlink_node *ptr, *nptr; | |
109 | rb_dictionary_iter iter; | |
110 | struct auth_client *auth; | |
111 | ||
112 | /* Cancel outstanding connections */ | |
113 | RB_DICTIONARY_FOREACH(auth, &iter, auth_clients) | |
114 | { | |
115 | auth_client_ref(auth); | |
116 | ||
117 | /* TBD - is this the right thing? */ | |
118 | reject_client(auth, UINT32_MAX, "destroy", | |
119 | "Authentication system is down... try reconnecting in a few seconds"); | |
120 | ||
121 | auth_client_unref(auth); | |
122 | } | |
123 | ||
124 | RB_DLINK_FOREACH_SAFE(ptr, nptr, auth_providers.head) | |
125 | { | |
126 | struct auth_provider *provider = ptr->data; | |
127 | ||
128 | if(provider->destroy) | |
129 | provider->destroy(); | |
130 | ||
131 | rb_dlinkDelete(ptr, &auth_providers); | |
132 | } | |
133 | ||
134 | rb_dictionary_destroy(auth_clients, NULL, NULL); | |
135 | rb_event_delete(timeout_ev); | |
136 | } | |
137 | ||
138 | /* Load a provider */ | |
139 | void | |
140 | load_provider(struct auth_provider *provider) | |
141 | { | |
142 | /* Assign a PID */ | |
143 | if(rb_dlink_list_length(&free_pids) > 0) | |
144 | { | |
145 | /* use the free list */ | |
146 | provider->id = RB_POINTER_TO_UINT(free_pids.head->data); | |
147 | rb_dlinkDestroy(free_pids.head, &free_pids); | |
148 | } | |
149 | else | |
150 | { | |
151 | if(allocated_pids == MAX_PROVIDERS || allocated_pids == UINT32_MAX) | |
152 | { | |
153 | warn_opers(L_WARN, "Cannot load additional provider, max reached!"); | |
154 | return; | |
155 | } | |
156 | ||
157 | provider->id = allocated_pids++; | |
158 | } | |
159 | ||
160 | if(provider->opt_handlers != NULL) | |
161 | { | |
162 | struct auth_opts_handler *handler; | |
163 | ||
164 | for(handler = provider->opt_handlers; handler->option != NULL; handler++) | |
165 | rb_dictionary_add(authd_option_handlers, handler->option, handler); | |
166 | } | |
167 | ||
168 | if(provider->stats_handler.letter != '\0') | |
169 | authd_stat_handlers[(unsigned char)provider->stats_handler.letter] = provider->stats_handler.handler; | |
170 | ||
171 | if(provider->init != NULL) | |
172 | provider->init(); | |
173 | ||
174 | rb_dlinkAdd(provider, &provider->node, &auth_providers); | |
175 | } | |
176 | ||
177 | void | |
178 | unload_provider(struct auth_provider *provider) | |
179 | { | |
180 | if(provider->opt_handlers != NULL) | |
181 | { | |
182 | struct auth_opts_handler *handler; | |
183 | ||
184 | for(handler = provider->opt_handlers; handler->option != NULL; handler++) | |
185 | rb_dictionary_delete(authd_option_handlers, handler->option); | |
186 | } | |
187 | ||
188 | if(provider->stats_handler.letter != '\0') | |
189 | authd_stat_handlers[(unsigned char)provider->stats_handler.letter] = NULL; | |
190 | ||
191 | if(provider->destroy != NULL) | |
192 | provider->destroy(); | |
193 | ||
194 | rb_dlinkDelete(&provider->node, &auth_providers); | |
195 | ||
196 | /* Reclaim ID */ | |
197 | rb_dlinkAddAlloc(RB_UINT_TO_POINTER(provider->id), &free_pids); | |
198 | } | |
199 | ||
200 | void | |
201 | auth_client_free(struct auth_client *auth) | |
202 | { | |
203 | rb_dictionary_delete(auth_clients, RB_UINT_TO_POINTER(auth->cid)); | |
204 | rb_free(auth->data); | |
205 | rb_free(auth); | |
206 | } | |
207 | ||
208 | /* Cancel outstanding providers for a client (if any). */ | |
209 | void | |
210 | cancel_providers(struct auth_client *auth) | |
211 | { | |
212 | if(auth->providers_cancelled) | |
213 | return; | |
214 | ||
215 | auth->providers_cancelled = true; | |
216 | ||
217 | if(auth->providers_active > 0) | |
218 | { | |
219 | rb_dlink_node *ptr; | |
220 | ||
221 | RB_DLINK_FOREACH(ptr, auth_providers.head) | |
222 | { | |
223 | struct auth_provider *provider = ptr->data; | |
224 | ||
225 | if(provider->cancel != NULL && is_provider_running(auth, provider->id)) | |
226 | /* Cancel if required */ | |
227 | provider->cancel(auth); | |
228 | } | |
229 | } | |
230 | } | |
231 | ||
232 | /* Provider is done */ | |
233 | void | |
234 | provider_done(struct auth_client *auth, uint32_t id) | |
235 | { | |
236 | rb_dlink_node *ptr; | |
237 | ||
238 | lrb_assert(is_provider_running(auth, id)); | |
239 | lrb_assert(id != UINT32_MAX); | |
240 | lrb_assert(id < allocated_pids); | |
241 | ||
242 | set_provider_done(auth, id); | |
243 | ||
244 | if(auth->providers_active == 0 && !auth->providers_starting) | |
245 | { | |
246 | /* All done */ | |
247 | accept_client(auth); | |
248 | return; | |
249 | } | |
250 | ||
251 | RB_DLINK_FOREACH(ptr, auth_providers.head) | |
252 | { | |
253 | struct auth_provider *provider = ptr->data; | |
254 | ||
255 | if(provider->completed != NULL && is_provider_running(auth, provider->id)) | |
256 | /* Notify pending clients who asked for it */ | |
257 | provider->completed(auth, id); | |
258 | } | |
259 | } | |
260 | ||
261 | /* Reject a client and cancel any outstanding providers */ | |
262 | void | |
263 | reject_client(struct auth_client *auth, uint32_t id, const char *data, const char *fmt, ...) | |
264 | { | |
265 | char buf[BUFSIZE]; | |
266 | va_list args; | |
267 | ||
268 | va_start(args, fmt); | |
269 | vsnprintf(buf, sizeof(buf), fmt, args); | |
270 | va_end(args); | |
271 | ||
272 | /* We send back username and hostname in case ircd wants to overrule our decision. | |
273 | * In the future this may not be the case. | |
274 | * --Elizafox | |
275 | */ | |
276 | rb_helper_write(authd_helper, "R %x %c %s %s %s :%s", | |
277 | auth->cid, id != UINT32_MAX ? auth->data[id].provider->letter : '*', | |
278 | auth->username, auth->hostname, | |
279 | data == NULL ? "*" : data, buf); | |
280 | ||
281 | if(id != UINT32_MAX) | |
282 | set_provider_done(auth, id); | |
283 | ||
284 | cancel_providers(auth); | |
285 | } | |
286 | ||
287 | /* Accept a client and cancel outstanding providers if any */ | |
288 | void | |
289 | accept_client(struct auth_client *auth) | |
290 | { | |
291 | rb_helper_write(authd_helper, "A %x %s %s", auth->cid, auth->username, auth->hostname); | |
292 | cancel_providers(auth); | |
293 | } | |
294 | ||
295 | /* Begin authenticating user */ | |
296 | static void | |
297 | start_auth(const char *cid, const char *l_ip, const char *l_port, const char *c_ip, const char *c_port, const char *protocol) | |
298 | { | |
299 | struct auth_client *auth; | |
300 | unsigned long long lcid = strtoull(cid, NULL, 16); | |
301 | rb_dlink_node *ptr; | |
302 | ||
303 | if(lcid == 0 || lcid > UINT32_MAX) | |
304 | return; | |
305 | ||
306 | auth = rb_malloc(sizeof(struct auth_client)); | |
307 | auth_client_ref(auth); | |
308 | auth->cid = (uint32_t)lcid; | |
309 | ||
310 | if(rb_dictionary_find(auth_clients, RB_UINT_TO_POINTER(auth->cid)) == NULL) | |
311 | rb_dictionary_add(auth_clients, RB_UINT_TO_POINTER(auth->cid), auth); | |
312 | else | |
313 | { | |
314 | warn_opers(L_CRIT, "provider: duplicate client added via start_auth: %s", cid); | |
315 | exit(EX_PROVIDER_ERROR); | |
316 | } | |
317 | ||
318 | auth->protocol = strtoull(protocol, NULL, 16); | |
319 | ||
320 | rb_strlcpy(auth->l_ip, l_ip, sizeof(auth->l_ip)); | |
321 | auth->l_port = (uint16_t)atoi(l_port); /* should be safe */ | |
322 | (void) rb_inet_pton_sock(l_ip, (struct sockaddr *)&auth->l_addr); | |
323 | SET_SS_PORT(&auth->l_addr, htons(auth->l_port)); | |
324 | ||
325 | rb_strlcpy(auth->c_ip, c_ip, sizeof(auth->c_ip)); | |
326 | auth->c_port = (uint16_t)atoi(c_port); | |
327 | (void) rb_inet_pton_sock(c_ip, (struct sockaddr *)&auth->c_addr); | |
328 | SET_SS_PORT(&auth->c_addr, htons(auth->c_port)); | |
329 | ||
330 | rb_strlcpy(auth->hostname, "*", sizeof(auth->hostname)); | |
331 | rb_strlcpy(auth->username, "*", sizeof(auth->username)); | |
332 | ||
333 | auth->data = rb_malloc(allocated_pids * sizeof(struct auth_client_data)); | |
334 | ||
335 | auth->providers_starting = true; | |
336 | RB_DLINK_FOREACH(ptr, auth_providers.head) | |
337 | { | |
338 | struct auth_provider *provider = ptr->data; | |
339 | ||
340 | auth->data[provider->id].provider = provider; | |
341 | ||
342 | lrb_assert(provider->start != NULL); | |
343 | ||
344 | /* Execute providers */ | |
345 | set_provider_running(auth, provider->id); | |
346 | if(!provider->start(auth)) | |
347 | /* Rejected immediately */ | |
348 | goto done; | |
349 | ||
350 | if(auth->providers_cancelled) | |
351 | break; | |
352 | } | |
353 | auth->providers_starting = false; | |
354 | ||
355 | /* If no providers are running, accept the client */ | |
356 | if(auth->providers_active == 0) | |
357 | accept_client(auth); | |
358 | ||
359 | done: | |
360 | auth_client_unref(auth); | |
361 | } | |
362 | ||
363 | /* Callback for the initiation */ | |
364 | void | |
365 | handle_new_connection(int parc, char *parv[]) | |
366 | { | |
367 | if (parc < 6) { | |
368 | warn_opers(L_CRIT, "provider: received too few params for new connection (6 expected, got %d)", parc); | |
369 | exit(EX_PROVIDER_ERROR); | |
370 | } | |
371 | ||
372 | start_auth(parv[1], parv[2], parv[3], parv[4], parv[5], parc > 6 ? parv[6] : "0"); | |
373 | } | |
374 | ||
375 | void | |
376 | handle_cancel_connection(int parc, char *parv[]) | |
377 | { | |
378 | struct auth_client *auth; | |
379 | unsigned long long lcid; | |
380 | ||
381 | if(parc < 2) | |
382 | { | |
383 | warn_opers(L_CRIT, "provider: received too few params for new connection (2 expected, got %d)", parc); | |
384 | exit(EX_PROVIDER_ERROR); | |
385 | } | |
386 | ||
387 | lcid = strtoull(parv[1], NULL, 16); | |
388 | if(lcid == 0 || lcid > UINT32_MAX) | |
389 | { | |
390 | warn_opers(L_CRIT, "provider: got a request to cancel a connection that can't exist: %s", parv[1]); | |
391 | exit(EX_PROVIDER_ERROR); | |
392 | } | |
393 | ||
394 | if((auth = rb_dictionary_retrieve(auth_clients, RB_UINT_TO_POINTER((uint32_t)lcid))) == NULL) | |
395 | { | |
396 | /* This could happen as a race if we've accepted/rejected but they cancel, so don't die here. | |
397 | * --Elizafox */ | |
398 | return; | |
399 | } | |
400 | ||
401 | auth_client_ref(auth); | |
402 | cancel_providers(auth); | |
403 | auth_client_unref(auth); | |
404 | } | |
405 | ||
406 | static void | |
407 | provider_timeout_event(void *notused __unused) | |
408 | { | |
409 | struct auth_client *auth; | |
410 | rb_dictionary_iter iter; | |
411 | const time_t curtime = rb_current_time(); | |
412 | ||
413 | RB_DICTIONARY_FOREACH(auth, &iter, auth_clients) | |
414 | { | |
415 | rb_dlink_node *ptr; | |
416 | ||
417 | auth_client_ref(auth); | |
418 | ||
419 | RB_DLINK_FOREACH(ptr, auth_providers.head) | |
420 | { | |
421 | struct auth_provider *provider = ptr->data; | |
422 | const time_t timeout = get_provider_timeout(auth, provider->id); | |
423 | ||
424 | if(is_provider_running(auth, provider->id) && provider->timeout != NULL && | |
425 | timeout > 0 && timeout < curtime) | |
426 | { | |
427 | provider->timeout(auth); | |
428 | } | |
429 | } | |
430 | ||
431 | auth_client_unref(auth); | |
432 | } | |
433 | } |