Multiple calls to ber_printf seem to work better. Closes #106
[freeradius.git] / src / modules / rlm_ldap / edir_ldapext.c
1 /*
2  * Copyright (C) 2002-2004 Novell, Inc.
3  *
4  * edir_ldapext.c  LDAP extension for reading eDirectory universal password
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of version 2 of the GNU General Public License as published
8  * by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13  * more details.
14  *
15  * You should have received a copy of the GNU General Public License along with
16  * this program; if not, contact Novell, Inc.
17  *
18  * To contact Novell about this file by physical or electronic mail, you may
19  * find current contact  information at www.novell.com.
20  *
21  * Copyright 2006 The FreeRADIUS Server Project.
22  */
23
24 #include <freeradius-devel/ident.h>
25 RCSID("$Id$")
26
27 #include <ldap.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <strings.h>
32
33 /* NMAS error codes */
34 #define NMAS_E_BASE                       (-1600)
35
36 #define NMAS_SUCCESS                      0
37 #define NMAS_E_SUCCESS                    NMAS_SUCCESS         /* Alias  */
38 #define NMAS_OK                           NMAS_SUCCESS         /* Alias  */
39
40 #define NMAS_E_FRAG_FAILURE               (NMAS_E_BASE-31)     /* -1631 0xFFFFF9A1 */
41 #define NMAS_E_BUFFER_OVERFLOW            (NMAS_E_BASE-33)     /* -1633 0xFFFFF99F */
42 #define NMAS_E_SYSTEM_RESOURCES           (NMAS_E_BASE-34)     /* -1634 0xFFFFF99E */
43 #define NMAS_E_INSUFFICIENT_MEMORY        (NMAS_E_BASE-35)     /* -1635 0xFFFFF99D */
44 #define NMAS_E_NOT_SUPPORTED              (NMAS_E_BASE-36)     /* -1636 0xFFFFF99C */
45 #define NMAS_E_INVALID_PARAMETER          (NMAS_E_BASE-43)     /* -1643 0xFFFFF995 */
46 #define NMAS_E_INVALID_VERSION            (NMAS_E_BASE-52)     /* -1652 0xFFFFF98C */
47
48 /* OID of LDAP extenstion calls to read Universal Password */
49 #define NMASLDAP_GET_PASSWORD_REQUEST         "2.16.840.1.113719.1.39.42.100.13"
50 #define NMASLDAP_GET_PASSWORD_RESPONSE        "2.16.840.1.113719.1.39.42.100.14"
51
52 #define NMAS_LDAP_EXT_VERSION 1
53
54 /* OID of LDAP extension call to perform NMAS authentication */
55 #define RADAUTH_OID_NMAS_AUTH_REQUEST         "2.16.840.1.113719.1.510.100.1"
56 #define RADAUTH_OID_NMAS_AUTH_REPLY           "2.16.840.1.113719.1.510.100.2"
57
58 #define RADAUTH_LDAP_EXT_VERSION 1
59
60 #define REQUEST_CHALLENGED 1
61
62
63 /* ------------------------------------------------------------------------
64  *      berEncodePasswordData
65  *      ==============================
66  *      RequestBer contents:
67  *              clientVersion                           INTEGER
68  *              targetObjectDN                          OCTET STRING
69  *              password1                                       OCTET STRING
70  *              password2                                       OCTET STRING
71  *
72  *      Description:
73  *              This function takes the request BER value and input data items
74  *              and BER encodes the data into the BER value
75  *
76  * ------------------------------------------------------------------------ */
77 int berEncodePasswordData(
78         struct berval **requestBV,
79         char    *objectDN,
80         char    *password,
81         char    *password2)
82 {
83         int err = 0, rc=0;
84         BerElement *requestBer = NULL;
85
86         char    * utf8ObjPtr = NULL;
87         int     utf8ObjSize = 0;
88         char    * utf8PwdPtr = NULL;
89         int     utf8PwdSize = 0;
90         char    * utf8Pwd2Ptr = NULL;
91         int     utf8Pwd2Size = 0;
92
93
94         utf8ObjSize = strlen(objectDN)+1;
95         utf8ObjPtr = objectDN;
96
97         if (password != NULL)
98         {
99                 utf8PwdSize = strlen(password)+1;
100                 utf8PwdPtr = password;
101         }
102
103         if (password2 != NULL)
104         {
105                 utf8Pwd2Size = strlen(password2)+1;
106                 utf8Pwd2Ptr = password2;
107         }
108
109         /* Allocate a BerElement for the request parameters.*/
110         if((requestBer = ber_alloc()) == NULL)
111         {
112                 err = NMAS_E_FRAG_FAILURE;
113                 goto Cleanup;
114         }
115
116         if (password != NULL && password2 != NULL)
117         {
118                 /* BER encode the NMAS Version, the objectDN, and the password */
119                 rc = ber_printf(requestBer, "{iooo}", NMAS_LDAP_EXT_VERSION, utf8ObjPtr, utf8ObjSize, utf8PwdPtr, utf8PwdSize, utf8Pwd2Ptr, utf8Pwd2Size);
120         }
121         else if (password != NULL)
122         {
123                 /* BER encode the NMAS Version, the objectDN, and the password */
124                 rc = ber_printf(requestBer, "{ioo}", NMAS_LDAP_EXT_VERSION, utf8ObjPtr, utf8ObjSize, utf8PwdPtr, utf8PwdSize);
125         }
126         else
127         {
128                 /* BER encode the NMAS Version and the objectDN */
129                 rc = ber_printf(requestBer, "{io}", NMAS_LDAP_EXT_VERSION, utf8ObjPtr, utf8ObjSize);
130         }
131
132         if (rc < 0)
133         {
134                 err = NMAS_E_FRAG_FAILURE;
135                 goto Cleanup;
136         }
137         else
138         {
139                 err = 0;
140         }
141
142         /*
143          * Convert the BER we just built to a berval that we'll send with the extended request.
144          */
145         if(ber_flatten(requestBer, requestBV) == LBER_ERROR)
146         {
147                 err = NMAS_E_FRAG_FAILURE;
148                 goto Cleanup;
149         }
150
151 Cleanup:
152
153         if(requestBer)
154         {
155                 ber_free(requestBer, 1);
156         }
157
158         return err;
159 } /* End of berEncodePasswordData */
160
161 /* ------------------------------------------------------------------------
162  *      berDecodeLoginData()
163  *      ==============================
164  *      ResponseBer contents:
165  *              serverVersion                           INTEGER
166  *              error                                   INTEGER
167  *              data                                            OCTET STRING
168  *
169  *      Description:
170  *              This function takes the reply BER Value and decodes the
171  *              NMAS server version and return code and if a non null retData
172  *              buffer was supplied, tries to decode the the return data and length
173  *
174  * ------------------------------------------------------------------------ */
175 int berDecodeLoginData(
176         struct berval *replyBV,
177         int      *serverVersion,
178         size_t   *retDataLen,
179         void     *retData )
180 {
181         int rc=0, err = 0;
182         BerElement *replyBer = NULL;
183         char    *retOctStr = NULL;
184         size_t  retOctStrLen = 0;
185
186         if((replyBer = ber_init(replyBV)) == NULL)
187         {
188                 err = NMAS_E_SYSTEM_RESOURCES;
189                 goto Cleanup;
190         }
191
192         if(retData)
193         {
194                 retOctStrLen = *retDataLen + 1;
195                 retOctStr = (char *)malloc(retOctStrLen);
196                 if(!retOctStr)
197                 {
198                         err = NMAS_E_SYSTEM_RESOURCES;
199                         goto Cleanup;
200                 }
201
202                 if( (rc = ber_scanf(replyBer, "{iis}", serverVersion, &err, retOctStr, &retOctStrLen)) != -1)
203                 {
204                         if (*retDataLen >= retOctStrLen)
205                         {
206                                 memcpy(retData, retOctStr, retOctStrLen);
207                         }
208                         else if (!err)
209                         {
210                                 err = NMAS_E_BUFFER_OVERFLOW;
211                         }
212
213                         *retDataLen = retOctStrLen;
214                 }
215                 else if (!err)
216                 {
217                         err = NMAS_E_FRAG_FAILURE;
218                 }
219         }
220         else
221         {
222                 if( (rc = ber_scanf(replyBer, "{ii}", serverVersion, &err)) == -1)
223                 {
224                         if (!err)
225                         {
226                                 err = NMAS_E_FRAG_FAILURE;
227                         }
228                 }
229         }
230
231 Cleanup:
232
233         if(replyBer)
234         {
235                 ber_free(replyBer, 1);
236         }
237
238         if (retOctStr != NULL)
239         {
240                 memset(retOctStr, 0, retOctStrLen);
241                 free(retOctStr);
242         }
243
244         return err;
245 } /* End of berDecodeLoginData */
246
247 /* -----------------------------------------------------------------------
248  *      nmasldap_get_password()
249  *      ==============================
250  *
251  *      Description:
252  *              This API attempts to get the universal password
253  *
254  * ------------------------------------------------------------------------ */
255 int nmasldap_get_password(
256         LDAP     *ld,
257         char     *objectDN,
258         size_t   *pwdSize,      // in bytes
259         char     *pwd )
260 {
261         int err = 0;
262
263         struct berval *requestBV = NULL;
264         char *replyOID = NULL;
265         struct berval *replyBV = NULL;
266         int serverVersion;
267         char *pwdBuf;
268         size_t pwdBufLen, bufferLen;
269
270 #ifdef  NOT_N_PLAT_NLM
271         int currentThreadGroupID;
272 #endif
273
274         /* Validate char    parameters. */
275         if(objectDN == NULL || (strlen(objectDN) == 0) || pwdSize == NULL || ld == NULL)
276         {
277                 return NMAS_E_INVALID_PARAMETER;
278         }
279
280         bufferLen = pwdBufLen = *pwdSize;
281         pwdBuf = (char *)malloc(pwdBufLen+2);
282         if(pwdBuf == NULL)
283         {
284                 return NMAS_E_INSUFFICIENT_MEMORY;
285         }
286
287 #ifdef  NOT_N_PLAT_NLM
288         currentThreadGroupID = SetThreadGroupID(nmasLDAPThreadGroupID);
289 #endif
290
291         err = berEncodePasswordData(&requestBV, objectDN, NULL, NULL);
292         if(err)
293         {
294                 goto Cleanup;
295         }
296
297         /* Call the ldap_extended_operation (synchronously) */
298         if((err = ldap_extended_operation_s(ld, NMASLDAP_GET_PASSWORD_REQUEST, requestBV, NULL, NULL, &replyOID, &replyBV)))
299         {
300                 goto Cleanup;
301         }
302
303         /* Make sure there is a return OID */
304         if(!replyOID)
305         {
306                 err = NMAS_E_NOT_SUPPORTED;
307                 goto Cleanup;
308         }
309
310         /* Is this what we were expecting to get back. */
311         if(strcmp(replyOID, NMASLDAP_GET_PASSWORD_RESPONSE))
312         {
313                 err = NMAS_E_NOT_SUPPORTED;
314                 goto Cleanup;
315         }
316
317         /* Do we have a good returned berval? */
318         if(!replyBV)
319         {
320                 /*
321                  * No; returned berval means we experienced a rather drastic error.
322                  * Return operations error.
323                  */
324                 err = NMAS_E_SYSTEM_RESOURCES;
325                 goto Cleanup;
326         }
327
328         err = berDecodeLoginData(replyBV, &serverVersion, &pwdBufLen, pwdBuf);
329
330         if(serverVersion != NMAS_LDAP_EXT_VERSION)
331         {
332                 err = NMAS_E_INVALID_VERSION;
333                 goto Cleanup;
334         }
335
336         if (!err && pwdBufLen != 0)
337         {
338                 if (*pwdSize >= pwdBufLen+1 && pwd != NULL)
339                 {
340                         memcpy(pwd, pwdBuf, pwdBufLen);
341                         pwd[pwdBufLen] = 0; /* add null termination */
342                 }
343                 *pwdSize = pwdBufLen; /* does not include null termination */
344         }
345
346 Cleanup:
347
348         if(replyBV)
349         {
350                 ber_bvfree(replyBV);
351         }
352
353         /* Free the return OID string if one was returned. */
354         if(replyOID)
355         {
356                 ldap_memfree(replyOID);
357         }
358
359         /* Free memory allocated while building the request ber and berval. */
360         if(requestBV)
361         {
362                 ber_bvfree(requestBV);
363         }
364
365         if (pwdBuf != NULL)
366         {
367                 memset(pwdBuf, 0, bufferLen);
368                 free(pwdBuf);
369         }
370
371 #ifdef  NOT_N_PLAT_NLM
372         SetThreadGroupID(currentThreadGroupID);
373 #endif
374
375         /* Return the appropriate error/success code. */
376         return err;
377 } /* end of nmasldap_get_password */
378
379 /* ------------------------------------------------------------------------
380  *      berEncodeAuthData
381  *      ==============================
382  *      RequestBer contents:
383  *              targetObjectDN                                  OCTET STRING
384  *              pwd                                             OCTET STRING
385  *              NasIP                                           OCTET STRING
386  *              stete                                           OCTET STRING
387  *
388  *      Description:
389  *              This function takes the request BER value and input data items
390  *              and BER encodes the data into the BER value
391  *
392  * ------------------------------------------------------------------------ */
393 int berEncodeAuthData(
394         struct berval **requestBV,
395         char    *objectDN,
396         char    *pwd,
397         char    *sequence,
398         char    *NasIP,
399         char    *state,
400         int     *auth_state)
401 {
402         int err = 0, rc=0;
403         BerElement *requestBer = NULL;
404
405         char    * utf8ObjPtr = NULL;
406         int     utf8ObjSize = 0;
407         char    * utf8PwdPtr = NULL;
408         int     utf8PwdSize = 0;
409         char    * utf8NasIPPtr = NULL;
410         int     utf8NasIPSize = 0;
411         char    * utf8StatePtr = NULL;
412         int     utf8StateSize = 0;
413         char    * utf8SeqPtr = NULL;
414         int     utf8SeqSize = 0;
415         int state_present = 0;
416
417         utf8ObjSize = strlen(objectDN)+1;
418         utf8ObjPtr = objectDN;
419
420         utf8PwdSize = strlen(pwd);
421         utf8PwdPtr = pwd;
422
423         utf8SeqSize = strlen(sequence)+1;
424         utf8SeqPtr = sequence;
425
426         utf8NasIPSize = strlen(NasIP)+1;
427         utf8NasIPPtr = NasIP;
428
429         /* Allocate a BerElement for the request parameters.*/
430         if((requestBer = ber_alloc()) == NULL)
431         {
432                 err = NMAS_E_FRAG_FAILURE;
433                 goto Cleanup;
434         }
435
436         /* BER encode the NMAS Version, the objectDN, and the password */
437         rc = ber_printf(requestBer, "{i", RADAUTH_LDAP_EXT_VERSION);
438         rc = ber_printf(requestBer, "o", utf8ObjPtr, utf8ObjSize);
439         rc = ber_printf(requestBer, "o", utf8PwdPtr, utf8PwdSize);
440         rc = ber_printf(requestBer, "o", utf8SeqPtr, utf8SeqSize);
441         rc = ber_printf(requestBer, "o", utf8NasIPPtr, utf8NasIPSize);
442
443         if( *auth_state == -2)
444         {
445                 utf8StateSize = strlen(state)+1;
446                 utf8StatePtr = state;
447                 state_present = 1;
448                 rc = ber_printf(requestBer, "io}", state_present, utf8StatePtr, utf8StateSize);
449         }
450         else
451         {
452                 rc = ber_printf(requestBer, "i}", state_present);
453         }
454
455         if (rc < 0)
456         {
457                 err = NMAS_E_FRAG_FAILURE;
458                 goto Cleanup;
459         }
460         else
461         {
462                 err = 0;
463         }
464         /*
465          * Convert the BER we just built to a berval that we'll send with the extended request.
466          */
467         if(ber_flatten(requestBer, requestBV) == -1)
468         {
469                 err = NMAS_E_FRAG_FAILURE;
470                 goto Cleanup;
471         }
472
473 Cleanup:
474
475         if(requestBer)
476         {
477                 ber_free(requestBer, 1);
478         }
479
480         return err;
481 } /* End of berEncodeAuthData */
482
483 /* ------------------------------------------------------------------------
484  *      berDecodeAuthData()
485  *      ==============================
486  *      ResponseBer contents:
487  *              serverVersion                           INTEGER
488  *              auth_state                              INTEGER
489  *              challenge_data                          OCTET STRING
490  *
491  *      Description:
492  *              This function takes the reply BER Value and decodes the
493  *              server version and return code and if a non null retData
494  *              buffer was supplied, tries to decode the the return data and length
495  *
496  * ------------------------------------------------------------------------ */
497 int berDecodeAuthData(
498         struct berval *replyBV,
499         int      *errCode,
500         size_t   *retDataLen,
501         char     *retData,
502         int      *auth_state )
503 {
504         int rc=0, err = 0;
505         BerElement *replyBer = NULL;
506         struct berval   challenge = {0};
507
508         if((replyBer = ber_init(replyBV)) == NULL)
509         {
510                 err = NMAS_E_SYSTEM_RESOURCES; // fix err code
511                 goto Cleanup;
512         }
513         if( (rc = ber_scanf(replyBer, "{ii", errCode, auth_state)) != -1)
514         {
515                 if ( *auth_state != REQUEST_CHALLENGED )
516                 {
517                         if( (rc = ber_scanf(replyBer, "}")) != -1)
518                                 return err;
519                 }
520                 else
521                 {
522                         if( (rc = ber_scanf(replyBer, "o}", &challenge)) != -1)
523                         {
524                                 if (*retDataLen >= challenge.bv_len)
525                                 {
526                                         memcpy(retData, challenge.bv_val, challenge.bv_len);
527                                 }
528                                 *retDataLen = challenge.bv_len;
529                         }
530                 }
531         }
532
533 Cleanup:
534         if(replyBer)
535         {
536                 ber_free(replyBer, 1);
537         }
538
539         return err;
540 }/* End of berDecodeLoginData */
541
542 /* -----------------------------------------------------------------------
543  *      radLdapXtnNMASAuth()
544  *      ==============================
545  *
546  *      Description:
547  *              This API attempts to perform NMAS authentication.
548  *
549  * ------------------------------------------------------------------------ */
550 int radLdapXtnNMASAuth(
551         LDAP    *ld,
552         char    *objectDN,
553         char    *pwd,
554         char    *sequence,
555         char    *NasIPaddr,
556         size_t  *statesize,
557         char    *state,
558         int     *auth_state
559 )
560 {
561         int err = 0;
562
563         struct berval *requestBV = NULL;
564         char *replyOID = NULL;
565         struct berval *replyBV = NULL;
566         int errCode;
567         char *challenge;
568         size_t challengesize;
569
570         challengesize = *statesize;
571         challenge = (char *)malloc(challengesize+2);
572                 if(challenge == NULL)
573                         {
574                                 return NMAS_E_INSUFFICIENT_MEMORY;
575                         }
576
577          /* Validate char    parameters. */
578         if(objectDN == NULL || (strlen(objectDN) == 0) || statesize == NULL || NasIPaddr == NULL || ld == NULL)
579         {
580                 return NMAS_E_INVALID_PARAMETER;
581         }
582
583         err = berEncodeAuthData(&requestBV, objectDN, pwd, sequence, NasIPaddr, state, auth_state);
584
585         if(err)
586         {
587                 goto Cleanup;
588         }
589
590         /* Call the ldap_extended_operation (synchronously) */
591         if((err = ldap_extended_operation_s(ld, RADAUTH_OID_NMAS_AUTH_REQUEST, requestBV, NULL, NULL, &replyOID, &replyBV))!=0)
592         {
593                 goto Cleanup;
594         }
595         /* Make sure there is a return OID */
596         if(!replyOID)
597         {
598                 err = NMAS_E_NOT_SUPPORTED; // change error values
599                 goto Cleanup;
600         }
601
602         /* Is this what we were expecting to get back. */
603         if(strcmp(replyOID, RADAUTH_OID_NMAS_AUTH_REPLY))
604         {
605                 err = NMAS_E_NOT_SUPPORTED; // change return value
606                 goto Cleanup;
607         }
608
609         /* Do we have a good returned berval? */
610         if(!replyBV)
611         {
612                 /*
613                  * No; returned berval means we experienced a rather drastic error.
614                  * Return operations error.
615                  */
616                 err = NMAS_E_SYSTEM_RESOURCES; //change return value
617                 goto Cleanup;
618         }
619         err = berDecodeAuthData(replyBV, &errCode, &challengesize, challenge, auth_state);
620
621 /* errCode return error in case of AUTH-REJECT */
622         if (!err && challengesize!= 0)
623         {
624                 if (*statesize >= challengesize+1 && challenge != NULL)
625                 {
626                         memcpy(state, challenge, challengesize);
627                         state[challengesize] = 0; /* add null termination */
628                 }
629                 *statesize = challengesize; /* does not include null termination */
630         }
631
632 Cleanup:
633         /* Free memory allocated for challenge  */
634         if(challenge)
635         {
636                 free(challenge);
637         }
638
639         if(replyBV)
640         {
641                 ber_bvfree(replyBV);
642         }
643
644         /* Free the return OID string if one was returned. */
645         if(replyOID)
646         {
647                 ldap_memfree(replyOID);
648         }
649
650         /* Free memory allocated while building the request ber and berval. */
651         if(requestBV)
652         {
653                 ber_bvfree(requestBV);
654         }
655
656 #ifdef  NOT_N_PLAT_NLM
657         SetThreadGroupID(currentThreadGroupID);
658 #endif
659
660         /* Return the appropriate error/success code. */
661         return err;
662 }/* End of radLdapXtnNMASAuth */
663