]>
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 "authd.h" | |
52 | #include "provider.h" | |
53 | #include "notice.h" | |
54 | ||
55 | static EVH provider_timeout_event; | |
56 | ||
57 | rb_dlink_list auth_providers; | |
58 | ||
59 | /* Clients waiting */ | |
60 | rb_dictionary *auth_clients; | |
61 | ||
62 | static struct ev_entry *timeout_ev; | |
63 | ||
64 | /* Load a provider */ | |
65 | void | |
66 | load_provider(struct auth_provider *provider) | |
67 | { | |
68 | if(rb_dlink_list_length(&auth_providers) >= MAX_PROVIDERS) | |
69 | { | |
70 | warn_opers(L_WARN, "provider: cannot load provider with id %d: maximum reached (%d)", | |
71 | provider->id, MAX_PROVIDERS); | |
72 | return; | |
73 | } | |
74 | ||
75 | if(provider->opt_handlers != NULL) | |
76 | { | |
77 | struct auth_opts_handler *handler; | |
78 | ||
79 | for(handler = provider->opt_handlers; handler->option != NULL; handler++) | |
80 | rb_dictionary_add(authd_option_handlers, handler->option, handler); | |
81 | } | |
82 | ||
83 | if(provider->stats_handler.letter != '\0') | |
84 | authd_stat_handlers[provider->stats_handler.letter] = provider->stats_handler.handler; | |
85 | ||
86 | if(provider->init != NULL) | |
87 | provider->init(); | |
88 | ||
89 | rb_dlinkAdd(provider, &provider->node, &auth_providers); | |
90 | } | |
91 | ||
92 | void | |
93 | unload_provider(struct auth_provider *provider) | |
94 | { | |
95 | if(provider->opt_handlers != NULL) | |
96 | { | |
97 | struct auth_opts_handler *handler; | |
98 | ||
99 | for(handler = provider->opt_handlers; handler->option != NULL; handler++) | |
100 | rb_dictionary_delete(authd_option_handlers, handler->option); | |
101 | } | |
102 | ||
103 | if(provider->stats_handler.letter != '\0') | |
104 | authd_stat_handlers[provider->stats_handler.letter] = NULL; | |
105 | ||
106 | if(provider->destroy != NULL) | |
107 | provider->destroy(); | |
108 | ||
109 | rb_dlinkDelete(&provider->node, &auth_providers); | |
110 | } | |
111 | ||
112 | /* Initalise all providers */ | |
113 | void | |
114 | init_providers(void) | |
115 | { | |
116 | auth_clients = rb_dictionary_create("pending auth clients", rb_uint32cmp); | |
117 | timeout_ev = rb_event_addish("provider_timeout_event", provider_timeout_event, NULL, 1); | |
118 | load_provider(&rdns_provider); | |
119 | load_provider(&ident_provider); | |
120 | load_provider(&blacklist_provider); | |
121 | load_provider(&opm_provider); | |
122 | } | |
123 | ||
124 | /* Terminate all providers */ | |
125 | void | |
126 | destroy_providers(void) | |
127 | { | |
128 | rb_dlink_node *ptr; | |
129 | rb_dictionary_iter iter; | |
130 | struct auth_client *auth; | |
131 | struct auth_provider *provider; | |
132 | ||
133 | /* Cancel outstanding connections */ | |
134 | RB_DICTIONARY_FOREACH(auth, &iter, auth_clients) | |
135 | { | |
136 | /* TBD - is this the right thing? */ | |
137 | reject_client(auth, -1, "destroy", "Authentication system is down... try reconnecting in a few seconds"); | |
138 | } | |
139 | ||
140 | RB_DLINK_FOREACH(ptr, auth_providers.head) | |
141 | { | |
142 | provider = ptr->data; | |
143 | ||
144 | if(provider->destroy) | |
145 | provider->destroy(); | |
146 | } | |
147 | ||
148 | rb_event_delete(timeout_ev); | |
149 | } | |
150 | ||
151 | /* Cancel outstanding providers for a client */ | |
152 | void | |
153 | cancel_providers(struct auth_client *auth) | |
154 | { | |
155 | rb_dlink_node *ptr; | |
156 | struct auth_provider *provider; | |
157 | ||
158 | RB_DLINK_FOREACH(ptr, auth_providers.head) | |
159 | { | |
160 | provider = ptr->data; | |
161 | ||
162 | if(provider->cancel && is_provider_on(auth, provider->id)) | |
163 | /* Cancel if required */ | |
164 | provider->cancel(auth); | |
165 | } | |
166 | ||
167 | rb_dictionary_delete(auth_clients, RB_UINT_TO_POINTER(auth->cid)); | |
168 | rb_free(auth); | |
169 | } | |
170 | ||
171 | /* Provider is done - WARNING: do not use auth instance after calling! */ | |
172 | void | |
173 | provider_done(struct auth_client *auth, provider_t id) | |
174 | { | |
175 | rb_dlink_node *ptr; | |
176 | struct auth_provider *provider; | |
177 | ||
178 | set_provider_off(auth, id); | |
179 | set_provider_done(auth, id); | |
180 | ||
181 | if(!auth->providers) | |
182 | { | |
183 | if(!auth->providers_starting) | |
184 | /* Only do this when there are no providers left */ | |
185 | accept_client(auth, -1); | |
186 | return; | |
187 | } | |
188 | ||
189 | RB_DLINK_FOREACH(ptr, auth_providers.head) | |
190 | { | |
191 | provider = ptr->data; | |
192 | ||
193 | if(provider->completed != NULL && is_provider_on(auth, provider->id)) | |
194 | /* Notify pending clients who asked for it */ | |
195 | provider->completed(auth, id); | |
196 | } | |
197 | } | |
198 | ||
199 | /* Reject a client - WARNING: do not use auth instance after calling! */ | |
200 | void | |
201 | reject_client(struct auth_client *auth, provider_t id, const char *data, const char *fmt, ...) | |
202 | { | |
203 | char reject; | |
204 | char buf[BUFSIZE]; | |
205 | va_list args; | |
206 | ||
207 | switch(id) | |
208 | { | |
209 | case PROVIDER_RDNS: | |
210 | reject = 'D'; | |
211 | break; | |
212 | case PROVIDER_IDENT: | |
213 | reject = 'I'; | |
214 | break; | |
215 | case PROVIDER_BLACKLIST: | |
216 | reject = 'B'; | |
217 | break; | |
218 | default: | |
219 | reject = 'N'; | |
220 | break; | |
221 | } | |
222 | ||
223 | if(data == NULL) | |
224 | data = "*"; | |
225 | ||
226 | va_start(args, fmt); | |
227 | vsnprintf(buf, sizeof(buf), fmt, args); | |
228 | va_end(args); | |
229 | ||
230 | /* We send back username and hostname in case ircd wants to overrule our decision. | |
231 | * In the future this may not be the case. | |
232 | * --Elizafox | |
233 | */ | |
234 | rb_helper_write(authd_helper, "R %x %c %s %s %s :%s", auth->cid, reject, auth->username, auth->hostname, data, buf); | |
235 | ||
236 | set_provider_off(auth, id); | |
237 | cancel_providers(auth); | |
238 | } | |
239 | ||
240 | /* Accept a client, cancel outstanding providers if any - WARNING: do nto use auth instance after calling! */ | |
241 | void | |
242 | accept_client(struct auth_client *auth, provider_t id) | |
243 | { | |
244 | rb_helper_write(authd_helper, "A %x %s %s", auth->cid, auth->username, auth->hostname); | |
245 | ||
246 | set_provider_off(auth, id); | |
247 | cancel_providers(auth); | |
248 | } | |
249 | ||
250 | /* Begin authenticating user */ | |
251 | static void | |
252 | start_auth(const char *cid, const char *l_ip, const char *l_port, const char *c_ip, const char *c_port) | |
253 | { | |
254 | struct auth_provider *provider; | |
255 | struct auth_client *auth = rb_malloc(sizeof(struct auth_client)); | |
256 | long lcid = strtol(cid, NULL, 16); | |
257 | rb_dlink_node *ptr; | |
258 | ||
259 | if(lcid >= UINT32_MAX) | |
260 | return; | |
261 | ||
262 | auth->cid = (uint32_t)lcid; | |
263 | ||
264 | if(rb_dictionary_find(auth_clients, RB_UINT_TO_POINTER(auth->cid)) == NULL) | |
265 | rb_dictionary_add(auth_clients, RB_UINT_TO_POINTER(auth->cid), auth); | |
266 | else | |
267 | { | |
268 | warn_opers(L_CRIT, "provider: duplicate client added via start_auth: %x", auth->cid); | |
269 | exit(EX_PROVIDER_ERROR); | |
270 | } | |
271 | ||
272 | rb_strlcpy(auth->l_ip, l_ip, sizeof(auth->l_ip)); | |
273 | auth->l_port = (uint16_t)atoi(l_port); /* should be safe */ | |
274 | (void) rb_inet_pton_sock(l_ip, (struct sockaddr *)&auth->l_addr); | |
275 | ||
276 | rb_strlcpy(auth->c_ip, c_ip, sizeof(auth->c_ip)); | |
277 | auth->c_port = (uint16_t)atoi(c_port); | |
278 | (void) rb_inet_pton_sock(c_ip, (struct sockaddr *)&auth->c_addr); | |
279 | ||
280 | #ifdef RB_IPV6 | |
281 | if(GET_SS_FAMILY(&auth->l_addr) == AF_INET6) | |
282 | ((struct sockaddr_in6 *)&auth->l_addr)->sin6_port = htons(auth->l_port); | |
283 | else | |
284 | #endif | |
285 | ((struct sockaddr_in *)&auth->l_addr)->sin_port = htons(auth->l_port); | |
286 | ||
287 | #ifdef RB_IPV6 | |
288 | if(GET_SS_FAMILY(&auth->c_addr) == AF_INET6) | |
289 | ((struct sockaddr_in6 *)&auth->c_addr)->sin6_port = htons(auth->c_port); | |
290 | else | |
291 | #endif | |
292 | ((struct sockaddr_in *)&auth->c_addr)->sin_port = htons(auth->c_port); | |
293 | ||
294 | rb_strlcpy(auth->hostname, "*", sizeof(auth->hostname)); | |
295 | rb_strlcpy(auth->username, "*", sizeof(auth->username)); | |
296 | ||
297 | memset(auth->data, 0, sizeof(auth->data)); | |
298 | ||
299 | auth->providers_starting = true; | |
300 | RB_DLINK_FOREACH(ptr, auth_providers.head) | |
301 | { | |
302 | provider = ptr->data; | |
303 | ||
304 | lrb_assert(provider->start != NULL); | |
305 | ||
306 | /* Execute providers */ | |
307 | if(!provider->start(auth)) | |
308 | { | |
309 | /* Rejected immediately */ | |
310 | cancel_providers(auth); | |
311 | return; | |
312 | } | |
313 | } | |
314 | auth->providers_starting = false; | |
315 | ||
316 | /* If no providers are running, accept the client */ | |
317 | if(!auth->providers) | |
318 | accept_client(auth, -1); | |
319 | } | |
320 | ||
321 | /* Callback for the initiation */ | |
322 | void | |
323 | handle_new_connection(int parc, char *parv[]) | |
324 | { | |
325 | if(parc < 6) | |
326 | { | |
327 | warn_opers(L_CRIT, "provider: received too few params for new connection (6 expected, got %d)", parc); | |
328 | exit(EX_PROVIDER_ERROR); | |
329 | } | |
330 | ||
331 | start_auth(parv[1], parv[2], parv[3], parv[4], parv[5]); | |
332 | } | |
333 | ||
334 | void | |
335 | handle_cancel_connection(int parc, char *parv[]) | |
336 | { | |
337 | struct auth_client *auth; | |
338 | long lcid; | |
339 | ||
340 | if(parc < 2) | |
341 | { | |
342 | warn_opers(L_CRIT, "provider: received too few params for new connection (2 expected, got %d)", parc); | |
343 | exit(EX_PROVIDER_ERROR); | |
344 | } | |
345 | ||
346 | if((lcid = strtol(parv[1], NULL, 16)) > UINT32_MAX) | |
347 | { | |
348 | warn_opers(L_CRIT, "provider: got a request to cancel a connection that can't exist: %lx", lcid); | |
349 | exit(EX_PROVIDER_ERROR); | |
350 | } | |
351 | ||
352 | if((auth = rb_dictionary_retrieve(auth_clients, RB_UINT_TO_POINTER((uint32_t)lcid))) == NULL) | |
353 | { | |
354 | warn_opers(L_CRIT, "provider: tried to cancel nonexistent connection %lx", lcid); | |
355 | exit(EX_PROVIDER_ERROR); | |
356 | } | |
357 | ||
358 | cancel_providers(auth); | |
359 | } | |
360 | ||
361 | static void | |
362 | provider_timeout_event(void *notused __unused) | |
363 | { | |
364 | struct auth_client *auth; | |
365 | rb_dictionary_iter iter; | |
366 | const time_t curtime = rb_current_time(); | |
367 | ||
368 | RB_DICTIONARY_FOREACH(auth, &iter, auth_clients) | |
369 | { | |
370 | rb_dlink_node *ptr; | |
371 | ||
372 | RB_DLINK_FOREACH(ptr, auth_providers.head) | |
373 | { | |
374 | struct auth_provider *provider = ptr->data; | |
375 | const time_t timeout = auth->timeout[provider->id]; | |
376 | ||
377 | if(is_provider_on(auth, provider->id) && provider->timeout != NULL && | |
378 | timeout > 0 && timeout < curtime) | |
379 | { | |
380 | provider->timeout(auth); | |
381 | } | |
382 | } | |
383 | } | |
384 | } |