]>
Commit | Line | Data |
---|---|---|
2b0cc3d3 EM |
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 | ||
89d22b9a EM |
21 | /* The basic design here is to have "authentication providers" that do things |
22 | * like query ident and blacklists and even open proxies. | |
2b0cc3d3 | 23 | * |
f7b37c1d EM |
24 | * Providers are registered in the auth_providers linked list. It is planned to |
25 | * use a bitmap to store provider ID's later. | |
2b0cc3d3 | 26 | * |
89d22b9a EM |
27 | * Providers can either return failure immediately, immediate acceptance, or do |
28 | * work in the background (calling set_provider to signal this). | |
2b0cc3d3 | 29 | * |
3e875f62 EM |
30 | * Provider-specific data for each client can be kept in an index of the data |
31 | * struct member (using the provider's ID). | |
2b0cc3d3 EM |
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 | * | |
89d22b9a EM |
43 | * Eventually, stuff like *:line handling will be moved here, but that means we |
44 | * have to talk to bandb directly first. | |
45 | * | |
2b0cc3d3 EM |
46 | * --Elizafox, 9 March 2016 |
47 | */ | |
48 | ||
52d49164 | 49 | #include "stdinc.h" |
a71b65b1 | 50 | #include "rb_dictionary.h" |
d955cd9f | 51 | #include "rb_lib.h" |
2b0cc3d3 EM |
52 | #include "authd.h" |
53 | #include "provider.h" | |
db821ee9 | 54 | #include "notice.h" |
2b0cc3d3 | 55 | |
15c49abb EM |
56 | static EVH provider_timeout_event; |
57 | ||
a71b65b1 | 58 | rb_dictionary *auth_clients; |
d955cd9f | 59 | rb_dlink_list auth_providers; |
2b0cc3d3 | 60 | |
731d1289 | 61 | static rb_dlink_list free_pids; |
075d4d56 | 62 | static uint32_t allocated_pids; |
15c49abb EM |
63 | static struct ev_entry *timeout_ev; |
64 | ||
731d1289 EM |
65 | /* Initalise all providers */ |
66 | void | |
67 | init_providers(void) | |
68 | { | |
a71b65b1 | 69 | auth_clients = rb_dictionary_create("pending auth clients", rb_uint32cmp); |
731d1289 EM |
70 | timeout_ev = rb_event_addish("provider_timeout_event", provider_timeout_event, NULL, 1); |
71 | ||
72 | load_provider(&rdns_provider); | |
73 | load_provider(&ident_provider); | |
74 | load_provider(&blacklist_provider); | |
75 | load_provider(&opm_provider); | |
76 | } | |
77 | ||
78 | /* Terminate all providers */ | |
79 | void | |
80 | destroy_providers(void) | |
81 | { | |
d955cd9f | 82 | rb_dlink_node *ptr, *nptr; |
a71b65b1 AC |
83 | rb_dictionary_iter iter; |
84 | struct auth_client *auth; | |
85 | struct auth_provider *provider; | |
731d1289 EM |
86 | |
87 | /* Cancel outstanding connections */ | |
a71b65b1 | 88 | RB_DICTIONARY_FOREACH(auth, &iter, auth_clients) |
731d1289 | 89 | { |
a5f52774 SA |
90 | auth_client_ref(auth); |
91 | ||
a71b65b1 | 92 | /* TBD - is this the right thing? */ |
4434f375 EM |
93 | reject_client(auth, UINT32_MAX, "destroy", |
94 | "Authentication system is down... try reconnecting in a few seconds"); | |
a5f52774 SA |
95 | |
96 | auth_client_unref(auth); | |
731d1289 EM |
97 | } |
98 | ||
d955cd9f | 99 | RB_DLINK_FOREACH_SAFE(ptr, nptr, auth_providers.head) |
731d1289 | 100 | { |
d955cd9f SA |
101 | struct auth_provider *provider = ptr->data; |
102 | ||
731d1289 EM |
103 | if(provider->destroy) |
104 | provider->destroy(); | |
d955cd9f SA |
105 | |
106 | rb_dlinkDelete(ptr, &auth_providers); | |
731d1289 EM |
107 | } |
108 | ||
a71b65b1 | 109 | rb_dictionary_destroy(auth_clients, NULL, NULL); |
731d1289 EM |
110 | rb_event_delete(timeout_ev); |
111 | } | |
112 | ||
2b0cc3d3 | 113 | /* Load a provider */ |
60374ac9 EM |
114 | void |
115 | load_provider(struct auth_provider *provider) | |
2b0cc3d3 | 116 | { |
731d1289 EM |
117 | /* Assign a PID */ |
118 | if(rb_dlink_list_length(&free_pids) > 0) | |
b2ede1aa | 119 | { |
731d1289 EM |
120 | /* use the free list */ |
121 | provider->id = RB_POINTER_TO_UINT(free_pids.head->data); | |
122 | rb_dlinkDestroy(free_pids.head, &free_pids); | |
b2ede1aa | 123 | } |
731d1289 | 124 | else |
4434f375 | 125 | { |
075d4d56 | 126 | if(allocated_pids == MAX_PROVIDERS || allocated_pids == UINT32_MAX) |
4434f375 | 127 | { |
4434f375 EM |
128 | warn_opers(L_WARN, "Cannot load additional provider, max reached!"); |
129 | return; | |
130 | } | |
131 | ||
075d4d56 | 132 | provider->id = allocated_pids++; |
4434f375 | 133 | } |
3e875f62 | 134 | |
a51487e0 EM |
135 | if(provider->opt_handlers != NULL) |
136 | { | |
137 | struct auth_opts_handler *handler; | |
138 | ||
139 | for(handler = provider->opt_handlers; handler->option != NULL; handler++) | |
140 | rb_dictionary_add(authd_option_handlers, handler->option, handler); | |
141 | } | |
142 | ||
ee7f9271 | 143 | if(provider->stats_handler.letter != '\0') |
1729f46e | 144 | authd_stat_handlers[(unsigned char)provider->stats_handler.letter] = provider->stats_handler.handler; |
ee7f9271 | 145 | |
9f9ab5c2 EM |
146 | if(provider->init != NULL) |
147 | provider->init(); | |
148 | ||
d955cd9f | 149 | rb_dlinkAdd(provider, &provider->node, &auth_providers); |
2b0cc3d3 EM |
150 | } |
151 | ||
60374ac9 EM |
152 | void |
153 | unload_provider(struct auth_provider *provider) | |
2b0cc3d3 | 154 | { |
a51487e0 EM |
155 | if(provider->opt_handlers != NULL) |
156 | { | |
157 | struct auth_opts_handler *handler; | |
158 | ||
159 | for(handler = provider->opt_handlers; handler->option != NULL; handler++) | |
160 | rb_dictionary_delete(authd_option_handlers, handler->option); | |
161 | } | |
ee7f9271 EM |
162 | |
163 | if(provider->stats_handler.letter != '\0') | |
1729f46e | 164 | authd_stat_handlers[(unsigned char)provider->stats_handler.letter] = NULL; |
ee7f9271 | 165 | |
9f9ab5c2 EM |
166 | if(provider->destroy != NULL) |
167 | provider->destroy(); | |
168 | ||
d955cd9f | 169 | rb_dlinkDelete(&provider->node, &auth_providers); |
2b0cc3d3 | 170 | |
731d1289 EM |
171 | /* Reclaim ID */ |
172 | rb_dlinkAddAlloc(RB_UINT_TO_POINTER(provider->id), &free_pids); | |
2b0cc3d3 EM |
173 | } |
174 | ||
b585278b AC |
175 | void |
176 | auth_client_free(struct auth_client *auth) | |
177 | { | |
178 | rb_dictionary_delete(auth_clients, RB_UINT_TO_POINTER(auth->cid)); | |
179 | rb_free(auth->data); | |
180 | rb_free(auth); | |
181 | } | |
2b0cc3d3 | 182 | |
b585278b | 183 | /* Cancel outstanding providers for a client (if any). */ |
60374ac9 EM |
184 | void |
185 | cancel_providers(struct auth_client *auth) | |
2b0cc3d3 | 186 | { |
9f928dc5 SA |
187 | if(auth->providers_cancelled) |
188 | return; | |
189 | ||
190 | auth->providers_cancelled = true; | |
191 | ||
a5f52774 | 192 | if(auth->providers_active > 0) |
2b0cc3d3 | 193 | { |
d955cd9f | 194 | rb_dlink_node *ptr; |
4434f375 | 195 | |
d955cd9f | 196 | RB_DLINK_FOREACH(ptr, auth_providers.head) |
4434f375 | 197 | { |
d955cd9f SA |
198 | struct auth_provider *provider = ptr->data; |
199 | ||
4434f375 EM |
200 | if(provider->cancel != NULL && is_provider_running(auth, provider->id)) |
201 | /* Cancel if required */ | |
202 | provider->cancel(auth); | |
203 | } | |
2b0cc3d3 EM |
204 | } |
205 | } | |
206 | ||
84d0b55e | 207 | /* Provider is done */ |
60374ac9 | 208 | void |
731d1289 | 209 | provider_done(struct auth_client *auth, uint32_t id) |
2b0cc3d3 | 210 | { |
d955cd9f | 211 | rb_dlink_node *ptr; |
2b0cc3d3 | 212 | |
4434f375 EM |
213 | lrb_assert(is_provider_running(auth, id)); |
214 | lrb_assert(id != UINT32_MAX); | |
075d4d56 | 215 | lrb_assert(id < allocated_pids); |
4434f375 EM |
216 | |
217 | set_provider_done(auth, id); | |
218 | ||
a5f52774 | 219 | if(auth->providers_active == 0 && !auth->providers_starting) |
2b0cc3d3 | 220 | { |
4434f375 | 221 | /* All done */ |
2f598dac | 222 | accept_client(auth); |
2b0cc3d3 EM |
223 | return; |
224 | } | |
225 | ||
d955cd9f | 226 | RB_DLINK_FOREACH(ptr, auth_providers.head) |
2b0cc3d3 | 227 | { |
d955cd9f SA |
228 | struct auth_provider *provider = ptr->data; |
229 | ||
376ae2e2 | 230 | if(provider->completed != NULL && is_provider_running(auth, provider->id)) |
2b0cc3d3 EM |
231 | /* Notify pending clients who asked for it */ |
232 | provider->completed(auth, id); | |
233 | } | |
234 | } | |
235 | ||
84d0b55e | 236 | /* Reject a client and cancel any outstanding providers */ |
60374ac9 | 237 | void |
731d1289 | 238 | reject_client(struct auth_client *auth, uint32_t id, const char *data, const char *fmt, ...) |
2b0cc3d3 | 239 | { |
64afc358 EM |
240 | char buf[BUFSIZE]; |
241 | va_list args; | |
2b0cc3d3 | 242 | |
a5ab1062 | 243 | va_start(args, fmt); |
64afc358 EM |
244 | vsnprintf(buf, sizeof(buf), fmt, args); |
245 | va_end(args); | |
246 | ||
3ad21f61 EM |
247 | /* We send back username and hostname in case ircd wants to overrule our decision. |
248 | * In the future this may not be the case. | |
249 | * --Elizafox | |
250 | */ | |
731d1289 | 251 | rb_helper_write(authd_helper, "R %x %c %s %s %s :%s", |
84d0b55e | 252 | auth->cid, id != UINT32_MAX ? auth->data[id].provider->letter : '*', |
731d1289 | 253 | auth->username, auth->hostname, |
4434f375 | 254 | data == NULL ? "*" : data, buf); |
2b0cc3d3 | 255 | |
4434f375 EM |
256 | if(id != UINT32_MAX) |
257 | set_provider_done(auth, id); | |
258 | ||
259 | cancel_providers(auth); | |
2b0cc3d3 EM |
260 | } |
261 | ||
2f598dac | 262 | /* Accept a client and cancel outstanding providers if any */ |
60374ac9 | 263 | void |
2f598dac | 264 | accept_client(struct auth_client *auth) |
2b0cc3d3 | 265 | { |
2b0cc3d3 | 266 | rb_helper_write(authd_helper, "A %x %s %s", auth->cid, auth->username, auth->hostname); |
4434f375 | 267 | cancel_providers(auth); |
2b0cc3d3 EM |
268 | } |
269 | ||
2b0cc3d3 | 270 | /* Begin authenticating user */ |
60374ac9 EM |
271 | static void |
272 | start_auth(const char *cid, const char *l_ip, const char *l_port, const char *c_ip, const char *c_port) | |
2b0cc3d3 | 273 | { |
a4da4fe5 | 274 | struct auth_client *auth; |
2392770f | 275 | unsigned long long lcid = strtoull(cid, NULL, 16); |
d955cd9f | 276 | rb_dlink_node *ptr; |
2b0cc3d3 | 277 | |
2392770f | 278 | if(lcid == 0 || lcid > UINT32_MAX) |
2b0cc3d3 EM |
279 | return; |
280 | ||
a4da4fe5 | 281 | auth = rb_malloc(sizeof(struct auth_client)); |
a5f52774 | 282 | auth_client_ref(auth); |
3e875f62 | 283 | auth->cid = (uint32_t)lcid; |
2b0cc3d3 | 284 | |
a71b65b1 AC |
285 | if(rb_dictionary_find(auth_clients, RB_UINT_TO_POINTER(auth->cid)) == NULL) |
286 | rb_dictionary_add(auth_clients, RB_UINT_TO_POINTER(auth->cid), auth); | |
05fdc030 EM |
287 | else |
288 | { | |
2392770f | 289 | warn_opers(L_CRIT, "provider: duplicate client added via start_auth: %s", cid); |
c23f9755 | 290 | exit(EX_PROVIDER_ERROR); |
05fdc030 EM |
291 | } |
292 | ||
2b0cc3d3 EM |
293 | rb_strlcpy(auth->l_ip, l_ip, sizeof(auth->l_ip)); |
294 | auth->l_port = (uint16_t)atoi(l_port); /* should be safe */ | |
32f8c78b | 295 | (void) rb_inet_pton_sock(l_ip, (struct sockaddr *)&auth->l_addr); |
d86692fa | 296 | SET_SS_PORT(&auth->l_addr, htons(auth->l_port)); |
2b0cc3d3 EM |
297 | |
298 | rb_strlcpy(auth->c_ip, c_ip, sizeof(auth->c_ip)); | |
299 | auth->c_port = (uint16_t)atoi(c_port); | |
32f8c78b | 300 | (void) rb_inet_pton_sock(c_ip, (struct sockaddr *)&auth->c_addr); |
d86692fa | 301 | SET_SS_PORT(&auth->c_addr, htons(auth->c_port)); |
2b0cc3d3 | 302 | |
1345a41d EM |
303 | rb_strlcpy(auth->hostname, "*", sizeof(auth->hostname)); |
304 | rb_strlcpy(auth->username, "*", sizeof(auth->username)); | |
305 | ||
075d4d56 | 306 | auth->data = rb_malloc(allocated_pids * sizeof(struct auth_client_data)); |
0cff7adb | 307 | |
05fdc030 | 308 | auth->providers_starting = true; |
d955cd9f | 309 | RB_DLINK_FOREACH(ptr, auth_providers.head) |
2b0cc3d3 | 310 | { |
d955cd9f SA |
311 | struct auth_provider *provider = ptr->data; |
312 | ||
731d1289 | 313 | auth->data[provider->id].provider = provider; |
2b0cc3d3 | 314 | |
05fdc030 EM |
315 | lrb_assert(provider->start != NULL); |
316 | ||
2b0cc3d3 EM |
317 | /* Execute providers */ |
318 | if(!provider->start(auth)) | |
2b0cc3d3 | 319 | /* Rejected immediately */ |
a5f52774 | 320 | goto done; |
9f928dc5 SA |
321 | |
322 | if(auth->providers_cancelled) | |
323 | break; | |
2b0cc3d3 | 324 | } |
05fdc030 | 325 | auth->providers_starting = false; |
2b0cc3d3 EM |
326 | |
327 | /* If no providers are running, accept the client */ | |
a5f52774 | 328 | if(auth->providers_active == 0) |
2f598dac | 329 | accept_client(auth); |
a5f52774 SA |
330 | |
331 | done: | |
332 | auth_client_unref(auth); | |
2b0cc3d3 EM |
333 | } |
334 | ||
335 | /* Callback for the initiation */ | |
60374ac9 EM |
336 | void |
337 | handle_new_connection(int parc, char *parv[]) | |
2b0cc3d3 | 338 | { |
0cff7adb | 339 | if(parc < 6) |
b2ede1aa | 340 | { |
c23f9755 EM |
341 | warn_opers(L_CRIT, "provider: received too few params for new connection (6 expected, got %d)", parc); |
342 | exit(EX_PROVIDER_ERROR); | |
b2ede1aa | 343 | } |
2b0cc3d3 EM |
344 | |
345 | start_auth(parv[1], parv[2], parv[3], parv[4], parv[5]); | |
346 | } | |
60374ac9 EM |
347 | |
348 | void | |
349 | handle_cancel_connection(int parc, char *parv[]) | |
350 | { | |
351 | struct auth_client *auth; | |
2392770f | 352 | unsigned long long lcid; |
60374ac9 EM |
353 | |
354 | if(parc < 2) | |
355 | { | |
c23f9755 EM |
356 | warn_opers(L_CRIT, "provider: received too few params for new connection (2 expected, got %d)", parc); |
357 | exit(EX_PROVIDER_ERROR); | |
60374ac9 EM |
358 | } |
359 | ||
2392770f SA |
360 | lcid = strtoull(parv[1], NULL, 16); |
361 | if(lcid == 0 || lcid > UINT32_MAX) | |
60374ac9 | 362 | { |
2392770f | 363 | warn_opers(L_CRIT, "provider: got a request to cancel a connection that can't exist: %s", parv[1]); |
c23f9755 | 364 | exit(EX_PROVIDER_ERROR); |
60374ac9 EM |
365 | } |
366 | ||
a71b65b1 | 367 | if((auth = rb_dictionary_retrieve(auth_clients, RB_UINT_TO_POINTER((uint32_t)lcid))) == NULL) |
60374ac9 | 368 | { |
5cbfed54 EM |
369 | /* This could happen as a race if we've accepted/rejected but they cancel, so don't die here. |
370 | * --Elizafox */ | |
a3b112f4 | 371 | return; |
60374ac9 EM |
372 | } |
373 | ||
a5f52774 | 374 | auth_client_ref(auth); |
60374ac9 | 375 | cancel_providers(auth); |
a5f52774 | 376 | auth_client_unref(auth); |
60374ac9 | 377 | } |
15c49abb EM |
378 | |
379 | static void | |
380 | provider_timeout_event(void *notused __unused) | |
381 | { | |
382 | struct auth_client *auth; | |
a71b65b1 | 383 | rb_dictionary_iter iter; |
15c49abb EM |
384 | const time_t curtime = rb_current_time(); |
385 | ||
a71b65b1 | 386 | RB_DICTIONARY_FOREACH(auth, &iter, auth_clients) |
15c49abb | 387 | { |
d955cd9f | 388 | rb_dlink_node *ptr; |
15c49abb | 389 | |
a5f52774 SA |
390 | auth_client_ref(auth); |
391 | ||
d955cd9f | 392 | RB_DLINK_FOREACH(ptr, auth_providers.head) |
15c49abb | 393 | { |
d955cd9f | 394 | struct auth_provider *provider = ptr->data; |
a68d9a2b | 395 | const time_t timeout = get_provider_timeout(auth, provider->id); |
15c49abb | 396 | |
376ae2e2 | 397 | if(is_provider_running(auth, provider->id) && provider->timeout != NULL && |
c23f9755 | 398 | timeout > 0 && timeout < curtime) |
15c49abb EM |
399 | { |
400 | provider->timeout(auth); | |
401 | } | |
402 | } | |
a5f52774 SA |
403 | |
404 | auth_client_unref(auth); | |
15c49abb EM |
405 | } |
406 | } |