]>
Commit | Line | Data |
---|---|---|
ca02e0ec | 1 | /* |
bf95c10a | 2 | * Copyright (C) 1996-2022 The Squid Software Foundation and contributors |
ca02e0ec AJ |
3 | * |
4 | * Squid software is distributed under GPLv2+ license and includes | |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
7 | */ | |
8 | ||
4ebcf1ce MM |
9 | /* |
10 | * ----------------------------------------------------------------------------- | |
11 | * | |
12 | * Author: Markus Moeller (markus_moeller at compuserve.com) | |
13 | * | |
14 | * Copyright (C) 2007 Markus Moeller. All rights reserved. | |
15 | * | |
16 | * This program is free software; you can redistribute it and/or modify | |
17 | * it under the terms of the GNU General Public License as published by | |
18 | * the Free Software Foundation; either version 2 of the License, or | |
19 | * (at your option) any later version. | |
20 | * | |
21 | * This program is distributed in the hope that it will be useful, | |
22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
24 | * GNU General Public License for more details. | |
25 | * | |
26 | * You should have received a copy of the GNU General Public License | |
27 | * along with this program; if not, write to the Free Software | |
28 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. | |
29 | * | |
30 | * As a special exemption, M Moeller gives permission to link this program | |
31 | * with MIT, Heimdal or other GSS/Kerberos libraries, and distribute | |
32 | * the resulting executable, without including the source code for | |
33 | * the Libraries in the source distribution. | |
34 | * | |
35 | * ----------------------------------------------------------------------------- | |
36 | */ | |
37 | ||
38 | #include "squid.h" | |
602d9612 | 39 | #include "rfc1738.h" |
4ebcf1ce MM |
40 | |
41 | #include "negotiate_kerberos.h" | |
42 | ||
f95eb8d8 | 43 | #if HAVE_GSSAPI && HAVE_PAC_SUPPORT |
4ebcf1ce MM |
44 | |
45 | static int bpos; | |
46 | static krb5_data *ad_data; | |
47 | static unsigned char *p; | |
48 | ||
685277d8 MM |
49 | extern int |
50 | check_k5_err(krb5_context context, const char *function, krb5_error_code code); | |
4ebcf1ce MM |
51 | |
52 | void | |
53 | align(int n) | |
54 | { | |
55 | if ( bpos % n != 0 ) { | |
56 | int al; | |
57 | al = (bpos/n); | |
58 | bpos = bpos+(bpos-n*al); | |
59 | } | |
60 | } | |
61 | ||
62 | void | |
63 | getustr(RPC_UNICODE_STRING *string) | |
64 | { | |
65 | ||
66 | string->length = (uint16_t)((p[bpos]<<0) | (p[bpos+1]<<8)); | |
67 | string->maxlength = (uint16_t)((p[bpos+2]<<0) | (p[bpos+2+1]<<8)); | |
68 | string->pointer = (uint32_t)((p[bpos+4]<<0) | (p[bpos+4+1]<<8) | (p[bpos+4+2]<<16) | (p[bpos+4+3]<<24)); | |
69 | bpos = bpos+8; | |
70 | ||
71 | } | |
72 | ||
73 | uint64_t | |
74 | get6byt_be(void) | |
75 | { | |
76 | uint64_t var; | |
77 | ||
78 | var = ((uint64_t)p[bpos+5]<<0) | ((uint64_t)p[bpos+4]<<8) | ((uint64_t)p[bpos+3]<<16) | ((uint64_t)p[bpos+2]<<24) | ((uint64_t)p[bpos+1]<<32) | ((uint64_t)p[bpos]<<40); | |
79 | bpos = bpos+6; | |
80 | ||
81 | return var; | |
82 | } | |
83 | ||
84 | uint32_t | |
85 | get4byt(void) | |
86 | { | |
87 | uint32_t var; | |
88 | ||
89 | var=(uint32_t)((p[bpos]<<0) | (p[bpos+1]<<8) | (p[bpos+2]<<16) | (p[bpos+3]<<24)); | |
90 | bpos = bpos+4; | |
91 | ||
92 | return var; | |
93 | } | |
94 | ||
95 | uint16_t | |
96 | get2byt(void) | |
97 | { | |
98 | uint16_t var; | |
99 | ||
100 | var=(uint16_t)((p[bpos]<<0) | (p[bpos+1]<<8)); | |
101 | bpos = bpos+2; | |
102 | ||
103 | return var; | |
104 | } | |
105 | ||
106 | uint8_t | |
107 | get1byt(void) | |
108 | { | |
109 | uint8_t var; | |
110 | ||
111 | var=(uint8_t)((p[bpos]<<0)); | |
112 | bpos = bpos+1; | |
113 | ||
114 | return var; | |
115 | } | |
116 | ||
8b082ed9 | 117 | static char * |
685277d8 | 118 | pstrcpy( char *src, const char *dst) |
4ebcf1ce MM |
119 | { |
120 | if (dst) { | |
121 | if (strlen(dst)>MAX_PAC_GROUP_SIZE) | |
122 | return NULL; | |
123 | else | |
124 | return strcpy(src,dst); | |
125 | } else | |
126 | return src; | |
127 | } | |
128 | ||
8b082ed9 | 129 | static char * |
685277d8 | 130 | pstrcat( char *src, const char *dst) |
4ebcf1ce MM |
131 | { |
132 | if (dst) { | |
133 | if (strlen(src)+strlen(dst)+1>MAX_PAC_GROUP_SIZE) | |
134 | return NULL; | |
135 | else | |
136 | return strcat(src,dst); | |
137 | } else | |
138 | return src; | |
139 | } | |
140 | ||
141 | int | |
142 | checkustr(RPC_UNICODE_STRING *string) | |
143 | { | |
4ebcf1ce MM |
144 | |
145 | if (string->pointer != 0) { | |
4616aac3 | 146 | uint32_t size,off,len; |
4ebcf1ce MM |
147 | align(4); |
148 | size = (uint32_t)((p[bpos]<<0) | (p[bpos+1]<<8) | (p[bpos+2]<<16) | (p[bpos+3]<<24)); | |
149 | bpos = bpos+4; | |
150 | off = (uint32_t)((p[bpos]<<0) | (p[bpos+1]<<8) | (p[bpos+2]<<16) | (p[bpos+3]<<24)); | |
151 | bpos = bpos+4; | |
152 | len = (uint32_t)((p[bpos]<<0) | (p[bpos+1]<<8) | (p[bpos+2]<<16) | (p[bpos+3]<<24)); | |
153 | bpos = bpos+4; | |
154 | if (len > size || off != 0 || | |
155 | string->length > string->maxlength || len != string->length/2) { | |
156 | debug((char *) "%s| %s: ERROR: RPC_UNICODE_STRING encoding error => size: %d len: %d/%d maxlength: %d offset: %d\n", | |
157 | LogTime(), PROGRAM, size, len, string->length, string->maxlength, off); | |
158 | return -1; | |
159 | } | |
160 | /* UNICODE string */ | |
161 | bpos = bpos+string->length; | |
162 | } | |
163 | return 0; | |
164 | } | |
165 | ||
166 | char ** | |
167 | getgids(char **Rids, uint32_t GroupIds, uint32_t GroupCount) | |
168 | { | |
169 | if (GroupIds!= 0) { | |
170 | uint32_t ngroup; | |
4ebcf1ce MM |
171 | int l; |
172 | ||
173 | align(4); | |
174 | ngroup = get4byt(); | |
175 | if ( ngroup != GroupCount) { | |
176 | debug((char *) "%s| %s: ERROR: Group encoding error => GroupCount: %d Array size: %d\n", | |
177 | LogTime(), PROGRAM, GroupCount, ngroup); | |
178 | return NULL; | |
179 | } | |
180 | debug((char *) "%s| %s: INFO: Found %d rids\n", LogTime(), PROGRAM, GroupCount); | |
181 | ||
182 | Rids=(char **)xcalloc(GroupCount*sizeof(char*),1); | |
183 | for ( l=0; l<(int)GroupCount; l++) { | |
4616aac3 | 184 | uint32_t sauth; |
4ebcf1ce MM |
185 | Rids[l]=(char *)xcalloc(4*sizeof(char),1); |
186 | memcpy((void *)Rids[l],(void *)&p[bpos],4); | |
187 | sauth = get4byt(); | |
188 | debug((char *) "%s| %s: Info: Got rid: %u\n", LogTime(), PROGRAM, sauth); | |
189 | /* attribute */ | |
190 | bpos = bpos+4; | |
191 | } | |
192 | } | |
193 | return Rids; | |
194 | } | |
195 | ||
196 | char * | |
197 | getdomaingids(char *ad_groups, uint32_t DomainLogonId, char **Rids, uint32_t GroupCount) | |
198 | { | |
4616aac3 MM |
199 | if (!ad_groups) { |
200 | debug((char *) "%s| %s: ERR: No space to store groups\n", | |
201 | LogTime(), PROGRAM); | |
202 | return NULL; | |
203 | } | |
204 | ||
4ebcf1ce | 205 | if (DomainLogonId!= 0) { |
4ebcf1ce MM |
206 | uint8_t rev; |
207 | uint64_t idauth; | |
4ebcf1ce MM |
208 | char dli[256]; |
209 | char *ag; | |
4ebcf1ce MM |
210 | int l; |
211 | ||
212 | align(4); | |
213 | ||
4474192d AJ |
214 | uint32_t nauth = get4byt(); |
215 | ||
216 | // check if nauth math will produce invalid length values on 32-bit | |
217 | static uint32_t maxGidCount = (UINT32_MAX-1-1-6)/4; | |
218 | if (nauth > maxGidCount) { | |
219 | debug((char *) "%s| %s: ERROR: Too many groups ! count > %d : %s\n", | |
220 | LogTime(), PROGRAM, maxGidCount, ad_groups); | |
221 | return NULL; | |
222 | } | |
223 | size_t length = 1+1+6+nauth*4; | |
4ebcf1ce MM |
224 | |
225 | /* prepend rids with DomainID */ | |
4ebcf1ce MM |
226 | for (l=0; l<(int)GroupCount; l++) { |
227 | ag=(char *)xcalloc((length+4)*sizeof(char),1); | |
228 | memcpy((void *)ag,(const void*)&p[bpos],1); | |
229 | memcpy((void *)&ag[1],(const void*)&p[bpos+1],1); | |
230 | ag[1] = ag[1]+1; | |
231 | memcpy((void *)&ag[2],(const void*)&p[bpos+2],6+nauth*4); | |
232 | memcpy((void *)&ag[length],(const void*)Rids[l],4); | |
233 | if (l==0) { | |
685277d8 | 234 | if (!pstrcpy(ad_groups,"group=")) { |
4ebcf1ce MM |
235 | debug((char *) "%s| %s: WARN: Too many groups ! size > %d : %s\n", |
236 | LogTime(), PROGRAM, MAX_PAC_GROUP_SIZE, ad_groups); | |
237 | } | |
238 | } else { | |
685277d8 | 239 | if (!pstrcat(ad_groups," group=")) { |
4ebcf1ce MM |
240 | debug((char *) "%s| %s: WARN: Too many groups ! size > %d : %s\n", |
241 | LogTime(), PROGRAM, MAX_PAC_GROUP_SIZE, ad_groups); | |
242 | } | |
243 | } | |
aadbbd7d AJ |
244 | struct base64_encode_ctx ctx; |
245 | base64_encode_init(&ctx); | |
786ee90a | 246 | const uint32_t expectedSz = base64_encode_len(length+4) +1 /* terminator */; |
1d11e9b3 | 247 | char *b64buf = static_cast<char *>(xcalloc(expectedSz, 1)); |
aadbbd7d AJ |
248 | size_t blen = base64_encode_update(&ctx, b64buf, length+4, reinterpret_cast<uint8_t*>(ag)); |
249 | blen += base64_encode_final(&ctx, b64buf+blen); | |
786ee90a | 250 | b64buf[expectedSz-1] = '\0'; |
1d11e9b3 | 251 | if (!pstrcat(ad_groups, b64buf)) { |
4ebcf1ce MM |
252 | debug((char *) "%s| %s: WARN: Too many groups ! size > %d : %s\n", |
253 | LogTime(), PROGRAM, MAX_PAC_GROUP_SIZE, ad_groups); | |
254 | } | |
aadbbd7d | 255 | xfree(b64buf); |
4ebcf1ce MM |
256 | xfree(ag); |
257 | } | |
258 | ||
259 | /* mainly for debug only */ | |
260 | rev = get1byt(); | |
261 | bpos = bpos + 1; /*nsub*/ | |
262 | idauth = get6byt_be(); | |
263 | ||
264 | snprintf(dli,sizeof(dli),"S-%d-%lu",rev,(long unsigned int)idauth); | |
265 | for ( l=0; l<(int)nauth; l++ ) { | |
4616aac3 | 266 | uint32_t sauth; |
4ebcf1ce MM |
267 | sauth = get4byt(); |
268 | snprintf((char *)&dli[strlen(dli)],sizeof(dli)-strlen(dli),"-%u",sauth); | |
269 | } | |
270 | debug((char *) "%s| %s: INFO: Got DomainLogonId %s\n", LogTime(), PROGRAM, dli); | |
271 | } | |
272 | return ad_groups; | |
273 | } | |
274 | ||
275 | char * | |
276 | getextrasids(char *ad_groups, uint32_t ExtraSids, uint32_t SidCount) | |
277 | { | |
278 | if (ExtraSids!= 0) { | |
279 | uint32_t ngroup; | |
280 | uint32_t *pa; | |
281 | char *ag; | |
4ebcf1ce MM |
282 | int l; |
283 | ||
284 | align(4); | |
285 | ngroup = get4byt(); | |
286 | if ( ngroup != SidCount) { | |
287 | debug((char *) "%s| %s: ERROR: Group encoding error => SidCount: %d Array size: %d\n", | |
288 | LogTime(), PROGRAM, SidCount, ngroup); | |
289 | return NULL; | |
290 | } | |
291 | debug((char *) "%s| %s: INFO: Found %d ExtraSIDs\n", LogTime(), PROGRAM, SidCount); | |
292 | ||
293 | pa=(uint32_t *)xmalloc(SidCount*sizeof(uint32_t)); | |
294 | for ( l=0; l < (int)SidCount; l++ ) { | |
295 | pa[l] = get4byt(); | |
296 | bpos = bpos+4; /* attr */ | |
297 | } | |
298 | ||
299 | for ( l=0; l<(int)SidCount; l++ ) { | |
300 | char es[256]; | |
4ebcf1ce MM |
301 | |
302 | if (pa[l] != 0) { | |
4616aac3 MM |
303 | uint8_t rev; |
304 | uint64_t idauth; | |
305 | ||
4474192d AJ |
306 | uint32_t nauth = get4byt(); |
307 | ||
308 | // check if nauth math will produce invalid length values on 32-bit | |
309 | static uint32_t maxGidCount = (UINT32_MAX-1-1-6)/4; | |
310 | if (nauth > maxGidCount) { | |
311 | debug((char *) "%s| %s: ERROR: Too many extra groups ! count > %d : %s\n", | |
312 | LogTime(), PROGRAM, maxGidCount, ad_groups); | |
313 | xfree(pa); | |
314 | return NULL; | |
315 | } | |
4ebcf1ce | 316 | |
4474192d | 317 | size_t length = 1+1+6+nauth*4; |
4ebcf1ce MM |
318 | ag = (char *)xcalloc((length)*sizeof(char),1); |
319 | memcpy((void *)ag,(const void*)&p[bpos],length); | |
320 | if (!ad_groups) { | |
4616aac3 MM |
321 | debug((char *) "%s| %s: ERR: No space to store groups\n", |
322 | LogTime(), PROGRAM); | |
323 | xfree(pa); | |
324 | xfree(ag); | |
325 | return NULL; | |
4ebcf1ce | 326 | } else { |
685277d8 | 327 | if (!pstrcat(ad_groups," group=")) { |
4ebcf1ce MM |
328 | debug((char *) "%s| %s: WARN: Too many groups ! size > %d : %s\n", |
329 | LogTime(), PROGRAM, MAX_PAC_GROUP_SIZE, ad_groups); | |
330 | } | |
331 | } | |
aadbbd7d AJ |
332 | |
333 | struct base64_encode_ctx ctx; | |
334 | base64_encode_init(&ctx); | |
786ee90a | 335 | const uint32_t expectedSz = base64_encode_len(length) +1 /* terminator */; |
1d11e9b3 | 336 | char *b64buf = static_cast<char *>(xcalloc(expectedSz, 1)); |
aadbbd7d AJ |
337 | size_t blen = base64_encode_update(&ctx, b64buf, length, reinterpret_cast<uint8_t*>(ag)); |
338 | blen += base64_encode_final(&ctx, b64buf+blen); | |
786ee90a | 339 | b64buf[expectedSz-1] = '\0'; |
aadbbd7d | 340 | if (!pstrcat(ad_groups, reinterpret_cast<char*>(b64buf))) { |
4ebcf1ce MM |
341 | debug((char *) "%s| %s: WARN: Too many groups ! size > %d : %s\n", |
342 | LogTime(), PROGRAM, MAX_PAC_GROUP_SIZE, ad_groups); | |
343 | } | |
aadbbd7d | 344 | xfree(b64buf); |
4ebcf1ce MM |
345 | xfree(ag); |
346 | ||
347 | rev = get1byt(); | |
348 | bpos = bpos + 1; /* nsub */ | |
349 | idauth = get6byt_be(); | |
350 | ||
351 | snprintf(es,sizeof(es),"S-%d-%lu",rev,(long unsigned int)idauth); | |
4616aac3 MM |
352 | for (int k=0; k<(int)nauth; k++ ) { |
353 | uint32_t sauth; | |
4ebcf1ce MM |
354 | sauth = get4byt(); |
355 | snprintf((char *)&es[strlen(es)],sizeof(es)-strlen(es),"-%u",sauth); | |
356 | } | |
357 | debug((char *) "%s| %s: INFO: Got ExtraSid %s\n", LogTime(), PROGRAM, es); | |
358 | } | |
359 | } | |
360 | xfree(pa); | |
361 | } | |
362 | return ad_groups; | |
363 | } | |
364 | ||
365 | char * | |
366 | get_ad_groups(char *ad_groups, krb5_context context, krb5_pac pac) | |
367 | { | |
368 | krb5_error_code ret; | |
369 | RPC_UNICODE_STRING EffectiveName; | |
370 | RPC_UNICODE_STRING FullName; | |
371 | RPC_UNICODE_STRING LogonScript; | |
372 | RPC_UNICODE_STRING ProfilePath; | |
373 | RPC_UNICODE_STRING HomeDirectory; | |
374 | RPC_UNICODE_STRING HomeDirectoryDrive; | |
375 | RPC_UNICODE_STRING LogonServer; | |
376 | RPC_UNICODE_STRING LogonDomainName; | |
377 | uint32_t GroupCount=0; | |
378 | uint32_t GroupIds=0; | |
379 | uint32_t LogonDomainId=0; | |
380 | uint32_t SidCount=0; | |
381 | uint32_t ExtraSids=0; | |
382 | /* | |
383 | uint32_t ResourceGroupDomainSid=0; | |
384 | uint32_t ResourceGroupCount=0; | |
385 | uint32_t ResourceGroupIds=0; | |
386 | */ | |
387 | char **Rids=NULL; | |
388 | int l=0; | |
389 | ||
4616aac3 MM |
390 | if (!ad_groups) { |
391 | debug((char *) "%s| %s: ERR: No space to store groups\n", | |
085c4f21 | 392 | LogTime(), PROGRAM); |
4616aac3 MM |
393 | return NULL; |
394 | } | |
395 | ||
1a22a39e | 396 | ad_data = (krb5_data *)xcalloc(1,sizeof(krb5_data)); |
4ebcf1ce MM |
397 | |
398 | #define KERB_LOGON_INFO 1 | |
399 | ret = krb5_pac_get_buffer(context, pac, KERB_LOGON_INFO, ad_data); | |
400 | if (check_k5_err(context, "krb5_pac_get_buffer", ret)) | |
401 | goto k5clean; | |
402 | ||
403 | p = (unsigned char *)ad_data->data; | |
404 | ||
61beade2 | 405 | debug((char *) "%s| %s: INFO: Got PAC data of length %d\n", |
4ebcf1ce MM |
406 | LogTime(), PROGRAM, (int)ad_data->length); |
407 | ||
408 | /* Skip 16 bytes icommon RPC header | |
409 | * Skip 4 bytes RPC unique pointer referent | |
410 | * http://msdn.microsoft.com/en-gb/library/cc237933.aspx | |
411 | */ | |
412 | /* Some data are pointers to data which follows the main KRB5 LOGON structure => | |
413 | * So need to read the data | |
414 | * some logical consistency checks are done when analysineg the pointer data | |
415 | */ | |
416 | bpos = 20; | |
417 | /* 8 bytes LogonTime | |
418 | * 8 bytes LogoffTime | |
419 | * 8 bytes KickOffTime | |
420 | * 8 bytes PasswordLastSet | |
421 | * 8 bytes PasswordCanChange | |
422 | * 8 bytes PasswordMustChange | |
423 | */ | |
424 | bpos = bpos+48; | |
425 | getustr(&EffectiveName); | |
426 | getustr(&FullName); | |
427 | getustr(&LogonScript); | |
428 | getustr(&ProfilePath); | |
429 | getustr(&HomeDirectory); | |
430 | getustr(&HomeDirectoryDrive); | |
431 | /* 2 bytes LogonCount | |
432 | * 2 bytes BadPasswordCount | |
433 | * 4 bytes UserID | |
434 | * 4 bytes PrimaryGroupId | |
435 | */ | |
436 | bpos = bpos+12; | |
437 | GroupCount = get4byt(); | |
438 | GroupIds = get4byt(); | |
439 | /* 4 bytes UserFlags | |
440 | * 16 bytes UserSessionKey | |
441 | */ | |
442 | bpos = bpos+20; | |
443 | getustr(&LogonServer); | |
444 | getustr(&LogonDomainName); | |
445 | LogonDomainId = get4byt(); | |
446 | /* 8 bytes Reserved1 | |
447 | * 4 bytes UserAccountControl | |
448 | * 4 bytes SubAuthStatus | |
449 | * 8 bytes LastSuccessfullLogon | |
450 | * 8 bytes LastFailedLogon | |
451 | * 4 bytes FailedLogonCount | |
452 | * 4 bytes Reserved2 | |
453 | */ | |
454 | bpos = bpos+40; | |
455 | SidCount = get4byt(); | |
456 | ExtraSids = get4byt(); | |
457 | /* 4 bytes ResourceGroupDomainSid | |
458 | * 4 bytes ResourceGroupCount | |
459 | * 4 bytes ResourceGroupIds | |
460 | */ | |
461 | bpos = bpos+12; | |
462 | /* | |
463 | * Read all data from structure => Now check pointers | |
464 | */ | |
465 | if (checkustr(&EffectiveName)<0) | |
466 | goto k5clean; | |
467 | if (checkustr(&FullName)<0) | |
468 | goto k5clean; | |
469 | if (checkustr(&LogonScript)<0) | |
470 | goto k5clean; | |
471 | if (checkustr(&ProfilePath)<0) | |
472 | goto k5clean; | |
473 | if (checkustr(&HomeDirectory)<0) | |
474 | goto k5clean; | |
475 | if (checkustr(&HomeDirectoryDrive)<0) | |
476 | goto k5clean; | |
477 | Rids = getgids(Rids,GroupIds,GroupCount); | |
478 | if (checkustr(&LogonServer)<0) | |
479 | goto k5clean; | |
480 | if (checkustr(&LogonDomainName)<0) | |
481 | goto k5clean; | |
482 | ad_groups = getdomaingids(ad_groups,LogonDomainId,Rids,GroupCount); | |
483 | if ((ad_groups = getextrasids(ad_groups,ExtraSids,SidCount))==NULL) | |
484 | goto k5clean; | |
485 | ||
486 | debug((char *) "%s| %s: INFO: Read %d of %d bytes \n", LogTime(), PROGRAM, bpos, (int)ad_data->length); | |
487 | if (Rids) { | |
488 | for ( l=0; l<(int)GroupCount; l++) { | |
489 | xfree(Rids[l]); | |
490 | } | |
491 | xfree(Rids); | |
492 | } | |
493 | krb5_free_data(context, ad_data); | |
494 | return ad_groups; | |
495 | k5clean: | |
496 | if (Rids) { | |
497 | for ( l=0; l<(int)GroupCount; l++) { | |
498 | xfree(Rids[l]); | |
499 | } | |
500 | xfree(Rids); | |
501 | } | |
502 | krb5_free_data(context, ad_data); | |
503 | return NULL; | |
504 | } | |
505 | #endif | |
f53969cc | 506 |