]> git.saurik.com Git - apple/system_cmds.git/blob - chpass.tproj/open_directory.c
system_cmds-431.tar.gz
[apple/system_cmds.git] / chpass.tproj / open_directory.c
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/OpenDirectoryPriv.h>
9 #include <DirectoryService/DirServicesTypes.h>
10
11 /*---------------------------------------------------------------------------
12 * PUBLIC setrestricted - sets the restricted flag
13 *---------------------------------------------------------------------------*/
14 void
15 setrestricted(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, CFSTR(kDS1AttrUserShell));
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
59 static CFStringRef
60 prompt_passwd(CFStringRef user)
61 {
62 CFStringRef result = NULL;
63 CFStringRef prompt = CFStringCreateWithFormat(NULL, NULL, CFSTR("Password for %@: "), user);
64 char buf[128];
65 CFStringGetCString(prompt, buf, sizeof(buf), kCFStringEncodingUTF8);
66 char* pass = getpass(buf);
67 result = CFStringCreateWithCString(NULL, pass, kCFStringEncodingUTF8);
68 memset(pass, 0, strlen(pass));
69 CFRelease(prompt);
70 return result;
71 }
72
73 static void
74 show_error(CFErrorRef error) {
75 if (error) {
76 CFStringRef desc = CFErrorCopyDescription(error);
77 if (desc) {
78 cfprintf(stderr, "%s: %@", progname, desc);
79 CFRelease(desc);
80 }
81 desc = CFErrorCopyFailureReason(error);
82 if (desc) cfprintf(stderr, " %@", desc);
83
84 desc = CFErrorCopyRecoverySuggestion(error);
85 if (desc) cfprintf(stderr, " %@", desc);
86
87 fprintf(stderr, "\n");
88 }
89 }
90
91 static int
92 is_singleuser(void) {
93 uint32_t su = 0;
94 size_t susz = sizeof(su);
95 if (sysctlbyname("kern.singleuser", &su, &susz, NULL, 0) != 0) {
96 return 0;
97 } else {
98 return (int)su;
99 }
100 }
101
102 static int
103 load_DirectoryServicesLocal() {
104 const char* launchctl = "/bin/launchctl";
105 const char* plist = "/System/Library/LaunchDaemons/com.apple.DirectoryServicesLocal.plist";
106
107 pid_t pid = fork();
108 int status, res;
109 switch (pid) {
110 case -1: // ERROR
111 perror("launchctl");
112 return 0;
113 case 0: // CHILD
114 execl(launchctl, launchctl, "load", plist, NULL);
115 /* NOT REACHED */
116 perror("launchctl");
117 exit(1);
118 break;
119 default: // PARENT
120 do {
121 res = waitpid(pid, &status, 0);
122 } while (res == -1 && errno == EINTR);
123 if (res == -1) {
124 perror("launchctl");
125 return 0;
126 }
127 break;
128 }
129 return (WIFEXITED(status) && (WEXITSTATUS(status) == EXIT_SUCCESS));
130 }
131
132 ODRecordRef
133 odGetUser(CFStringRef location, CFStringRef authname, CFStringRef user, CFDictionaryRef* attrs)
134 {
135 ODSessionRef session = NULL;
136 ODNodeRef node = NULL;
137 ODRecordRef rec = NULL;
138 CFErrorRef error = NULL;
139
140 assert(attrs);
141
142 /*
143 * Connect to DS server
144 */
145 session = ODSessionCreate(NULL, NULL, &error);
146 if ( !session && error && CFErrorGetCode(error) == eServerNotRunning ) {
147 /*
148 * In single-user mode, attempt to load the local DS daemon.
149 */
150 if (is_singleuser() && load_DirectoryServicesLocal()) {
151 CFTypeRef keys[] = { kODSessionLocalPath };
152 CFTypeRef vals[] = { CFSTR("/var/db/dslocal") };
153 CFDictionaryRef opts = CFDictionaryCreate(NULL, keys, vals, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
154 if (opts) {
155 session = ODSessionCreate(NULL, opts, &error);
156 CFRelease(opts);
157 }
158
159 if (!location) {
160 location = CFRetain(CFSTR("/Local/Default"));
161 }
162 } else {
163 show_error(error);
164 return -1;
165 }
166 }
167
168 /*
169 * Open the specified node, or perform a search.
170 * Copy the record and put the record's location into DSPath.
171 */
172 if (location) {
173 node = ODNodeCreateWithName(NULL, session, location, &error);
174 } else {
175 node = ODNodeCreateWithNodeType(NULL, session, kODTypeAuthenticationSearchNode, &error);
176 }
177 if (session) CFRelease(session);
178 if (node) {
179 CFTypeRef vals[] = { CFSTR(kDSAttributesStandardAll) };
180 CFArrayRef desiredAttrs = CFArrayCreate(NULL, vals, 1, &kCFTypeArrayCallBacks);
181 rec = ODNodeCopyRecord(node, CFSTR(kDSStdRecordTypeUsers), user, desiredAttrs, &error );
182 if (desiredAttrs) CFRelease(desiredAttrs);
183 CFRelease(node);
184 }
185 if (rec) {
186 *attrs = ODRecordCopyDetails(rec, NULL, &error);
187 if (*attrs) {
188 CFArrayRef values = CFDictionaryGetValue(*attrs, CFSTR(kDSNAttrMetaNodeLocation));
189 DSPath = (values && CFArrayGetCount(values) > 0) ? CFArrayGetValueAtIndex(values, 0) : NULL;
190 }
191
192 /*
193 * Prompt for a password if -u was specified,
194 * or if we are not root,
195 * or if we are updating something not on the
196 * local node.
197 */
198 if (authname || !master_mode ||
199 (DSPath && CFStringCompareWithOptions(DSPath, CFSTR("/Local/"), CFRangeMake(0, 7), 0) != kCFCompareEqualTo)) {
200
201 CFStringRef password = NULL;
202
203 if (!authname) authname = user;
204
205 password = prompt_passwd(authname);
206 if (!ODRecordSetNodeCredentials(rec, authname, password, &error)) {
207 CFRelease(rec);
208 rec = NULL;
209 }
210 }
211 }
212
213 if (error) show_error(error);
214 return rec;
215 }
216
217 void
218 odUpdateUser(ODRecordRef rec, CFDictionaryRef attrs_orig, CFDictionaryRef attrs)
219 {
220 CFErrorRef error = NULL;
221 int updated = 0;
222 ENTRY* ep;
223
224 for (ep = list; ep->prompt; ep++) {
225
226 // Nothing to update
227 if (!rec || !attrs_orig || !attrs) break;
228
229 // No need to update if entry is restricted
230 if (ep->restricted) continue;
231
232 CFArrayRef values_orig = CFDictionaryGetValue(attrs_orig, ep->attrName);
233 CFTypeRef value_orig = values_orig && CFArrayGetCount(values_orig) ? CFArrayGetValueAtIndex(values_orig, 0) : NULL;
234 CFTypeRef value = CFDictionaryGetValue(attrs, ep->attrName);
235
236 // No need to update if both values are the same
237 if (value == value_orig) continue;
238
239 // No need to update if strings are equal
240 if (value && value_orig) {
241 if (CFGetTypeID(value_orig) == CFStringGetTypeID() &&
242 CFStringCompare(value_orig, value, 0) == kCFCompareEqualTo) continue;
243 }
244
245 // No need to update if empty string replaces NULL
246 if (!value_orig && value) {
247 if (CFStringGetLength(value) == 0) continue;
248 }
249
250 // Needs update
251 if (value) {
252 // if new value is an empty string, send an empty dictionary which will delete the property.
253 CFIndex count = CFEqual(value, CFSTR("")) ? 0 : 1;
254 CFTypeRef vals[] = { value };
255 CFArrayRef values = CFArrayCreate(NULL, vals, count, &kCFTypeArrayCallBacks);
256 if (values && ODRecordSetValues(rec, ep->attrName, values, &error)) {
257 updated = 1;
258 }
259 if (values) CFRelease(values);
260 if (error) show_error(error);
261 }
262 }
263
264 if (updated) {
265 updated = ODRecordSynchronize(rec, &error);
266 if (error) show_error(error);
267 }
268 if (!updated) {
269 fprintf(stderr, "%s: no changes made\n", progname);
270 }
271 }
272 #endif /* OPEN_DIRECTORY */