Libinfo-324.1.tar.gz
[apple/libinfo.git] / membership.subproj / membership.c
1 /*
2 * Copyright (c) 2004-2007 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License"). You may not use this file except in compliance with the
9 * License. Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
11 *
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22
23 #include <stdlib.h>
24 #include <sys/errno.h>
25 #include <mach/mach.h>
26 #include "membership.h"
27 #include "membershipPriv.h"
28 #include <servers/bootstrap.h>
29 #include <libkern/OSByteOrder.h>
30 #ifdef DS_AVAILABLE
31 #include <DSmemberdMIG.h>
32 #include <DSmemberdMIG_types.h>
33 #endif
34
35 #ifdef DS_AVAILABLE
36 extern mach_port_t _mbr_port;
37 extern int _ds_running(void);
38
39 static const uint8_t _mbr_root_uuid[] = {0xff, 0xff, 0xee, 0xee, 0xdd, 0xdd, 0xcc, 0xcc, 0xbb, 0xbb, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00};
40
41 #define MAX_LOOKUP_ATTEMPTS 10
42 #endif
43
44 __private_extern__ uid_t
45 audit_token_uid(audit_token_t a)
46 {
47 /*
48 * This should really call audit_token_to_au32,
49 * but that's in libbsm, not in a Libsystem library.
50 */
51 return (uid_t)a.val[1];
52 }
53
54 #ifdef DS_AVAILABLE
55 static int
56 _mbr_MembershipCall(struct kauth_identity_extlookup *req)
57 {
58 audit_token_t token;
59 kern_return_t status;
60 uint32_t i;
61
62 if (_ds_running() == 0) return EIO;
63 if (_mbr_port == MACH_PORT_NULL) return EIO;
64
65 memset(&token, 0, sizeof(audit_token_t));
66
67 status = MIG_SERVER_DIED;
68 for (i = 0; (_mbr_port != MACH_PORT_NULL) && (status == MIG_SERVER_DIED) && (i < MAX_LOOKUP_ATTEMPTS); i++)
69 {
70 status = memberdDSmig_MembershipCall(_mbr_port, req, &token);
71 if (status == MACH_SEND_INVALID_DEST)
72 {
73 mach_port_mod_refs(mach_task_self(), _mbr_port, MACH_PORT_RIGHT_SEND, -1);
74 _mbr_port = MACH_PORT_NULL;
75 _ds_running();
76 status = MIG_SERVER_DIED;
77 }
78 }
79
80 if (status != KERN_SUCCESS) return EIO;
81 if (audit_token_uid(token) != 0) return EAUTH;
82
83 return 0;
84 }
85 #endif
86
87 #ifdef DS_AVAILABLE
88 static int
89 _mbr_MapName(char *name, int type, guid_t *uu)
90 {
91 kern_return_t status;
92 audit_token_t token;
93 uint32_t i;
94
95 if (name == NULL) return EINVAL;
96 if (strlen(name) > 255) return EINVAL;
97
98 if (_ds_running() == 0) return EIO;
99 if (_mbr_port == MACH_PORT_NULL) return EIO;
100
101 memset(&token, 0, sizeof(audit_token_t));
102
103 status = MIG_SERVER_DIED;
104 for (i = 0; (_mbr_port != MACH_PORT_NULL) && (status == MIG_SERVER_DIED) && (i < MAX_LOOKUP_ATTEMPTS); i++)
105 {
106 status = memberdDSmig_MapName(_mbr_port, type, name, uu, &token);
107 if (status == KERN_FAILURE) return ENOENT;
108
109 if (status == MACH_SEND_INVALID_DEST)
110 {
111 mach_port_mod_refs(mach_task_self(), _mbr_port, MACH_PORT_RIGHT_SEND, -1);
112 _mbr_port = MACH_PORT_NULL;
113 _ds_running();
114 status = MIG_SERVER_DIED;
115 }
116 }
117
118 if (status != KERN_SUCCESS) return EIO;
119 if (audit_token_uid(token) != 0) return EAUTH;
120
121 return 0;
122 }
123 #endif
124
125 #ifdef DS_AVAILABLE
126 static int
127 _mbr_ClearCache()
128 {
129 kern_return_t status;
130 uint32_t i;
131
132 if (_ds_running() == 0) return EIO;
133 if (_mbr_port == MACH_PORT_NULL) return EIO;
134
135 status = MIG_SERVER_DIED;
136 for (i = 0; (_mbr_port != MACH_PORT_NULL) && (status == MIG_SERVER_DIED) && (i < MAX_LOOKUP_ATTEMPTS); i++)
137 {
138 status = memberdDSmig_ClearCache(_mbr_port);
139 if (status == MACH_SEND_INVALID_DEST)
140 {
141 mach_port_mod_refs(mach_task_self(), _mbr_port, MACH_PORT_RIGHT_SEND, -1);
142 _mbr_port = MACH_PORT_NULL;
143 _ds_running();
144 status = MIG_SERVER_DIED;
145 }
146 }
147
148 if (status != KERN_SUCCESS) return EIO;
149
150 return 0;
151 }
152 #endif
153
154 int
155 mbr_uid_to_uuid(uid_t id, uuid_t uu)
156 {
157 #ifdef DS_AVAILABLE
158 struct kauth_identity_extlookup request;
159 int status;
160
161 if (id == 0)
162 {
163 memcpy(uu, _mbr_root_uuid, sizeof(uuid_t));
164 return 0;
165 }
166
167 /* used as a byte order field */
168 request.el_seqno = 1;
169 request.el_flags = KAUTH_EXTLOOKUP_VALID_UID | KAUTH_EXTLOOKUP_WANT_UGUID;
170 request.el_uid = id;
171
172 status = _mbr_MembershipCall(&request);
173 if (status != 0) return status;
174 if ((request.el_flags & KAUTH_EXTLOOKUP_VALID_UGUID) == 0) return ENOENT;
175
176 memcpy(uu, &request.el_uguid, sizeof(guid_t));
177 return 0;
178 #else
179 return EIO;
180 #endif
181 }
182
183 int
184 mbr_gid_to_uuid(gid_t id, uuid_t uu)
185 {
186 #ifdef DS_AVAILABLE
187 struct kauth_identity_extlookup request;
188 int status;
189
190 request.el_seqno = 1;
191 request.el_flags = KAUTH_EXTLOOKUP_VALID_GID | KAUTH_EXTLOOKUP_WANT_GGUID;
192 request.el_gid = id;
193
194 status = _mbr_MembershipCall(&request);
195 if (status != 0) return status;
196 if ((request.el_flags & KAUTH_EXTLOOKUP_VALID_GGUID) == 0) return ENOENT;
197
198 memcpy(uu, &request.el_gguid, sizeof(guid_t));
199 return 0;
200 #else
201 return EIO;
202 #endif
203 }
204
205 int
206 mbr_uuid_to_id(const uuid_t uu, uid_t *id, int *id_type)
207 {
208 #ifdef DS_AVAILABLE
209 struct kauth_identity_extlookup request;
210 int status;
211
212 if (id == NULL) return EIO;
213 if (id_type == NULL) return EIO;
214
215 if (!memcmp(uu, _mbr_root_uuid, sizeof(uuid_t)))
216 {
217 *id = 0;
218 *id_type = ID_TYPE_UID;
219 return 0;
220 }
221
222 request.el_seqno = 1;
223 request.el_flags = KAUTH_EXTLOOKUP_VALID_UGUID | KAUTH_EXTLOOKUP_VALID_GGUID | KAUTH_EXTLOOKUP_WANT_UID | KAUTH_EXTLOOKUP_WANT_GID;
224 memcpy(&request.el_uguid, uu, sizeof(guid_t));
225 memcpy(&request.el_gguid, uu, sizeof(guid_t));
226
227 status = _mbr_MembershipCall(&request);
228 if (status != 0) return status;
229
230 if ((request.el_flags & KAUTH_EXTLOOKUP_VALID_UID) != 0)
231 {
232 *id = request.el_uid;
233 *id_type = ID_TYPE_UID;
234 }
235 else if ((request.el_flags & KAUTH_EXTLOOKUP_VALID_GID) != 0)
236 {
237 *id = request.el_gid;
238 *id_type = ID_TYPE_GID;
239 }
240 else
241 {
242 return ENOENT;
243 }
244
245 return 0;
246 #else
247 return EIO;
248 #endif
249 }
250
251 int
252 mbr_sid_to_uuid(const nt_sid_t *sid, uuid_t uu)
253 {
254 #ifdef DS_AVAILABLE
255 struct kauth_identity_extlookup request;
256 int status;
257
258 request.el_seqno = 1;
259 request.el_flags = KAUTH_EXTLOOKUP_VALID_GSID | KAUTH_EXTLOOKUP_WANT_GGUID | KAUTH_EXTLOOKUP_VALID_USID | KAUTH_EXTLOOKUP_WANT_UGUID;
260 memset(&request.el_gsid, 0, sizeof(ntsid_t));
261 memcpy(&request.el_gsid, sid, KAUTH_NTSID_SIZE(sid));
262 memset(&request.el_usid, 0, sizeof(ntsid_t));
263 memcpy(&request.el_usid, sid, KAUTH_NTSID_SIZE(sid));
264
265 status = _mbr_MembershipCall(&request);
266 if (status != 0) return status;
267
268 if ((request.el_flags & KAUTH_EXTLOOKUP_VALID_GGUID) != 0) memcpy(uu, &request.el_gguid, sizeof(guid_t));
269 else if ((request.el_flags & KAUTH_EXTLOOKUP_VALID_UGUID) != 0) memcpy(uu, &request.el_uguid, sizeof(guid_t));
270 else return ENOENT;
271
272 return 0;
273 #else
274 return EIO;
275 #endif
276 }
277
278 int
279 mbr_identifier_to_uuid(int id_type, const void *identifier, size_t identifier_size, uuid_t uu)
280 {
281 #ifdef DS_AVAILABLE
282 kern_return_t status;
283 audit_token_t token;
284 vm_offset_t ool = 0;
285 mach_msg_type_number_t oolCnt = 0;
286 uint32_t i;
287 #if __BIG_ENDIAN__
288 id_t newID;
289 #endif
290
291 if (identifier == NULL) return EINVAL;
292 if (identifier_size == 0) return EINVAL;
293 else if (identifier_size == -1) identifier_size = strlen((char*) identifier) + 1;
294
295 if (_ds_running() == 0) return EIO;
296 if (_mbr_port == MACH_PORT_NULL) return EIO;
297
298 memset(&token, 0, sizeof(audit_token_t));
299
300 #if __BIG_ENDIAN__
301 switch (id_type)
302 {
303 case ID_TYPE_UID:
304 case ID_TYPE_GID:
305 if (identifier_size < sizeof(id_t)) return EINVAL;
306 newID = OSSwapInt32(*((id_t *) identifier));
307 identifier = &newID;
308 break;
309 }
310 #endif
311
312 if (identifier_size > MAX_MIG_INLINE_DATA)
313 {
314 if (vm_read(mach_task_self(), (vm_offset_t) identifier, identifier_size, &ool, &oolCnt) != 0) return ENOMEM;
315 identifier = NULL;
316 identifier_size = 0;
317 }
318
319 status = MIG_SERVER_DIED;
320 for (i = 0; (_mbr_port != MACH_PORT_NULL) && (status == MIG_SERVER_DIED) && (i < MAX_LOOKUP_ATTEMPTS); i++)
321 {
322 status = memberdDSmig_MapIdentifier(_mbr_port, id_type, (vm_offset_t)identifier, identifier_size, ool, oolCnt, uu, &token);
323 if (status == KERN_FAILURE) return ENOENT;
324
325 if (status == MACH_SEND_INVALID_DEST)
326 {
327 if (ool != 0) vm_deallocate(mach_task_self(), ool, oolCnt);
328
329 mach_port_mod_refs(mach_task_self(), _mbr_port, MACH_PORT_RIGHT_SEND, -1);
330 _mbr_port = MACH_PORT_NULL;
331 _ds_running();
332 status = MIG_SERVER_DIED;
333 }
334 }
335
336 if (status != KERN_SUCCESS) return EIO;
337 if (audit_token_uid(token) != 0) return EAUTH;
338
339 return 0;
340 #else
341 return EIO;
342 #endif
343 }
344
345 int
346 mbr_uuid_to_sid_type(const uuid_t uu, nt_sid_t *sid, int *id_type)
347 {
348 #ifdef DS_AVAILABLE
349 struct kauth_identity_extlookup request;
350 int status;
351
352 request.el_seqno = 1;
353 request.el_flags = KAUTH_EXTLOOKUP_VALID_UGUID | KAUTH_EXTLOOKUP_VALID_GGUID | KAUTH_EXTLOOKUP_WANT_USID | KAUTH_EXTLOOKUP_WANT_GSID;
354 memcpy(&request.el_uguid, uu, sizeof(guid_t));
355 memcpy(&request.el_gguid, uu, sizeof(guid_t));
356
357 status = _mbr_MembershipCall(&request);
358 if (status != 0) return status;
359
360 if ((request.el_flags & KAUTH_EXTLOOKUP_VALID_USID) != 0)
361 {
362 *id_type = SID_TYPE_USER;
363 memcpy(sid, &request.el_usid, sizeof(nt_sid_t));
364 }
365 else if ((request.el_flags & KAUTH_EXTLOOKUP_VALID_GSID) != 0)
366 {
367 *id_type = SID_TYPE_GROUP;
368 memcpy(sid, &request.el_gsid, sizeof(nt_sid_t));
369 }
370 else
371 {
372 return ENOENT;
373 }
374
375 return 0;
376 #else
377 return EIO;
378 #endif
379 }
380
381 int
382 mbr_uuid_to_sid(const uuid_t uu, nt_sid_t *sid)
383 {
384 #ifdef DS_AVAILABLE
385 int type, status;
386
387 type = 0;
388
389 status = mbr_uuid_to_sid_type(uu, sid, &type);
390 if (status != 0) return status;
391
392 return 0;
393 #else
394 return EIO;
395 #endif
396 }
397
398 int
399 mbr_check_membership(uuid_t user, uuid_t group, int *ismember)
400 {
401 #ifdef DS_AVAILABLE
402 struct kauth_identity_extlookup request;
403 int status;
404
405 request.el_seqno = 1;
406 request.el_flags = KAUTH_EXTLOOKUP_VALID_UGUID | KAUTH_EXTLOOKUP_VALID_GGUID | KAUTH_EXTLOOKUP_WANT_MEMBERSHIP;
407 memcpy(&request.el_uguid, user, sizeof(guid_t));
408 memcpy(&request.el_gguid, group, sizeof(guid_t));
409
410 status = _mbr_MembershipCall(&request);
411 if (status != 0) return status;
412 if ((request.el_flags & KAUTH_EXTLOOKUP_VALID_MEMBERSHIP) == 0) return ENOENT;
413
414 *ismember = ((request.el_flags & KAUTH_EXTLOOKUP_ISMEMBER) != 0);
415 return 0;
416 #else
417 return EIO;
418 #endif
419 }
420
421 int
422 mbr_check_membership_refresh(const uuid_t user, uuid_t group, int *ismember)
423 {
424 #ifdef DS_AVAILABLE
425 struct kauth_identity_extlookup request;
426 int status;
427
428 request.el_seqno = 1;
429 request.el_flags = KAUTH_EXTLOOKUP_VALID_UGUID | KAUTH_EXTLOOKUP_VALID_GGUID | KAUTH_EXTLOOKUP_WANT_MEMBERSHIP | (1<<15);
430 memcpy(&request.el_uguid, user, sizeof(guid_t));
431 memcpy(&request.el_gguid, group, sizeof(guid_t));
432
433 status = _mbr_MembershipCall(&request);
434 if (status != 0) return status;
435 if ((request.el_flags & KAUTH_EXTLOOKUP_VALID_MEMBERSHIP) == 0) return ENOENT;
436
437 *ismember = ((request.el_flags & KAUTH_EXTLOOKUP_ISMEMBER) != 0);
438 return 0;
439 #else
440 return EIO;
441 #endif
442 }
443
444 int
445 mbr_check_membership_by_id(uuid_t user, gid_t group, int *ismember)
446 {
447 #ifdef DS_AVAILABLE
448 struct kauth_identity_extlookup request;
449 int status;
450
451 request.el_seqno = 1;
452 request.el_flags = KAUTH_EXTLOOKUP_VALID_UGUID | KAUTH_EXTLOOKUP_VALID_GID | KAUTH_EXTLOOKUP_WANT_MEMBERSHIP;
453 memcpy(&request.el_uguid, user, sizeof(guid_t));
454 request.el_gid = group;
455
456 status = _mbr_MembershipCall(&request);
457 if (status != 0) return status;
458 if ((request.el_flags & KAUTH_EXTLOOKUP_VALID_MEMBERSHIP) == 0) return ENOENT;
459
460 *ismember = ((request.el_flags & KAUTH_EXTLOOKUP_ISMEMBER) != 0);
461 return 0;
462 #else
463 return EIO;
464 #endif
465 }
466
467 int
468 mbr_reset_cache()
469 {
470 #ifdef DS_AVAILABLE
471 return _mbr_ClearCache();
472 #else
473 return EIO;
474 #endif
475 }
476
477 int
478 mbr_user_name_to_uuid(const char *name, uuid_t uu)
479 {
480 #ifdef DS_AVAILABLE
481 return _mbr_MapName((char *)name, 1, (guid_t *)uu);
482 #else
483 return EIO;
484 #endif
485 }
486
487 int
488 mbr_group_name_to_uuid(const char *name, uuid_t uu)
489 {
490 #ifdef DS_AVAILABLE
491 return _mbr_MapName((char *)name, 0, (guid_t *)uu);
492 #else
493 return EIO;
494 #endif
495 }
496
497 int
498 mbr_check_service_membership(const uuid_t user, const char *servicename, int *ismember)
499 {
500 #ifdef DS_AVAILABLE
501 char *prefix = "com.apple.access_";
502 char *all_services = "com.apple.access_all_services";
503 char groupName[256];
504 uuid_t group_uu;
505 int result, dummy;
506
507 if (servicename == NULL) return EINVAL;
508 if (strlen(servicename) > 255 - strlen(prefix)) return EINVAL;
509
510 /* start by checking "all services" */
511 result = mbr_group_name_to_uuid(all_services, group_uu);
512
513 if (result == EAUTH) return result;
514
515 if (result == ENOENT)
516 {
517 /* all_services group didn't exist, check individual group */
518 memcpy(groupName, prefix, strlen(prefix));
519 strcpy(groupName + strlen(prefix), servicename);
520 result = mbr_group_name_to_uuid(groupName, group_uu);
521 }
522
523 if (result == 0)
524 {
525 result = mbr_check_membership_refresh(user, group_uu, ismember);
526 }
527 else if (result == EAUTH)
528 {
529 return result;
530 }
531 else
532 {
533 /* just force cache update with bogus membership check */
534 memset(group_uu, 0, sizeof(group_uu));
535 mbr_check_membership_refresh(user, group_uu, &dummy);
536 }
537
538 return result;
539 #else
540 return EIO;
541 #endif
542 }
543
544 #ifdef DS_AVAILABLE
545 static char *
546 ConvertBytesToDecimal(char *buffer, unsigned long long value)
547 {
548 char *temp;
549 buffer[24] = '\0';
550 buffer[23] = '0';
551
552 if (value == 0)
553 return &buffer[23];
554
555 temp = &buffer[24];
556 while (value != 0)
557 {
558 temp--;
559 *temp = '0' + (value % 10);
560 value /= 10;
561 }
562
563 return temp;
564 }
565 #endif
566
567 int
568 mbr_sid_to_string(const nt_sid_t *sid, char *string)
569 {
570 #ifdef DS_AVAILABLE
571 char *current = string;
572 long long temp = 0;
573 int i;
574 char tempBuffer[25];
575
576 if (sid->sid_authcount > NTSID_MAX_AUTHORITIES) return EINVAL;
577
578 for (i = 0; i < 6; i++)
579 temp = (temp << 8) | sid->sid_authority[i];
580
581 current[0] = 'S';
582 current[1] = '-';
583 current += 2;
584 strcpy(current, ConvertBytesToDecimal(tempBuffer, sid->sid_kind));
585 current = current + strlen(current);
586 *current = '-';
587 current++;
588 strcpy(current, ConvertBytesToDecimal(tempBuffer, temp));
589
590 for(i=0; i < sid->sid_authcount; i++)
591 {
592 current = current + strlen(current);
593 *current = '-';
594 current++;
595 strcpy(current, ConvertBytesToDecimal(tempBuffer, sid->sid_authorities[i]));
596 }
597
598 return 0;
599 #else
600 return EIO;
601 #endif
602 }
603
604 int
605 mbr_string_to_sid(const char *string, nt_sid_t *sid)
606 {
607 #ifdef DS_AVAILABLE
608 char *current = (char *)string+2;
609 int count = 0;
610 long long temp;
611
612 if (string == NULL) return EINVAL;
613
614 memset(sid, 0, sizeof(nt_sid_t));
615 if (string[0] != 'S' || string[1] != '-') return EINVAL;
616
617 sid->sid_kind = strtol(current, &current, 10);
618 if (*current == '\0') return EINVAL;
619 current++;
620 temp = strtoll(current, &current, 10);
621
622 /* convert to BigEndian before copying */
623 temp = OSSwapHostToBigInt64(temp);
624 memcpy(sid->sid_authority, ((char*)&temp)+2, 6);
625 while (*current != '\0' && count < NTSID_MAX_AUTHORITIES)
626 {
627 current++;
628 sid->sid_authorities[count] = (u_int32_t)strtoll(current, &current, 10);
629 count++;
630 }
631
632 if (*current != '\0') return EINVAL;
633
634 sid->sid_authcount = count;
635
636 return 0;
637 #else
638 return EIO;
639 #endif
640 }
641
642 #ifdef DS_AVAILABLE
643 static void
644 ConvertBytesToHex(char **string, char **data, int numBytes)
645 {
646 int i;
647
648 for (i=0; i < numBytes; i++)
649 {
650 unsigned char hi = ((**data) >> 4) & 0xf;
651 unsigned char low = (**data) & 0xf;
652 if (hi < 10)
653 **string = '0' + hi;
654 else
655 **string = 'A' + hi - 10;
656
657 (*string)++;
658
659 if (low < 10)
660 **string = '0' + low;
661 else
662 **string = 'A' + low - 10;
663
664 (*string)++;
665 (*data)++;
666 }
667 }
668 #endif
669
670 int
671 mbr_uuid_to_string(const uuid_t uu, char *string)
672 {
673 #ifdef DS_AVAILABLE
674 char *guid = (char*)uu;
675 char *strPtr = string;
676 ConvertBytesToHex(&strPtr, &guid, 4);
677 *strPtr = '-'; strPtr++;
678 ConvertBytesToHex(&strPtr, &guid, 2);
679 *strPtr = '-'; strPtr++;
680 ConvertBytesToHex(&strPtr, &guid, 2);
681 *strPtr = '-'; strPtr++;
682 ConvertBytesToHex(&strPtr, &guid, 2);
683 *strPtr = '-'; strPtr++;
684 ConvertBytesToHex(&strPtr, &guid, 6);
685 *strPtr = '\0';
686
687 return 0;
688 #else
689 return EIO;
690 #endif
691 }
692
693 int
694 mbr_string_to_uuid(const char *string, uuid_t uu)
695 {
696 #ifdef DS_AVAILABLE
697 short dataIndex = 0;
698 int isFirstNibble = 1;
699
700 if (string == NULL) return EINVAL;
701 if (strlen(string) > MBR_UU_STRING_SIZE) return EINVAL;
702
703 while (*string != '\0' && dataIndex < 16)
704 {
705 char nibble;
706
707 if (*string >= '0' && *string <= '9')
708 nibble = *string - '0';
709 else if (*string >= 'A' && *string <= 'F')
710 nibble = *string - 'A' + 10;
711 else if (*string >= 'a' && *string <= 'f')
712 nibble = *string - 'a' + 10;
713 else
714 {
715 if (*string != '-')
716 return EINVAL;
717 string++;
718 continue;
719 }
720
721 if (isFirstNibble)
722 {
723 uu[dataIndex] = nibble << 4;
724 isFirstNibble = 0;
725 }
726 else
727 {
728 uu[dataIndex] |= nibble;
729 dataIndex++;
730 isFirstNibble = 1;
731 }
732
733 string++;
734 }
735
736 if (dataIndex != 16) return EINVAL;
737
738 return 0;
739 #else
740 return EIO;
741 #endif
742 }
743