1/* 2 * Copyright (c) 2003-2013 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23/* 24 * AFPUsers.c 25 * - create/maintain AFP logins 26 */ 27#include <unistd.h> 28#include <stdlib.h> 29#include <stdio.h> 30#include <syslog.h> 31#include <mach/boolean.h> 32#include <sys/errno.h> 33#include <limits.h> 34#include <sys/types.h> 35#include <pwd.h> 36#include <grp.h> 37#include <stdarg.h> 38#include <CoreFoundation/CoreFoundation.h> 39#include <SystemConfiguration/SCPrivate.h> // for _SC_cfstring_to_cstring 40#include <OpenDirectory/OpenDirectory.h> 41 42#include "netinfo.h" 43#include "NICache.h" 44#include "NICachePrivate.h" 45#include "AFPUsers.h" 46#include "NetBootServer.h" 47#include "cfutil.h" 48 49extern void 50my_log(int priority, const char *message, ...); 51 52#define kAFPUserODRecord CFSTR("record") 53#define kAFPUserUID CFSTR("uid") 54#define kAFPUserPassword CFSTR("passwd") 55#define kAFPUserDatePasswordLastSet CFSTR("setdate") 56 57#define BSDPD_CREATOR "bsdpd" 58#define MAX_RETRY 5 59 60#define CHARSET_LOWERCASE "abcdefghijklmnopqrstuvwxyz" 61#define CHARSET_LOWERCASE_LENGTH (sizeof(CHARSET_LOWERCASE) - 1) 62#define CHARSET_UPPERCASE "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 63#define CHARSET_UPPERCASE_LENGTH (sizeof(CHARSET_UPPERCASE) - 1) 64#define CHARSET_NUMBERS "0123456789" 65#define CHARSET_NUMBERS_LENGTH (sizeof(CHARSET_NUMBERS) - 1) 66 67struct charset_info { 68 const char * charset; 69 int length; 70}; 71 72static void 73generate_random_password(char * passwd, size_t passwd_len) 74{ 75 static const struct charset_info charsets[] = { 76 { CHARSET_LOWERCASE, CHARSET_LOWERCASE_LENGTH }, 77 { CHARSET_UPPERCASE, CHARSET_UPPERCASE_LENGTH }, 78 { CHARSET_NUMBERS, CHARSET_NUMBERS_LENGTH }, 79 { CHARSET_SYMBOLS, CHARSET_SYMBOLS_LENGTH }, 80 }; 81 size_t charsets_size = sizeof(charsets) / sizeof(charsets[0]); 82 int idx; 83 uint32_t used; 84 85 memset(passwd, 0, passwd_len); 86 used = 0; 87 88 for (idx = 0; idx < (passwd_len - 1); idx++) { 89 int charsetidx = (int)(arc4random() % charsets_size); 90 const struct charset_info * info; 91 92 if (idx >= (passwd_len - charsets_size)) { 93 /* 94 * Nearly finished generating the password. Make sure that we 95 * have picked at least one character from each charset. 96 */ 97 size_t count = 0; 98 while ((used & (1 << charsetidx)) != 0 && count < charsets_size) { 99 count++; 100 if (++charsetidx >= charsets_size) { 101 charsetidx = 0; 102 } 103 } 104 } 105 info = charsets + charsetidx; 106 used |= (1 << charsetidx); 107 passwd[idx] = info->charset[arc4random() % info->length]; 108 } 109} 110 111static uid_t 112uid_from_odrecord(ODRecordRef record) 113{ 114 uid_t uid = -2; 115 CFArrayRef values = NULL; 116 117 values = ODRecordCopyValues(record, CFSTR(kDS1AttrUniqueID), NULL); 118 if ((values != NULL) && (CFArrayGetCount(values) > 0)) { 119 char buf[64]; 120 char * end; 121 CFStringRef uidStr; 122 unsigned long val; 123 124 uidStr = CFArrayGetValueAtIndex(values, 0); 125 (void) _SC_cfstring_to_cstring(uidStr, buf, sizeof(buf), 126 kCFStringEncodingASCII); 127 errno = 0; 128 val = strtoul(buf, &end, 0); 129 if ((buf[0] != '\0') && (*end == '\0') && (errno == 0)) { 130 uid = (uid_t)val; 131 } 132 } 133 my_CFRelease(&values); 134 return (uid); 135} 136 137static AFPUserRef 138AFPUser_create(ODRecordRef record) 139{ 140 AFPUserRef user; 141 uid_t uid; 142 CFNumberRef uid_cf; 143 144 user = CFDictionaryCreateMutable(NULL, 0, 145 &kCFTypeDictionaryKeyCallBacks, 146 &kCFTypeDictionaryValueCallBacks); 147 CFDictionarySetValue(user, kAFPUserODRecord, record); 148 uid = uid_from_odrecord(record); 149 uid_cf = CFNumberCreate(NULL, kCFNumberSInt32Type, &uid); 150 CFDictionarySetValue(user, kAFPUserUID, uid_cf); 151 CFRelease(uid_cf); 152 return (user); 153} 154 155void 156AFPUserList_free(AFPUserListRef users) 157{ 158 my_CFRelease(&users->node); 159 my_CFRelease(&users->list); 160 bzero(users, sizeof(*users)); 161} 162 163Boolean 164AFPUserList_init(AFPUserListRef users) 165{ 166 CFErrorRef error; 167 int i; 168 int n; 169 CFArrayRef results; 170 ODQueryRef query; 171 172 bzero(users, sizeof(*users)); 173 174 users->node = ODNodeCreateWithNodeType(NULL, kODSessionDefault, 175 kODNodeTypeLocalNodes, &error); 176 if (users->node == NULL) { 177 my_log(LOG_NOTICE, 178 "AFPUserList_init: ODNodeCreateWithNodeType() failed"); 179 goto failed; 180 } 181 182 query = ODQueryCreateWithNode(NULL, 183 users->node, // inNode 184 CFSTR(kDSStdRecordTypeUsers), // inRecordTypeOrList 185 CFSTR(NIPROP__CREATOR), // inAttribute 186 kODMatchEqualTo, // inMatchType 187 CFSTR(BSDPD_CREATOR), // inQueryValueOrList 188 CFSTR(kDSAttributesAll), // inReturnAttributeOrList 189 0, // inMaxResults 190 &error); 191 if (query == NULL) { 192 my_log(LOG_NOTICE, "AFPUserList_init: ODQueryCreateWithNode() failed"); 193 my_CFRelease(&error); 194 goto failed; 195 } 196 197 results = ODQueryCopyResults(query, FALSE, &error); 198 CFRelease(query); 199 if (results == NULL) { 200 my_log(LOG_NOTICE, "AFPUserList_init: ODQueryCopyResults() failed"); 201 my_CFRelease(&error); 202 goto failed; 203 } 204 205 users->list = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 206 n = CFArrayGetCount(results); 207 for (i = 0; i < n; i++) { 208 ODRecordRef record; 209 AFPUserRef user; 210 211 record = (ODRecordRef)CFArrayGetValueAtIndex(results, i); 212 user = AFPUser_create(record); 213 CFArrayAppendValue(users->list, user); 214 CFRelease(user); 215 } 216 CFRelease(results); 217 return (TRUE); 218 219 failed: 220 AFPUserList_free(users); 221 return (FALSE); 222} 223 224static __inline__ Boolean 225S_uid_taken(AFPUserListRef users, CFStringRef uid) 226{ 227 CFErrorRef error; 228 Boolean taken = FALSE; 229 ODQueryRef query; 230 CFArrayRef results; 231 232 query = ODQueryCreateWithNode(NULL, 233 users->node, // inNode 234 CFSTR(kDSStdRecordTypeUsers), // inRecordTypeOrList 235 CFSTR(kDS1AttrUniqueID), // inAttribute 236 kODMatchEqualTo, // inMatchType 237 uid, // inQueryValueOrList 238 NULL, // inReturnAttributeOrList 239 0, // inMaxResults 240 &error); 241 if (query == NULL) { 242 my_log(LOG_NOTICE, "S_uid_taken: ODQueryCreateWithNode() failed"); 243 my_CFRelease(&error); 244 goto failed; 245 } 246 247 results = ODQueryCopyResults(query, FALSE, &error); 248 CFRelease(query); 249 if (results == NULL) { 250 my_log(LOG_NOTICE, "S_uid_taken: ODQueryCopyResults() failed"); 251 my_CFRelease(&error); 252 goto failed; 253 } 254 255 if (CFArrayGetCount(results) > 0) { 256 taken = TRUE; 257 } 258 CFRelease(results); 259 260 failed: 261 return (taken); 262} 263 264static void 265_myCFDictionarySetStringValueAsArray(CFMutableDictionaryRef dict, 266 CFStringRef key, CFStringRef str) 267{ 268 CFArrayRef array; 269 270 array = CFArrayCreate(NULL, (const void **)&str, 271 1, &kCFTypeArrayCallBacks); 272 CFDictionarySetValue(dict, key, array); 273 CFRelease(array); 274 return; 275} 276 277Boolean 278AFPUserList_create(AFPUserListRef users, gid_t gid, 279 uid_t start, int count) 280{ 281 CFMutableDictionaryRef attributes; 282 char buf[256]; 283 CFStringRef gidStr; 284 int need; 285 Boolean ret = FALSE; 286 uid_t scan; 287 288 need = count - CFArrayGetCount(users->list); 289 if (need <= 0) { 290 return (TRUE); 291 } 292 293 attributes = CFDictionaryCreateMutable(NULL, 0, 294 &kCFTypeDictionaryKeyCallBacks, 295 &kCFTypeDictionaryValueCallBacks); 296 _myCFDictionarySetStringValueAsArray(attributes, 297 CFSTR(kDS1AttrUserShell), 298 CFSTR("/usr/bin/false")); 299 300 snprintf(buf, sizeof(buf), "%d", gid); 301 gidStr = CFStringCreateWithCString(NULL, buf, kCFStringEncodingASCII); 302 _myCFDictionarySetStringValueAsArray(attributes, 303 CFSTR(kDS1AttrPrimaryGroupID), 304 gidStr); 305 CFRelease(gidStr); 306 _myCFDictionarySetStringValueAsArray(attributes, 307 CFSTR(kDS1AttrPassword), 308 CFSTR("*")); 309 _myCFDictionarySetStringValueAsArray(attributes, 310 CFSTR(NIPROP__CREATOR), 311 CFSTR(BSDPD_CREATOR)); 312 313 for (scan = start; need > 0; scan++) { 314 CFErrorRef error = NULL; 315 ODRecordRef record = NULL; 316 CFStringRef uidStr; 317 CFDictionaryRef user; 318 CFStringRef userStr = NULL; 319 320 snprintf(buf, sizeof(buf), "%d", scan); 321 uidStr = CFStringCreateWithCString(NULL, buf, kCFStringEncodingASCII); 322 if (S_uid_taken(users, uidStr)) { 323 goto nextUid; 324 } 325 _myCFDictionarySetStringValueAsArray(attributes, 326 CFSTR(kDS1AttrUniqueID), 327 uidStr); 328 snprintf(buf, sizeof(buf), NETBOOT_USER_PREFIX "%03d", scan); 329 userStr = CFStringCreateWithCString(NULL, buf, kCFStringEncodingASCII); 330 331 _myCFDictionarySetStringValueAsArray(attributes, 332 CFSTR(kDS1AttrDistinguishedName), 333 userStr); 334 record = ODNodeCreateRecord(users->node, 335 CFSTR(kDSStdRecordTypeUsers), 336 userStr, 337 attributes, 338 &error); 339 if (record == NULL) { 340 my_log(LOG_NOTICE, 341 "AFPUserList_create: ODNodeCreateRecord() failed"); 342 goto nextUid; 343 } 344 345 if (!ODRecordSynchronize(record, &error)) { 346 my_log(LOG_NOTICE, 347 "AFPUserList_create: ODRecordSynchronize() failed"); 348 goto nextUid; 349 } 350 user = AFPUser_create(record); 351 CFArrayAppendValue(users->list, user); 352 CFRelease(user); 353 need--; 354 355 nextUid: 356 my_CFRelease(&record); 357 my_CFRelease(&uidStr); 358 my_CFRelease(&userStr); 359 if (error != NULL) { 360 my_CFRelease(&error); 361 goto done; 362 } 363 } 364 365 ret = TRUE; 366 367 done: 368 my_CFRelease(&attributes); 369 return (ret); 370} 371 372AFPUserRef 373AFPUserList_lookup(AFPUserListRef users, CFStringRef afp_user) 374{ 375 int i; 376 int n; 377 378 n = CFArrayGetCount(users->list); 379 for (i = 0; i < n; i++) { 380 CFStringRef name; 381 AFPUserRef user; 382 ODRecordRef record; 383 384 user = (AFPUserRef)CFArrayGetValueAtIndex(users->list, i); 385 record = (ODRecordRef)CFDictionaryGetValue(user, kAFPUserODRecord); 386 name = ODRecordGetRecordName(record); 387 if (CFEqual(name, afp_user)) { 388 return (user); 389 } 390 } 391 392 return (NULL); 393} 394 395uid_t 396AFPUser_get_uid(AFPUserRef user) 397{ 398 uid_t uid = -2; 399 400 CFNumberGetValue(CFDictionaryGetValue(user, kAFPUserUID), 401 kCFNumberSInt32Type, &uid); 402 return (uid); 403} 404 405char * 406AFPUser_get_user(AFPUserRef user, char *buf, size_t buf_len) 407{ 408 CFStringRef name; 409 ODRecordRef record; 410 411 record = (ODRecordRef)CFDictionaryGetValue(user, kAFPUserODRecord); 412 name = ODRecordGetRecordName(record); 413 (void) _SC_cfstring_to_cstring(name, buf, buf_len, kCFStringEncodingASCII); 414 return buf; 415} 416 417#define AFPUSER_PASSWORD_CHANGE_INTERVAL ((int)8) 418/* 419 * Function: AFPUser_set_random_password 420 * Purpose: 421 * Set a random password for the user and returns it in passwd. 422 * Do not change the password again until AFPUSER_PASSWORD_CHANGE_INTERVAL 423 * has elapsed. This overcomes the problem where every client 424 * request packet is duplicated. In that case, the client tries to use 425 * a password that subsequently gets changed when the duplicate arrives. 426 */ 427Boolean 428AFPUser_set_random_password(AFPUserRef user, 429 char * passwd, size_t passwd_len) 430{ 431 CFDateRef last_set; 432 Boolean ok = TRUE; 433 CFDateRef now; 434 CFStringRef pw; 435 ODRecordRef record; 436 437 now = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent()); 438 pw = CFDictionaryGetValue(user, kAFPUserPassword); 439 last_set = CFDictionaryGetValue(user, kAFPUserDatePasswordLastSet); 440 if (pw != NULL && last_set != NULL 441 && (CFDateGetTimeIntervalSinceDate(now, last_set) 442 < AFPUSER_PASSWORD_CHANGE_INTERVAL)) { 443 /* return what we have */ 444#ifdef TEST_AFPUSERS 445 printf("No need to change the password %d < %d\n", 446 (int)CFDateGetTimeIntervalSinceDate(now, last_set), 447 AFPUSER_PASSWORD_CHANGE_INTERVAL); 448#endif /* TEST_AFPUSERS */ 449 (void)_SC_cfstring_to_cstring(pw, passwd, passwd_len, 450 kCFStringEncodingASCII); 451 CFDictionarySetValue(user, kAFPUserDatePasswordLastSet, now); 452 } 453 else { 454 generate_random_password(passwd, passwd_len); 455 456 record = (ODRecordRef)CFDictionaryGetValue(user, kAFPUserODRecord); 457 pw = CFStringCreateWithCString(NULL, passwd, kCFStringEncodingASCII); 458 ok = ODRecordChangePassword(record, NULL, pw, NULL); 459 if (ok) { 460 CFDictionarySetValue(user, kAFPUserPassword, pw); 461 CFDictionarySetValue(user, kAFPUserDatePasswordLastSet, now); 462 } 463 else { 464 my_log(LOG_NOTICE, "AFPUser_set_random_password:" 465 " ODRecordChangePassword() failed"); 466 CFDictionaryRemoveValue(user, kAFPUserPassword); 467 CFDictionaryRemoveValue(user, kAFPUserDatePasswordLastSet); 468 } 469 CFRelease(pw); 470 } 471 CFRelease(now); 472 return ok; 473} 474 475#ifdef TEST_AFPUSERS 476 477#include "afp.h" 478 479#define USECS_PER_SEC 1000000 480/* 481 * Function: timeval_subtract 482 * 483 * Purpose: 484 * Computes result = tv1 - tv2. 485 */ 486void 487timeval_subtract(struct timeval tv1, struct timeval tv2, 488 struct timeval * result) 489{ 490 result->tv_sec = tv1.tv_sec - tv2.tv_sec; 491 result->tv_usec = tv1.tv_usec - tv2.tv_usec; 492 if (result->tv_usec < 0) { 493 result->tv_usec += USECS_PER_SEC; 494 result->tv_sec--; 495 } 496 return; 497} 498 499void 500timestamp_printf(char * msg) 501{ 502 static struct timeval tvp = {0,0}; 503 struct timeval tv; 504 505 gettimeofday(&tv, 0); 506 if (tvp.tv_sec) { 507 struct timeval result; 508 509 timeval_subtract(tv, tvp, &result); 510 printf("%d.%06d (%d.%06d): %s\n", 511 (int)tv.tv_sec, 512 (int)tv.tv_usec, 513 (int)result.tv_sec, 514 (int)result.tv_usec, msg); 515 } 516 else 517 printf("%d.%06d (%d.%06d): %s\n", 518 (int)tv.tv_sec, (int)tv.tv_usec, 0, 0, msg); 519 tvp = tv; 520} 521 522void 523AFPUserList_print(AFPUserListRef users) 524{ 525 CFShow(users->list); 526} 527 528int 529main(int argc, char * argv[]) 530{ 531 CFIndex i; 532 CFIndex n; 533 AFPUserList users; 534 struct group * group_ent_p; 535 int count; 536 int start; 537 538 if (argc < 3) { 539 printf("usage: AFPUsers user_count start\n"); 540 exit(1); 541 } 542 543 group_ent_p = getgrnam(NETBOOT_GROUP); 544 if (group_ent_p == NULL) { 545 printf("Group '%s' missing\n", NETBOOT_GROUP); 546 exit(1); 547 } 548 549 count = strtol(argv[1], NULL, 0); 550 if (count < 0 || count > 100) { 551 printf("invalid user_count\n"); 552 exit(1); 553 } 554 start = strtol(argv[2], NULL, 0); 555 if (start <= 0) { 556 printf("invalid start\n"); 557 exit(1); 558 } 559 timestamp_printf("before processing existing users"); 560 AFPUserList_init(&users); 561 timestamp_printf("after processing existing users"); 562 //AFPUserList_print(&users); 563 564 timestamp_printf("before creating new users"); 565 AFPUserList_create(&users, group_ent_p->gr_gid, start, count); 566 timestamp_printf("after creating new users"); 567 //AFPUserList_print(&users); 568 569 timestamp_printf("before setting passwords"); 570 n = CFArrayGetCount(users.list); 571 for (i = 0; i < n; i++) { 572 char pass_buf[AFP_PASSWORD_LEN + 1]; 573 AFPUserRef user; 574 575 user = (AFPUserRef)CFArrayGetValueAtIndex(users.list, i); 576 AFPUser_set_random_password(user, pass_buf, sizeof(pass_buf)); 577 } 578 timestamp_printf("after setting passwords"); 579 580 printf("Sleeping 1 second\n"); 581 sleep (1); 582 timestamp_printf("before setting passwords again"); 583 n = CFArrayGetCount(users.list); 584 for (i = 0; i < n; i++) { 585 char pass_buf[AFP_PASSWORD_LEN + 1]; 586 AFPUserRef user; 587 588 user = (AFPUserRef)CFArrayGetValueAtIndex(users.list, i); 589 AFPUser_set_random_password(user, pass_buf, sizeof(pass_buf)); 590 } 591 timestamp_printf("after setting passwords again"); 592 593 printf("Sleeping 1 second\n"); 594 sleep(1); 595 596 timestamp_printf("before setting passwords for 3rd time"); 597 for (i = 0; i < n; i++) { 598 char pass_buf[AFP_PASSWORD_LEN + 1]; 599 AFPUserRef user; 600 601 user = (AFPUserRef)CFArrayGetValueAtIndex(users.list, i); 602 AFPUser_set_random_password(user, pass_buf, sizeof(pass_buf)); 603 } 604 timestamp_printf("after setting passwords for 3rd time"); 605 606 printf("sleeping %d seconds\n", AFPUSER_PASSWORD_CHANGE_INTERVAL); 607 sleep(AFPUSER_PASSWORD_CHANGE_INTERVAL); 608 609 timestamp_printf("before setting passwords for second time"); 610 for (i = 0; i < n; i++) { 611 char pass_buf[AFP_PASSWORD_LEN + 1]; 612 AFPUserRef user; 613 614 user = (AFPUserRef)CFArrayGetValueAtIndex(users.list, i); 615 AFPUser_set_random_password(user, pass_buf, sizeof(pass_buf)); 616 } 617 timestamp_printf("after setting passwords for second time"); 618 619 AFPUserList_free(&users); 620 printf("sleeping for 60 seconds, run leaks on %d\n", getpid()); 621 sleep(60); 622 exit(0); 623 return (0); 624} 625 626void 627my_log(int priority, const char *message, ...) 628{ 629 va_list ap; 630 631 va_start(ap, message); 632 vprintf(message, ap); 633 return; 634} 635 636#endif /* TEST_AFPUSERS */ 637