]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (c) 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 | #include <xpc/xpc.h> | |
25 | #include <xpc/private.h> | |
26 | #include <notify.h> | |
27 | #include <tzfile.h> | |
28 | ||
29 | #include "tzlink_internal.h" | |
30 | ||
31 | static bool peer_entitled(xpc_connection_t); | |
32 | static int set_timezone(const char *); | |
33 | static int build_source_path(char *, size_t, const char *); | |
34 | static int validate_source_path(const char *); | |
35 | ||
36 | int | |
37 | main(void) | |
38 | { | |
39 | dispatch_queue_t queue; | |
40 | xpc_connection_t listener; | |
41 | ||
42 | xpc_track_activity(); | |
43 | ||
44 | queue = dispatch_queue_create(TZLINK_SERVICE_NAME, NULL); | |
45 | ||
46 | listener = xpc_connection_create_mach_service(TZLINK_SERVICE_NAME, queue, XPC_CONNECTION_MACH_SERVICE_LISTENER); | |
47 | xpc_connection_set_event_handler(listener, ^(xpc_object_t peer) { | |
48 | if (xpc_get_type(peer) != XPC_TYPE_CONNECTION) { | |
49 | return; | |
50 | } | |
51 | ||
52 | xpc_connection_set_target_queue(peer, queue); | |
53 | xpc_connection_set_event_handler(peer, ^(xpc_object_t request) { | |
54 | xpc_object_t reply; | |
55 | const char *tz; | |
56 | int error; | |
57 | ||
58 | if (xpc_get_type(request) != XPC_TYPE_DICTIONARY) { | |
59 | return; | |
60 | } | |
61 | ||
62 | if (peer_entitled(peer)) { | |
63 | tz = xpc_dictionary_get_string(request, TZLINK_KEY_REQUEST_TIMEZONE); | |
64 | error = tz ? set_timezone(tz) : EINVAL; | |
65 | } else { | |
66 | error = EPERM; | |
67 | } | |
68 | ||
69 | reply = xpc_dictionary_create_reply(request); | |
70 | xpc_dictionary_set_uint64(reply, TZLINK_KEY_REPLY_ERROR, (uint64_t)error); | |
71 | xpc_connection_send_message(peer, reply); | |
72 | xpc_release(reply); | |
73 | }); | |
74 | xpc_connection_resume(peer); | |
75 | }); | |
76 | xpc_connection_resume(listener); | |
77 | ||
78 | dispatch_main(); | |
79 | } | |
80 | ||
81 | static bool | |
82 | peer_entitled(xpc_connection_t peer) | |
83 | { | |
84 | xpc_object_t val; | |
85 | bool entitled; | |
86 | ||
87 | entitled = false; | |
88 | ||
89 | val = xpc_connection_copy_entitlement_value(peer, TZLINK_ENTITLEMENT); | |
90 | if (val != NULL) { | |
91 | entitled = xpc_bool_get_value(val); | |
92 | xpc_release(val); | |
93 | } | |
94 | ||
95 | return entitled; | |
96 | } | |
97 | ||
98 | static int | |
99 | set_timezone(const char *tz) | |
100 | { | |
101 | char srcpath[PATH_MAX]; | |
102 | int error; | |
103 | ||
104 | error = build_source_path(srcpath, sizeof(srcpath), tz); | |
105 | if (error != 0) { | |
106 | return error; | |
107 | } | |
108 | ||
109 | error = validate_source_path(srcpath); | |
110 | if (error != 0) { | |
111 | return error; | |
112 | } | |
113 | ||
114 | (void)unlink(TZDEFAULT); | |
115 | if (symlink(srcpath, TZDEFAULT) != 0) { | |
116 | return errno ? errno : EFAULT; | |
117 | } | |
118 | ||
119 | /* | |
120 | * notifyd posts "com.apple.system.timezone" automatically, | |
121 | * but we also need post this. Sigh. | |
122 | */ | |
123 | (void)notify_post("SignificantTimeChangeNotification"); | |
124 | ||
125 | return 0; | |
126 | } | |
127 | ||
128 | /* Create path from input. */ | |
129 | static int | |
130 | build_source_path(char *path, size_t size, const char *tz) | |
131 | { | |
132 | char *str, *str0; | |
133 | char *pathcomp; | |
134 | int error; | |
135 | ||
136 | if (strlcpy(path, TZDIR, size) >= size) { | |
137 | return ENAMETOOLONG; | |
138 | } | |
139 | ||
140 | error = 0; | |
141 | ||
142 | /* Attempt to validate the input; construct a clean path as we go. */ | |
143 | str0 = str = strdup(tz); | |
144 | while ((pathcomp = strsep(&str, "/")) != NULL) { | |
145 | if (pathcomp[0] == '\0' || pathcomp[0] == '.') { | |
146 | error = EINVAL; | |
147 | break; | |
148 | } | |
149 | if (strlcat(path, "/", size) >= size) { | |
150 | error = ENAMETOOLONG; | |
151 | break; | |
152 | } | |
153 | if (strlcat(path, pathcomp, size) >= size) { | |
154 | error = ENAMETOOLONG; | |
155 | break; | |
156 | } | |
157 | } | |
158 | free(str0); | |
159 | ||
160 | return error; | |
161 | } | |
162 | ||
163 | /* Validate path. */ | |
164 | static int | |
165 | validate_source_path(const char *path) | |
166 | { | |
167 | struct stat sb; | |
168 | ||
169 | if (lstat(path, &sb) != 0) { | |
170 | return errno ? errno : EFAULT; | |
171 | } | |
172 | ||
173 | /* Ensure that the time zone file is... */ | |
174 | ||
175 | /* ... a regular file. */ | |
176 | if (!S_ISREG(sb.st_mode)) { | |
177 | return ENOENT; | |
178 | } | |
179 | ||
180 | /* ... owned by root:wheel */ | |
181 | if (sb.st_uid != 0 || sb.st_gid != 0) { | |
182 | return ENOENT; | |
183 | } | |
184 | ||
185 | /* ... 0644 perms */ | |
186 | if ((sb.st_mode & ACCESSPERMS) != 0644) { | |
187 | return ENOENT; | |
188 | } | |
189 | ||
190 | /* ... is a real tzfile (starts with TZif) */ | |
191 | // TODO: Validate contents of file. | |
192 | ||
193 | return 0; | |
194 | } |