]>
Commit | Line | Data |
---|---|---|
5958d7c0 | 1 | /* |
17d3ee29 | 2 | * Copyright (c) 2000-2009, 2011 Apple Inc. All rights reserved. |
5958d7c0 A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
009ee3c6 | 5 | * |
009ee3c6 A |
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 | |
5958d7c0 A |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
009ee3c6 A |
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 | * | |
5958d7c0 A |
21 | * @APPLE_LICENSE_HEADER_END@ |
22 | */ | |
23 | ||
0fae82ee A |
24 | /* |
25 | * Modification History | |
26 | * | |
4c5e92e2 A |
27 | * January 15, 2004 Allan Nathanson <ajn@apple.com> |
28 | * - limit location changes to "root" (uid==0), users who are | |
29 | * a member of group "admin", and processses which have access | |
30 | * to a local graphics console. | |
31 | * | |
0fae82ee A |
32 | * June 1, 2001 Allan Nathanson <ajn@apple.com> |
33 | * - public API conversion | |
34 | * | |
35 | * January 1, 2001 Allan Nathanson <ajn@apple.com> | |
36 | * - initial revision | |
37 | */ | |
38 | ||
009ee3c6 | 39 | #include <getopt.h> |
5958d7c0 | 40 | #include <unistd.h> |
009ee3c6 | 41 | #include <sysexits.h> |
4c5e92e2 A |
42 | #include <sys/param.h> |
43 | #include <sys/types.h> | |
4c5e92e2 | 44 | #include <sys/stat.h> |
edebe297 | 45 | #include <dlfcn.h> |
4c5e92e2 | 46 | #include <grp.h> |
5958d7c0 | 47 | |
0fae82ee A |
48 | #include <SystemConfiguration/SystemConfiguration.h> |
49 | #include <SystemConfiguration/SCPrivate.h> | |
50 | ||
a40a14f8 | 51 | #if !TARGET_OS_IPHONE |
4c5e92e2 | 52 | #include <Security/AuthSession.h> |
a40a14f8 | 53 | #endif /* !TARGET_OS_IPHONE */ |
4c5e92e2 | 54 | |
5958d7c0 | 55 | |
dbf6a266 | 56 | static Boolean apply = TRUE; |
5958d7c0 A |
57 | |
58 | ||
dbf6a266 | 59 | static const struct option longopts[] = { |
009ee3c6 A |
60 | // { "debug", no_argument, 0, 'd' }, |
61 | // { "verbose", no_argument, 0, 'v' }, | |
62 | // { "do-not-apply", no_argument, 0, 'n' }, | |
63 | { "help", no_argument, 0, '?' }, | |
64 | { 0, 0, 0, 0 } | |
65 | }; | |
66 | ||
67 | ||
4c5e92e2 | 68 | static void |
5958d7c0 A |
69 | usage(const char *command) |
70 | { | |
0fae82ee | 71 | SCPrint(TRUE, stderr, CFSTR("usage: %s [-n] new-set-name\n"), command); |
009ee3c6 | 72 | exit (EX_USAGE); |
5958d7c0 A |
73 | } |
74 | ||
75 | ||
4c5e92e2 A |
76 | static Boolean |
77 | isAdmin() | |
78 | { | |
dbf6a266 A |
79 | gid_t groups[NGROUPS_MAX]; |
80 | int ngroups; | |
81 | ||
82 | if (getuid() == 0) { | |
83 | return TRUE; // if "root" | |
84 | } | |
85 | ||
86 | ngroups = getgroups(NGROUPS_MAX, groups); | |
87 | if(ngroups > 0) { | |
88 | struct group *adminGroup; | |
89 | ||
90 | adminGroup = getgrnam("admin"); | |
91 | if (adminGroup != NULL) { | |
92 | gid_t adminGid = adminGroup->gr_gid; | |
93 | int i; | |
94 | ||
95 | for (i = 0; i < ngroups; i++) { | |
96 | if (groups[i] == adminGid) { | |
97 | return TRUE; // if a member of group "admin" | |
98 | } | |
99 | } | |
100 | } | |
101 | } | |
102 | ||
103 | return FALSE; | |
4c5e92e2 A |
104 | } |
105 | ||
106 | ||
a40a14f8 | 107 | #if !TARGET_OS_IPHONE |
4c5e92e2 A |
108 | static void * |
109 | __loadSecurity(void) { | |
edebe297 | 110 | static void *image = NULL; |
4c5e92e2 | 111 | if (NULL == image) { |
17d3ee29 | 112 | const char *framework = "/System/Library/Frameworks/Security.framework/Security"; |
4c5e92e2 A |
113 | struct stat statbuf; |
114 | const char *suffix = getenv("DYLD_IMAGE_SUFFIX"); | |
115 | char path[MAXPATHLEN]; | |
116 | ||
edebe297 A |
117 | strlcpy(path, framework, sizeof(path)); |
118 | if (suffix) strlcat(path, suffix, sizeof(path)); | |
4c5e92e2 | 119 | if (0 <= stat(path, &statbuf)) { |
edebe297 | 120 | image = dlopen(path, RTLD_LAZY | RTLD_LOCAL); |
4c5e92e2 | 121 | } else { |
edebe297 | 122 | image = dlopen(framework, RTLD_LAZY | RTLD_LOCAL); |
4c5e92e2 A |
123 | } |
124 | } | |
125 | return (void *)image; | |
126 | } | |
127 | ||
128 | ||
129 | static OSStatus | |
130 | _SessionGetInfo(SecuritySessionId session, SecuritySessionId *sessionId, SessionAttributeBits *attributes) | |
131 | { | |
edebe297 A |
132 | #undef SessionGetInfo |
133 | static typeof (SessionGetInfo) *dyfunc = NULL; | |
4c5e92e2 A |
134 | if (!dyfunc) { |
135 | void *image = __loadSecurity(); | |
edebe297 | 136 | if (image) dyfunc = dlsym(image, "SessionGetInfo"); |
4c5e92e2 A |
137 | } |
138 | return dyfunc ? dyfunc(session, sessionId, attributes) : -1; | |
139 | } | |
140 | #define SessionGetInfo _SessionGetInfo | |
a40a14f8 | 141 | #endif /* !TARGET_OS_IPHONE */ |
4c5e92e2 A |
142 | |
143 | static Boolean | |
144 | hasLocalConsoleAccess() | |
145 | { | |
a40a14f8 | 146 | #if !TARGET_OS_IPHONE |
dbf6a266 A |
147 | OSStatus error; |
148 | SecuritySessionId sessionID = 0; | |
149 | SessionAttributeBits attributeBits = 0; | |
150 | ||
151 | error = SessionGetInfo(callerSecuritySession, &sessionID, &attributeBits); | |
152 | if (error != noErr) { | |
153 | /* Security check failed, must not permit access */ | |
154 | return FALSE; | |
155 | } | |
4c5e92e2 | 156 | |
dbf6a266 | 157 | return (attributeBits & (sessionHasGraphicAccess|sessionIsRemote)) == sessionHasGraphicAccess; |
a40a14f8 A |
158 | #else /* !TARGET_OS_IPHONE */ |
159 | return TRUE; | |
160 | #endif /* !TARGET_OS_IPHONE */ | |
4c5e92e2 A |
161 | } |
162 | ||
163 | ||
5958d7c0 A |
164 | int |
165 | main(int argc, char **argv) | |
166 | { | |
167 | const char *command = argv[0]; | |
168 | extern int optind; | |
169 | int opt; | |
170 | CFStringRef current = NULL; | |
171 | int currentMatched = 0; | |
172 | CFStringRef newSet = NULL; /* set key */ | |
173 | CFStringRef newSetUDN = NULL; /* user defined name */ | |
174 | CFStringRef prefix; | |
dbf6a266 | 175 | SCPreferencesRef prefs; |
5958d7c0 A |
176 | CFDictionaryRef sets; |
177 | CFIndex nSets; | |
a5f60add A |
178 | const void **setKeys = NULL; |
179 | const void **setVals = NULL; | |
5958d7c0 A |
180 | CFIndex i; |
181 | ||
182 | /* process any arguments */ | |
183 | ||
17d3ee29 | 184 | while ((opt = getopt_long(argc, argv, "dvn", longopts, NULL)) != -1) { |
5958d7c0 | 185 | switch(opt) { |
17d3ee29 A |
186 | case 'd': |
187 | _sc_debug = TRUE; | |
188 | _sc_log = FALSE; /* enable framework logging */ | |
189 | break; | |
190 | case 'v': | |
191 | _sc_verbose = TRUE; | |
192 | break; | |
193 | case 'n': | |
194 | apply = FALSE; | |
195 | break; | |
196 | case '?': | |
197 | default : | |
198 | usage(command); | |
199 | } | |
5958d7c0 A |
200 | } |
201 | argc -= optind; | |
202 | argv += optind; | |
203 | ||
204 | prefix = CFStringCreateWithFormat(NULL, NULL, CFSTR("/%@/"), kSCPrefSets); | |
205 | ||
17d3ee29 A |
206 | if (argc == 1) { |
207 | newSet = CFStringCreateWithCString(NULL, argv[0], kCFStringEncodingMacRoman); | |
5958d7c0 | 208 | |
17d3ee29 A |
209 | /* check if a full path to the new "set" was specified */ |
210 | if ((CFStringGetLength(newSet) > 0) && CFStringHasPrefix(newSet, prefix)) { | |
211 | CFRange range; | |
212 | CFMutableStringRef str; | |
5958d7c0 | 213 | |
17d3ee29 A |
214 | str = CFStringCreateMutableCopy(NULL, 0, newSet); |
215 | CFRelease(newSet); | |
5958d7c0 | 216 | |
17d3ee29 A |
217 | CFStringDelete(str, CFRangeMake(0, CFStringGetLength(prefix))); |
218 | newSet = CFStringCreateCopy(NULL, newSet); | |
219 | CFRelease(str); | |
5958d7c0 | 220 | |
17d3ee29 A |
221 | range = CFStringFind(newSet, CFSTR("/"), 0); |
222 | if (range.location != kCFNotFound) { | |
223 | SCPrint(TRUE, stderr, CFSTR("Set \"%@\" not available\n."), newSet); | |
224 | exit (1); | |
225 | } | |
5958d7c0 | 226 | } |
17d3ee29 A |
227 | } else { |
228 | newSet = CFRetain(CFSTR("")); | |
229 | } | |
5958d7c0 | 230 | |
17d3ee29 A |
231 | |
232 | prefs = SCPreferencesCreate(NULL, CFSTR("Select Set Command"), NULL); | |
233 | if (prefs == NULL) { | |
234 | SCPrint(TRUE, stderr, CFSTR("SCPreferencesCreate() failed\n")); | |
235 | exit (1); | |
5958d7c0 A |
236 | } |
237 | ||
dbf6a266 A |
238 | sets = SCPreferencesGetValue(prefs, kSCPrefSets); |
239 | if (sets == NULL) { | |
240 | SCPrint(TRUE, stderr, CFSTR("No network sets defined.\n")); | |
5958d7c0 A |
241 | exit (1); |
242 | } | |
243 | ||
dbf6a266 A |
244 | current = SCPreferencesGetValue(prefs, kSCPrefCurrentSet); |
245 | if (current != NULL) { | |
0fae82ee A |
246 | if (CFStringHasPrefix(current, prefix)) { |
247 | CFMutableStringRef tmp; | |
248 | ||
249 | tmp = CFStringCreateMutableCopy(NULL, 0, current); | |
250 | CFStringDelete(tmp, CFRangeMake(0, CFStringGetLength(prefix))); | |
251 | current = tmp; | |
252 | } else { | |
a40a14f8 | 253 | CFRetain(current); |
0fae82ee A |
254 | currentMatched = -1; /* not prefixed */ |
255 | } | |
256 | } else { | |
a40a14f8 | 257 | current = CFRetain(CFSTR("")); |
0fae82ee | 258 | currentMatched = -2; /* not defined */ |
5958d7c0 A |
259 | } |
260 | ||
261 | nSets = CFDictionaryGetCount(sets); | |
a5f60add A |
262 | if (nSets > 0) { |
263 | setKeys = CFAllocatorAllocate(NULL, nSets * sizeof(CFStringRef), 0); | |
264 | setVals = CFAllocatorAllocate(NULL, nSets * sizeof(CFDictionaryRef), 0); | |
265 | CFDictionaryGetKeysAndValues(sets, setKeys, setVals); | |
266 | } | |
5958d7c0 A |
267 | |
268 | /* check for set with matching name */ | |
009ee3c6 | 269 | for (i = 0; i < nSets; i++) { |
5958d7c0 A |
270 | CFStringRef key = (CFStringRef) setKeys[i]; |
271 | CFDictionaryRef dict = (CFDictionaryRef)setVals[i]; | |
272 | ||
273 | if ((currentMatched >= 0) && CFEqual(key, current)) { | |
274 | currentMatched++; | |
275 | } | |
276 | ||
277 | if (CFEqual(newSet, key)) { | |
278 | newSetUDN = CFDictionaryGetValue(dict, kSCPropUserDefinedName); | |
dbf6a266 | 279 | if (newSetUDN != NULL) CFRetain(newSetUDN); |
5958d7c0 A |
280 | goto found; |
281 | } | |
282 | } | |
283 | ||
284 | /* check for set with matching user-defined name */ | |
009ee3c6 | 285 | for (i = 0; i < nSets; i++) { |
5958d7c0 A |
286 | CFStringRef key = (CFStringRef) setKeys[i]; |
287 | CFDictionaryRef dict = (CFDictionaryRef)setVals[i]; | |
288 | ||
289 | newSetUDN = CFDictionaryGetValue(dict, kSCPropUserDefinedName); | |
290 | if ((newSetUDN != NULL) && CFEqual(newSet, newSetUDN)) { | |
291 | CFRelease(newSet); | |
292 | newSet = CFRetain(key); | |
293 | CFRetain(newSetUDN); | |
5958d7c0 A |
294 | goto found; |
295 | } | |
296 | } | |
297 | ||
009ee3c6 | 298 | if (argc == 1) { |
6bb65964 A |
299 | SCPrint(TRUE, stderr, CFSTR("Set \"%@\" not available.\n"), newSet); |
300 | exit(1); | |
5958d7c0 A |
301 | } |
302 | ||
6bb65964 | 303 | SCPrint(TRUE, stdout, |
0fae82ee A |
304 | CFSTR("Defined sets include:%s\n"), |
305 | (currentMatched > 0) ? " (* == current set)" : ""); | |
5958d7c0 | 306 | |
009ee3c6 | 307 | for (i = 0; i < nSets; i++) { |
5958d7c0 A |
308 | CFStringRef key = (CFStringRef) setKeys[i]; |
309 | CFDictionaryRef dict = (CFDictionaryRef)setVals[i]; | |
310 | CFStringRef udn = CFDictionaryGetValue(dict, kSCPropUserDefinedName); | |
311 | ||
6bb65964 | 312 | SCPrint(TRUE, stdout, |
0fae82ee | 313 | CFSTR(" %s %@\t(%@)\n"), |
5958d7c0 A |
314 | ((currentMatched > 0) && CFEqual(key, current)) ? "*" : " ", |
315 | key, | |
316 | udn ? udn : CFSTR("")); | |
317 | } | |
318 | ||
319 | switch (currentMatched) { | |
320 | case -2 : | |
6bb65964 | 321 | SCPrint(TRUE, stdout, CFSTR("\nCurrent set not defined.\n")); |
5958d7c0 A |
322 | break; |
323 | case -1 : | |
6bb65964 | 324 | SCPrint(TRUE, stdout, CFSTR("\nCurrent set \"%@\" may not be valid\n"), current); |
5958d7c0 A |
325 | break; |
326 | case 0 : | |
6bb65964 | 327 | SCPrint(TRUE, stdout, CFSTR("\nCurrent set \"%@\" not valid\n"), current); |
5958d7c0 A |
328 | break; |
329 | default : | |
330 | break; | |
331 | } | |
332 | ||
6bb65964 | 333 | exit (0); |
5958d7c0 A |
334 | |
335 | found : | |
336 | ||
4c5e92e2 | 337 | if (!(isAdmin() || hasLocalConsoleAccess())) { |
dbf6a266 A |
338 | SCPrint(TRUE, stderr, |
339 | CFSTR("Only local console users and administrators can change locations\n")); | |
340 | exit (EX_NOPERM); | |
4c5e92e2 | 341 | } |
dbf6a266 | 342 | |
a40a14f8 A |
343 | CFRelease(current); |
344 | current = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@%@"), prefix, newSet); | |
345 | ||
dbf6a266 | 346 | if (!SCPreferencesSetValue(prefs, kSCPrefCurrentSet, current)) { |
0fae82ee A |
347 | SCPrint(TRUE, stderr, |
348 | CFSTR("SCPreferencesSetValue(...,%@,%@) failed\n"), | |
5958d7c0 | 349 | kSCPrefCurrentSet, |
0fae82ee | 350 | current); |
5958d7c0 A |
351 | exit (1); |
352 | } | |
353 | ||
dbf6a266 | 354 | if (!SCPreferencesCommitChanges(prefs)) { |
0fae82ee | 355 | SCPrint(TRUE, stderr, CFSTR("SCPreferencesCommitChanges() failed\n")); |
5958d7c0 A |
356 | exit (1); |
357 | } | |
358 | ||
359 | if (apply) { | |
dbf6a266 | 360 | if (!SCPreferencesApplyChanges(prefs)) { |
0fae82ee | 361 | SCPrint(TRUE, stderr, CFSTR("SCPreferencesApplyChanges() failed\n")); |
5958d7c0 A |
362 | exit (1); |
363 | } | |
364 | } | |
365 | ||
0fae82ee A |
366 | SCPrint(TRUE, stdout, |
367 | CFSTR("%@ updated to %@ (%@)\n"), | |
5958d7c0 A |
368 | kSCPrefCurrentSet, |
369 | newSet, | |
370 | newSetUDN ? newSetUDN : CFSTR("")); | |
371 | ||
a40a14f8 A |
372 | CFRelease(current); |
373 | CFRelease(newSet); | |
374 | if (newSetUDN != NULL) CFRelease(newSetUDN); | |
375 | CFRelease(prefix); | |
376 | CFRelease(prefs); | |
377 | ||
5958d7c0 A |
378 | exit (0); |
379 | return 0; | |
380 | } |