1#ifdef OPEN_DIRECTORY
2#include "open_directory.h"
3#include "chpass.h"
4#include <err.h>
5#include <sys/time.h>
6#include <unistd.h>
7#include <sys/sysctl.h>
8#include <OpenDirectory/OpenDirectory.h>
9#include <OpenDirectory/OpenDirectoryPriv.h>
10
11/*---------------------------------------------------------------------------
12 * PUBLIC setrestricted - sets the restricted flag
13 *---------------------------------------------------------------------------*/
14void
15setrestricted(CFDictionaryRef attrs)
16{
17	const char* user_allowed[] = { "shell", "full name", "office location", "office phone", "home phone", "picture", NULL };
18	const char* root_restricted[] = { "password", "change", "expire", "class", NULL };
19	ENTRY* ep;
20	const char** pp;
21	int restrict_by_default = !master_mode;
22
23	// for ordinary users, everything is restricted except for the values
24	// expressly permitted above
25	// for root, everything is permitted except for the values expressly
26	// restricted above
27
28	for (ep = list; ep->prompt; ep++) {
29		ep->restricted = restrict_by_default;
30		pp = restrict_by_default ? user_allowed : root_restricted;
31		for (; *pp; pp++) {
32			if (strncasecmp(ep->prompt, *pp, ep->len) == 0) {
33				ep->restricted = !restrict_by_default;
34				break;
35			}
36		}
37
38		// If not root, then it is only permitted to change the shell
39		// when the original value is one of the approved shells.
40		// Otherwise, the assumption is that root has given this user
41		// a restricted shell which they must not change away from.
42		if (restrict_by_default && strcmp(ep->prompt, "shell") == 0) {
43			ep->restricted = 1;
44			CFArrayRef values = CFDictionaryGetValue(attrs, kODAttributeTypeUserShell);
45			CFTypeRef value = values && CFArrayGetCount(values) > 0 ? CFArrayGetValueAtIndex(values, 0) : NULL;
46			if (value && CFGetTypeID(value) == CFStringGetTypeID()) {
47				size_t size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(value), kCFStringEncodingUTF8)+1;
48				char* shell = malloc(size);
49				if (CFStringGetCString(value, shell, size, kCFStringEncodingUTF8)) {
50					if (ok_shell(shell)) {
51						ep->restricted = 0;
52					}
53				}
54			}
55		}
56	}
57}
58
59static CFStringRef
60prompt_passwd(CFStringRef user)
61{
62	CFStringRef result = NULL;
63	CFStringRef prompt = CFStringCreateWithFormat(NULL, NULL, CFSTR("Password for %@: "), user);
64	size_t prompt_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(prompt), kCFStringEncodingUTF8);
65	char* buf = malloc(prompt_size);
66	CFStringGetCString(prompt, buf, prompt_size, kCFStringEncodingUTF8);
67	char* pass = getpass(buf);
68	result = CFStringCreateWithCString(NULL, pass, kCFStringEncodingUTF8);
69	memset(pass, 0, strlen(pass));
70	free(buf);
71	CFRelease(prompt);
72	return result;
73}
74
75static void
76show_error(CFErrorRef error) {
77	if (error) {
78		CFStringRef desc = CFErrorCopyDescription(error);
79		if (desc) {
80			cfprintf(stderr, "%s: %@", progname, desc);
81			CFRelease(desc);
82		}
83		desc = CFErrorCopyFailureReason(error);
84		if (desc) cfprintf(stderr, "  %@", desc);
85
86		desc = CFErrorCopyRecoverySuggestion(error);
87		if (desc) cfprintf(stderr, "  %@", desc);
88
89		fprintf(stderr, "\n");
90	}
91}
92
93ODRecordRef
94odGetUser(CFStringRef location, CFStringRef authname, CFStringRef user, CFDictionaryRef* attrs)
95{
96	ODNodeRef node = NULL;
97	ODRecordRef rec = NULL;
98	CFErrorRef error = NULL;
99
100	assert(attrs);
101
102	/*
103	 * Open the specified node, or perform a search.
104	 * Copy the record and put the record's location into DSPath.
105	 */
106	if (location) {
107		node = ODNodeCreateWithName(NULL, kODSessionDefault, location, &error);
108	} else {
109		node = ODNodeCreateWithNodeType(NULL, kODSessionDefault, kODNodeTypeAuthentication, &error);
110	}
111	if (node) {
112		CFTypeRef	vals[] = { kODAttributeTypeStandardOnly };
113		CFArrayRef desiredAttrs = CFArrayCreate(NULL, vals, 1, &kCFTypeArrayCallBacks);
114		rec = ODNodeCopyRecord(node, kODRecordTypeUsers, user, desiredAttrs, &error);
115		if (desiredAttrs) CFRelease(desiredAttrs);
116		CFRelease(node);
117	}
118	if (rec) {
119		*attrs = ODRecordCopyDetails(rec, NULL, &error);
120		if (*attrs) {
121			CFArrayRef values = CFDictionaryGetValue(*attrs, kODAttributeTypeMetaNodeLocation);
122			DSPath = (values && CFArrayGetCount(values) > 0) ? CFArrayGetValueAtIndex(values, 0) : NULL;
123		}
124
125		/*
126		 * Prompt for a password if -u was specified,
127		 * or if we are not root,
128		 * or if we are updating something not on the
129		 * local node.
130		 */
131		if (authname || !master_mode ||
132			(DSPath && CFStringCompareWithOptions(DSPath, CFSTR("/Local/"), CFRangeMake(0, 7), 0) != kCFCompareEqualTo)) {
133
134			CFStringRef password = NULL;
135
136			if (!authname) authname = user;
137
138			password = prompt_passwd(authname);
139			if (!ODRecordSetNodeCredentials(rec, authname, password, &error)) {
140				CFRelease(rec);
141				rec = NULL;
142			}
143		}
144	}
145
146	if (error) show_error(error);
147	return rec;
148}
149
150void
151odUpdateUser(ODRecordRef rec, CFDictionaryRef attrs_orig, CFDictionaryRef attrs)
152{
153	CFErrorRef error = NULL;
154	int updated = 0;
155	ENTRY* ep;
156
157	for (ep = list; ep->prompt; ep++) {
158
159		// Nothing to update
160		if (!rec || !attrs_orig || !attrs) break;
161
162		// No need to update if entry is restricted
163		if (ep->restricted) continue;
164
165		CFArrayRef values_orig = CFDictionaryGetValue(attrs_orig, *ep->attrName);
166		CFTypeRef value_orig = values_orig && CFArrayGetCount(values_orig) ? CFArrayGetValueAtIndex(values_orig, 0) : NULL;
167		CFTypeRef value = CFDictionaryGetValue(attrs, *ep->attrName);
168
169		// No need to update if both values are the same
170		if (value == value_orig) continue;
171
172		// No need to update if strings are equal
173		if (value && value_orig) {
174			if (CFGetTypeID(value_orig) == CFStringGetTypeID() &&
175				CFStringCompare(value_orig, value, 0) == kCFCompareEqualTo) continue;
176		}
177
178		// No need to update if empty string replaces NULL
179		if (!value_orig && value) {
180			if (CFStringGetLength(value) == 0) continue;
181		}
182
183		// Needs update
184		if (value) {
185			// if new value is an empty string, send an empty dictionary which will delete the property.
186			CFIndex count = CFEqual(value, CFSTR("")) ? 0 : 1;
187			CFTypeRef	vals[] = { value };
188			CFArrayRef	values = CFArrayCreate(NULL, vals, count, &kCFTypeArrayCallBacks);
189			if (values && ODRecordSetValue(rec, *ep->attrName, values, &error)) {
190				updated = 1;
191			}
192			if (values) CFRelease(values);
193			if (error) show_error(error);
194		}
195	}
196
197	if (updated) {
198		updated = ODRecordSynchronize(rec, &error);
199		if (error) show_error(error);
200	}
201	if (!updated) {
202		fprintf(stderr, "%s: no changes made\n", progname);
203	}
204}
205#endif /* OPEN_DIRECTORY */
206