]> jfr.im git - irc/evilnet/x3.git/blob - src/x3ldap.c
adding email support to ldap logins
[irc/evilnet/x3.git] / src / x3ldap.c
1 /* x3ldap.c - LDAP functionality for x3, by Rubin
2 * Copyright 2002-2007 x3 Development Team
3 *
4 * This file is part of x3.
5 *
6 * x3 is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with srvx; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
19 *
20 *
21 * TODO:
22 * * get queries working in static existance, so i understand how it works
23 * * get ldap enabled in ./configure
24 * * x3.conf settings to enable/configure its use
25 * * generic functions to enable ldap
26 * * nickserv.c work to use said functions.
27 */
28
29 #include "config.h"
30 #ifdef WITH_LDAP
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <ldap.h>
35
36 #include "conf.h"
37 #include "global.h"
38 #include "log.h"
39 #include "x3ldap.h"
40
41 extern struct nickserv_config nickserv_conf;
42
43
44 LDAP *ld = NULL;
45
46 int ldap_do_init()
47 {
48 if(!nickserv_conf.ldap_enable)
49 return false;
50 /* TODO: check here for all required config options and exit() out if not present */
51 ld = ldap_init(nickserv_conf.ldap_host, nickserv_conf.ldap_port);
52 if(ld == NULL) {
53 log_module(MAIN_LOG, LOG_ERROR, "LDAP initilization failed!\n");
54 exit(1);
55 }
56 ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &nickserv_conf.ldap_version);
57 log_module(MAIN_LOG, LOG_INFO, "Success! ldap_init() was successfull in connecting to %s port %d\n", nickserv_conf.ldap_host, nickserv_conf.ldap_port );
58 return true;
59 }
60
61
62 /* Try to auth someone. If theres problems, try reconnecting
63 * once every 10 seconds for 1 minute.
64 * TODO: move this stuff to config file
65 */
66 unsigned int ldap_do_bind( const char *dn, const char *pass)
67 {
68 int q;
69
70 int n = 0;
71 while(1) {
72 q = ldap_simple_bind_s(ld, dn, pass);
73 if(q == LDAP_SUCCESS) {
74 log_module(MAIN_LOG, LOG_DEBUG, "bind() successfull! You are bound as %s", dn);
75 /* unbind now */
76 return q;
77 }
78 else if(q == LDAP_INVALID_CREDENTIALS) {
79 return q;
80 }
81 else {
82 log_module(MAIN_LOG, LOG_ERROR, "Bind failed: %s/****** (%d)", dn, q);
83 ldap_perror(ld, "ldap");
84 ldap_do_init();
85 }
86 if(n++ > 1) {
87 /* TODO: return to the user that this is a connection error and not a problem
88 * with their password
89 */
90 log_module(MAIN_LOG, LOG_ERROR, "Failing to reconnect to ldap server. Auth failing.");
91 return q;
92 }
93 }
94 log_module(MAIN_LOG, LOG_ERROR, "ldap_do_bind falling off the end. this shouldnt happen");
95 return q;
96 }
97 int ldap_do_admin_bind()
98 {
99 if(!(nickserv_conf.ldap_admin_dn && *nickserv_conf.ldap_admin_dn &&
100 nickserv_conf.ldap_admin_pass && *nickserv_conf.ldap_admin_pass)) {
101 log_module(MAIN_LOG, LOG_ERROR, "Tried to admin bind, but no admin credentials configured in config file. ldap_admin_dn/ldap_admin_pass");
102 return LDAP_OTHER; /* not configured to do this */
103 }
104 return(ldap_do_bind(nickserv_conf.ldap_admin_dn, nickserv_conf.ldap_admin_pass));
105 }
106
107
108 unsigned int ldap_check_auth( char *account, char *pass)
109 {
110 char buff[MAXLEN];
111
112 if(!nickserv_conf.ldap_enable)
113 return LDAP_OTHER;
114
115 memset(buff, 0, MAXLEN);
116 snprintf(buff, sizeof(buff)-1, nickserv_conf.ldap_dn_fmt /*"uid=%s,ou=Users,dc=afternet,dc=org"*/, account);
117 return ldap_do_bind(buff, pass);
118
119 }
120
121 int ldap_search_user(char *account, LDAPMessage **entry)
122 {
123
124 char filter[MAXLEN+1];
125 int rc;
126 LDAPMessage *res;
127
128 struct timeval timeout;
129
130 memset(filter, 0, MAXLEN+1);
131 snprintf(filter, MAXLEN, "%s=%s", nickserv_conf.ldap_field_account, account);
132 /*
133 Now we do a search;
134 */
135 timeout.tv_usec = 0;
136 timeout.tv_sec = 5;
137 if(LDAP_SUCCESS != ( rc = ldap_do_admin_bind())) {
138 log_module(MAIN_LOG, LOG_ERROR, "failed to bind as admin");
139 return rc;
140 }
141 if( (rc = ldap_search_st(ld, nickserv_conf.ldap_base, LDAP_SCOPE_ONELEVEL, filter, NULL, 0, &timeout, &res)) != LDAP_SUCCESS) {
142 log_module(MAIN_LOG, LOG_ERROR, "search failed: %s %s: %s", nickserv_conf.ldap_base, filter, ldap_err2string(rc));
143 return(rc);
144 }
145 log_module(MAIN_LOG, LOG_DEBUG, "Search successfull! %s %s\n", nickserv_conf.ldap_base, filter);
146 if(ldap_count_entries(ld, res) != 1) {
147 log_module(MAIN_LOG, LOG_ERROR, "LDAP search got %d entries when looking for %s", ldap_count_entries(ld, res), account);
148 return(LDAP_OTHER); /* Search was a success, but user not found.. */
149 }
150 log_module(MAIN_LOG, LOG_DEBUG, "LDAP search got %d entries", ldap_count_entries(ld, res));
151 *entry = ldap_first_entry(ld, res);
152 return(rc);
153 }
154
155 /* queries the ldap server for account..
156 * if a single account match is found,
157 * email is allocated and set to the email address
158 * and returns LDAP_SUCCESS. returns LDAP_OTHER if
159 * 0 or 2+ entries are matched, or the proper ldap error
160 * code for other errors.
161 */
162 int ldap_get_user_info(char *account, char **email)
163 {
164 int rc;
165 char **value;
166 LDAPMessage *entry, *res;
167 *email = NULL;
168 if( (rc = ldap_search_user(account, &res)) == LDAP_SUCCESS) {
169 entry = ldap_first_entry(ld, res);
170 value = ldap_get_values(ld, entry, nickserv_conf.ldap_field_email);
171 if(!value) {
172 return(LDAP_OTHER);
173 }
174 *email = strdup(value[0]);
175 log_module(MAIN_LOG, LOG_DEBUG, "%s: %s\n", nickserv_conf.ldap_field_email, value[0]);
176 /*
177 value = ldap_get_values(ld, entry, "description");
178 log_module(MAIN_LOG, LOG_DEBUG, "Description: %s\n", value[0]);
179 value = ldap_get_values(ld, entry, "userPassword");
180 log_module(MAIN_LOG, LOG_DEBUG, "pass: %s\n", value ? value[0] : "error");
181 */
182 }
183 return(rc);
184 }
185
186 /*
187 ldap_result();
188 ldap_first_entry();
189 ldap_first_attribute();
190 for(;;) {
191 ldap_get_values();
192 ldap_next_attribute();
193 }
194
195 ldap_parse_result();
196
197 ldap_unbind_ext();
198 */
199 /* get errors with ldap_err2string(); */
200
201
202 /********* base64 stuff ***********/
203
204 unsigned char *pack(char *str, unsigned int *len)
205 {
206 int nibbleshift = 4;
207 int first = 1;
208 char *v;
209 static unsigned char buf[MAXLEN+1];
210 int outputpos = -1;
211
212 memset(buf, 0, MAXLEN+1);
213 v = str;
214 while(*v) {
215 char n = *(v++);
216
217 if((n >= '0') && (n <= '9')) {
218 n -= '0';
219 } else if ((n >= 'A') && (n <= 'F')) {
220 n -= ('A' - 10);
221 } else if ((n >= 'a') && (n <= 'f')) {
222 n -= ('a' - 10);
223 } else {
224 printf("pack type H: illegal hex digit %c", n);
225 n = 0;
226 }
227
228 if (first--) {
229 buf[++outputpos] = 0;
230 } else {
231 first = 1;
232 }
233
234 buf[outputpos] |= (n << nibbleshift);
235 nibbleshift = (nibbleshift + 4) & 7;
236 }
237 *len = outputpos+1;
238 return(buf);
239 }
240
241
242 /* from php5 sources */
243 static char base64_table[] =
244 { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
245 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
246 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
247 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
248 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '\0'
249 };
250 static char base64_pad = '=';
251
252 char *base64_encode(const unsigned char *str, int length, int *ret_length)
253 {
254 const unsigned char *current = str;
255 char *p;
256 char *result;
257
258 if ((length + 2) < 0 || ((length + 2) / 3) >= (1 << (sizeof(int) * 8 - 2))) {
259 if (ret_length != NULL) {
260 *ret_length = 0;
261 }
262 return NULL;
263 }
264
265 result = (char *)calloc(((length + 2) / 3) * 4, sizeof(char));
266 p = result;
267
268 while (length > 2) { /* keep going until we have less than 24 bits */
269 *p++ = base64_table[current[0] >> 2];
270 *p++ = base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)];
271 *p++ = base64_table[((current[1] & 0x0f) << 2) + (current[2] >> 6)];
272 *p++ = base64_table[current[2] & 0x3f];
273
274 current += 3;
275 length -= 3; /* we just handle 3 octets of data */
276 }
277
278 /* now deal with the tail end of things */
279 if (length != 0) {
280 *p++ = base64_table[current[0] >> 2];
281 if (length > 1) {
282 *p++ = base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)];
283 *p++ = base64_table[(current[1] & 0x0f) << 2];
284 *p++ = base64_pad;
285 } else {
286 *p++ = base64_table[(current[0] & 0x03) << 4];
287 *p++ = base64_pad;
288 *p++ = base64_pad;
289 }
290 }
291 if (ret_length != NULL) {
292 *ret_length = (int)(p - result);
293 }
294 *p = '\0';
295 return result;
296 }
297
298
299 char **make_object_vals()
300 {
301 unsigned int y;
302 static char **object_vals = NULL;
303
304 if(object_vals)
305 free(object_vals);
306
307 object_vals = malloc(sizeof( *object_vals ) * nickserv_conf.ldap_object_classes->used);
308
309 for(y = 0; y < nickserv_conf.ldap_object_classes->used; y++) {
310 object_vals[y] = nickserv_conf.ldap_object_classes->list[y];
311 }
312 object_vals[y] = NULL;
313 return object_vals;
314 }
315
316 char *make_password(const char *password)
317 {
318 char *base64pass;
319 char crypted[MD5_CRYPT_LENGTH+1];
320 unsigned char *packed;
321 unsigned int len;
322 char *passbuf;
323 cryptpass(password, crypted);
324
325 packed = pack(crypted, &len);
326 base64pass = base64_encode(packed, len, NULL);
327 passbuf = malloc(strlen(base64pass) + 1 + 5);
328 strcpy(passbuf, "{MD5}");
329 strcat(passbuf, base64pass);
330 //log_module(MAIN_LOG, LOG_DEBUG, "Encoded password is: '%s'", passbuf);
331 free(base64pass);
332 return passbuf;
333
334 }
335
336 LDAPMod **make_mods_add(const char *account, const char *password, const char *email, int *num_mods_ret)
337 {
338 static char *account_vals[] = { NULL, NULL };
339 static char *password_vals[] = { NULL, NULL };
340 static char *email_vals[] = { NULL, NULL };
341 int num_mods = 3;
342 int i;
343 /* TODO: take this from nickserv_conf.ldap_add_objects */
344 LDAPMod **mods;
345 static char **object_vals;
346 object_vals = make_object_vals();
347
348 account_vals[0] = (char *) account;
349 password_vals[0] = (char *) password;
350 email_vals[0] = (char *) email;
351
352 if(!(nickserv_conf.ldap_field_account && *nickserv_conf.ldap_field_account))
353 return 0; /* account required */
354 if(!(nickserv_conf.ldap_field_password && *nickserv_conf.ldap_field_password))
355 return 0; /* password required */
356 if(email && *email && nickserv_conf.ldap_field_email && *nickserv_conf.ldap_field_email)
357 num_mods++;
358
359 mods = ( LDAPMod ** ) malloc(( num_mods + 1 ) * sizeof( LDAPMod * ));
360 for( i = 0; i < num_mods; i++) {
361 mods[i] = (LDAPMod *) malloc(sizeof(LDAPMod));
362 memset(mods[i], 0, sizeof(LDAPMod));
363 }
364
365 mods[0]->mod_op = LDAP_MOD_ADD;
366 mods[0]->mod_type = strdup("objectclass");
367 mods[0]->mod_values = object_vals;
368
369 mods[1]->mod_op = LDAP_MOD_ADD;
370 mods[1]->mod_type = strdup(nickserv_conf.ldap_field_account);
371 mods[1]->mod_values = account_vals;
372
373 mods[2]->mod_op = LDAP_MOD_ADD;
374 mods[2]->mod_type = strdup(nickserv_conf.ldap_field_password);
375 mods[2]->mod_values = password_vals;
376
377 if(nickserv_conf.ldap_field_email && *nickserv_conf.ldap_field_email && email && *email) {
378 mods[3]->mod_op = LDAP_MOD_ADD;
379 mods[3]->mod_type = strdup(nickserv_conf.ldap_field_email);
380 mods[3]->mod_values = email_vals;
381 mods[4] = NULL;
382 }
383 else
384 mods[3] = NULL;
385 *num_mods_ret = num_mods;
386 return mods;
387 }
388
389 int ldap_do_add(const char *account, const char *password, const char *email)
390 {
391 char newdn[MAXLEN];
392 LDAPMod **mods;
393 int rc, i;
394 int num_mods;
395 char *passbuf;
396
397 if(LDAP_SUCCESS != ( rc = ldap_do_admin_bind())) {
398 log_module(MAIN_LOG, LOG_ERROR, "failed to bind as admin");
399 return rc;
400 }
401
402 passbuf = make_password(password);
403 snprintf(newdn, MAXLEN-1, nickserv_conf.ldap_dn_fmt, account);
404 mods = make_mods_add(account, password, email, &num_mods);
405 if(!mods) {
406 log_module(MAIN_LOG, LOG_ERROR, "Error building mods for ldap_add");
407 return LDAP_OTHER;
408 }
409 rc = ldap_add_ext_s(ld, newdn, mods, NULL, NULL);
410 if(rc != LDAP_SUCCESS) {
411 log_module(MAIN_LOG, LOG_ERROR, "Error adding ldap account: %s -- %s", account, ldap_err2string(rc));
412 return rc;
413 }
414 //ldap_unbind_s(ld);
415 for(i = 0; i < num_mods; i++) {
416 free(mods[i]->mod_type);
417 free(mods[i]);
418 }
419 free(mods);
420 free(passbuf);
421 return rc;
422 }
423
424 int ldap_delete_account(char *account)
425 {
426 char dn[MAXLEN];
427 memset(dn, 0, MAXLEN);
428 snprintf(dn, MAXLEN-1, nickserv_conf.ldap_dn_fmt, account);
429 return(ldap_delete_s(ld, dn));
430 }
431
432 int ldap_rename_account(char *oldaccount, char *newaccount)
433 {
434 char dn[MAXLEN], newdn[MAXLEN];
435 int rc;
436
437 if(LDAP_SUCCESS != ( rc = ldap_do_admin_bind())) {
438 log_module(MAIN_LOG, LOG_ERROR, "failed to bind as admin");
439 return rc;
440 }
441
442 memset(dn, 0, MAXLEN);
443 memset(newdn, 0, MAXLEN);
444 snprintf(dn, MAXLEN-1, nickserv_conf.ldap_dn_fmt, oldaccount);
445 strcat(newdn, nickserv_conf.ldap_field_account);
446 strcat(newdn, "=");
447 strcat(newdn, newaccount);
448 rc = ldap_modrdn2_s(ld, dn, newdn, true);
449 if(rc != LDAP_SUCCESS) {
450 log_module(MAIN_LOG, LOG_ERROR, "Error modifying ldap account: %s -- %s", oldaccount, ldap_err2string(rc));
451 return rc;
452 }
453 return rc;
454
455 }
456
457 LDAPMod **make_mods_modify(const char *password, const char *email, int *num_mods_ret)
458 {
459 static char *password_vals[] = { NULL, NULL };
460 static char *email_vals[] = { NULL, NULL };
461 int num_mods = 1;
462 int i;
463 /* TODO: take this from nickserv_conf.ldap_add_objects */
464 LDAPMod **mods;
465
466 password_vals[0] = (char *) password;
467 email_vals[0] = (char *) email;
468
469 if(!(nickserv_conf.ldap_field_password && *nickserv_conf.ldap_field_password))
470 return 0; /* password required */
471 if(email && *email && nickserv_conf.ldap_field_email && *nickserv_conf.ldap_field_email)
472 num_mods++;
473
474 mods = ( LDAPMod ** ) malloc(( num_mods + 1 ) * sizeof( LDAPMod * ));
475 for( i = 0; i < num_mods; i++) {
476 mods[i] = (LDAPMod *) malloc(sizeof(LDAPMod));
477 memset(mods[i], 0, sizeof(LDAPMod));
478 }
479
480 i = 0;
481 if(nickserv_conf.ldap_field_password && *nickserv_conf.ldap_field_password &&
482 password) {
483 mods[i]->mod_op = LDAP_MOD_REPLACE;
484 mods[i]->mod_type = strdup(nickserv_conf.ldap_field_password);
485 mods[i]->mod_values = password_vals;
486 i++;
487 }
488
489 if(nickserv_conf.ldap_field_email && *nickserv_conf.ldap_field_email && email) {
490 mods[i]->mod_op = LDAP_MOD_REPLACE;
491 mods[i]->mod_type = strdup(nickserv_conf.ldap_field_email);
492 mods[i]->mod_values = email_vals;
493 i++;
494 }
495 mods[i] = NULL;
496 *num_mods_ret = num_mods;
497 return mods;
498 }
499
500
501 /* Save email or password to server
502 *
503 * password - UNENCRYPTED password. This function encrypts if libs are available
504 * email - email address
505 *
506 * NULL to make no change
507 */
508 int ldap_do_modify(const char *account, const char *password, const char *email)
509 {
510 char dn[MAXLEN];
511 LDAPMod **mods;
512 int rc, i;
513 int num_mods;
514 char *passbuf = NULL;
515
516 if(LDAP_SUCCESS != ( rc = ldap_do_admin_bind())) {
517 log_module(MAIN_LOG, LOG_ERROR, "failed to bind as admin");
518 return rc;
519 }
520
521 if(password) {
522 passbuf = make_password(password);
523 }
524
525 snprintf(dn, MAXLEN-1, nickserv_conf.ldap_dn_fmt, account);
526 mods = make_mods_modify(passbuf, email, &num_mods);
527 if(!mods) {
528 log_module(MAIN_LOG, LOG_ERROR, "Error building mods for ldap_add");
529 return LDAP_OTHER;
530 }
531 rc = ldap_modify_s(ld, dn, mods);
532 if(rc != LDAP_SUCCESS) {
533 log_module(MAIN_LOG, LOG_ERROR, "Error adding ldap account: %s -- %s", account, ldap_err2string(rc));
534 return rc;
535 }
536 for(i = 0; i < num_mods; i++) {
537 free(mods[i]->mod_type);
538 free(mods[i]);
539 }
540 free(mods);
541 if(passbuf)
542 free(passbuf);
543 return rc;
544 }
545
546 void ldap_close()
547 {
548 ldap_unbind(ld);
549 }
550
551 #endif