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