]>
Commit | Line | Data |
---|---|---|
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 */ |