|
1 /* |
|
2 * Copyright (c) 2004-2006 Angelo Laub |
|
3 * Contributions by Dirk Theisen |
|
4 * |
|
5 * This program is free software; you can redistribute it and/or modify |
|
6 * it under the terms of the GNU General Public License version 2 |
|
7 * as published by the Free Software Foundation. |
|
8 * |
|
9 * This program is distributed in the hope that it will be useful, |
|
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 * GNU General Public License for more details. |
|
13 * |
|
14 * You should have received a copy of the GNU General Public License |
|
15 * along with this program (see the file COPYING included with this |
|
16 * distribution); if not, write to the Free Software Foundation, Inc., |
|
17 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
18 */ |
|
19 |
|
20 #import <Foundation/Foundation.h> |
|
21 #import <Foundation/NSDebug.h> |
|
22 #import <sys/types.h> |
|
23 #import <sys/stat.h> |
|
24 #import <unistd.h> |
|
25 #import <sys/sysctl.h> |
|
26 #import <signal.h> |
|
27 |
|
28 void startVPN (NSString* configFile, int port, BOOL useScripts, BOOL skipCheck); //Tries to start an openvpn connection. May complain and exit if can't become root |
|
29 void killOneOpenvpn (pid_t pid); //Returns having killed an openvpn process, or complains and exits |
|
30 int killAllOpenvpn (void); //Kills all openvpn processes and returns the number of processes that were killed. May complain and exit |
|
31 |
|
32 void loadKexts (void); //Tries to load kexts -- no indication of failure. May complain and exit if can't become root |
|
33 void becomeRoot (void); //Returns as root, having setuid(0) if necessary; complains and exits if can't become root |
|
34 |
|
35 void getProcesses (struct kinfo_proc** procs, int* number); //Fills in process information |
|
36 BOOL isOpenvpn (pid_t pid); //Returns TRUE if process is an openvpn process (i.e., process name = "openvpn") |
|
37 BOOL configNeedsRepair (void); //Returns NO if configuration file is secure, otherwise complains and exits |
|
38 |
|
39 NSString* execPath; //Path to folder containing this executable, openvpn, tap.kext, tun.kext, client.up.osx.sh, and client.down.osx.sh |
|
40 NSString* configPath; //Path to configuration file (in ~/Library/openvpn) |
|
41 NSAutoreleasePool* pool; |
|
42 |
|
43 int main(int argc, char* argv[]) |
|
44 { |
|
45 pool = [[NSAutoreleasePool alloc] init]; |
|
46 |
|
47 BOOL syntaxError = TRUE; |
|
48 |
|
49 if (argc > 1) { |
|
50 char* command = argv[1]; |
|
51 if( strcmp(command, "killall") == 0 ) { |
|
52 if (argc == 2) { |
|
53 int nKilled; |
|
54 nKilled = killAllOpenvpn(); |
|
55 if (nKilled) { |
|
56 printf("%d openvpn processes killed\n", nKilled); |
|
57 } else { |
|
58 fprintf(stderr, "%d openvpn processes killed\n", nKilled); |
|
59 } |
|
60 syntaxError = FALSE; |
|
61 } |
|
62 } else if( strcmp(command, "kill") == 0 ) { |
|
63 if (argc == 3) { |
|
64 pid_t pid = (pid_t) atoi(argv[2]); |
|
65 killOneOpenvpn(pid); |
|
66 syntaxError = FALSE; |
|
67 } |
|
68 } else if( strcmp(command, "start") == 0 ) { |
|
69 if ( (argc > 3) && (argc < 7) ) { |
|
70 NSString* configFile = [NSString stringWithUTF8String:argv[2]]; |
|
71 execPath = [[NSString stringWithUTF8String:argv[0]] stringByDeletingLastPathComponent]; |
|
72 if(strlen(argv[3]) < 6 ) { |
|
73 unsigned int port = atoi(argv[3]); |
|
74 if (port<=65535) { |
|
75 BOOL useScripts = FALSE; if( (argc > 4) && (atoi(argv[4]) == 1) ) useScripts = TRUE; |
|
76 BOOL skipCheck = FALSE; if( (argc > 5) && (atoi(argv[5]) == 1) ) skipCheck = TRUE; |
|
77 startVPN(configFile, port, useScripts, skipCheck); |
|
78 syntaxError = FALSE; |
|
79 } |
|
80 } |
|
81 } |
|
82 } |
|
83 } |
|
84 |
|
85 if (syntaxError) { |
|
86 fprintf(stderr, "Error: Syntax error. Usage:\n\n" |
|
87 |
|
88 "\t./openvpnstart killall\n" |
|
89 "\t./openvpnstart kill processId\n" |
|
90 "\t./openvpnstart start configName mgtPort [useScripts [skipCheck] ]\n\n" |
|
91 |
|
92 "Where:\n" |
|
93 "\tprocessId is the process ID of the openvpn process to kill\n" |
|
94 "\tconfigName is the name of the configuration file (which must be in ~/Library/openvpn)\n" |
|
95 "\tmgtPort is the port number (0-65535) to use for managing the connection\n" |
|
96 "\tuseScripts is 1 to run the client.up.osx.sh script before connecting, and client.down.osx.sh after disconnecting\n" |
|
97 "\t (The scripts are in Tunnelblick.app/Contents/Resources/)\n" |
|
98 "\tskipCheck is 1 to skip checking ownership and permissions of the configuration file\n\n" |
|
99 |
|
100 "useScripts and skipCheck each default to 0.\n\n" |
|
101 |
|
102 "The normal return code is 0. If an error occurs a message is sent to stderr and a code of 2 is returned.\n" |
|
103 |
|
104 "This executable must be in the same folder as openvpn, tap.kext, and tun.kext (and client.up.osx.sh and client.down.osx.sh if they are used).\n\n" |
|
105 |
|
106 "Tunnelblick must have been run and an administrator password entered at least once before openvpnstart can be used." |
|
107 ); |
|
108 [pool drain]; |
|
109 exit(2); |
|
110 } |
|
111 |
|
112 [pool drain]; |
|
113 exit(0); |
|
114 } |
|
115 |
|
116 //Tries to start an openvpn connection -- no indication of failure. May complain and exit if can't become root |
|
117 void startVPN(NSString* configFile, int port, BOOL useScripts, BOOL skipCheck) |
|
118 { |
|
119 NSString* directoryPath = [NSHomeDirectory() stringByAppendingPathComponent:@"/Library/openvpn"]; |
|
120 configPath = [directoryPath stringByAppendingPathComponent:configFile]; |
|
121 NSString* openvpnPath = [execPath stringByAppendingPathComponent: @"openvpn"]; |
|
122 NSMutableString* upscriptPath = [[execPath stringByAppendingPathComponent: @"client.up.osx.sh"] mutableCopy]; |
|
123 NSMutableString* downscriptPath = [[execPath stringByAppendingPathComponent: @"client.down.osx.sh"] mutableCopy]; |
|
124 [upscriptPath replaceOccurrencesOfString:@" " withString:@"\\ " options:NSLiteralSearch range:NSMakeRange(0, [upscriptPath length])]; |
|
125 [downscriptPath replaceOccurrencesOfString:@" " withString:@"\\ " options:NSLiteralSearch range:NSMakeRange(0, [downscriptPath length])]; |
|
126 |
|
127 if ( ! skipCheck ) { |
|
128 if(configNeedsRepair()) { |
|
129 [pool drain]; |
|
130 exit(2); |
|
131 } |
|
132 } |
|
133 |
|
134 // default arguments to openvpn command line |
|
135 NSMutableArray* arguments = [NSMutableArray arrayWithObjects: |
|
136 @"--management-query-passwords", |
|
137 @"--cd", directoryPath, |
|
138 @"--daemon", |
|
139 @"--management-hold", |
|
140 @"--management", @"127.0.0.1", [NSString stringWithFormat:@"%d", port], |
|
141 @"--config", configPath, |
|
142 @"--script-security", @"2", // allow us to call the up and down scripts or scripts defined in config |
|
143 nil |
|
144 ]; |
|
145 |
|
146 // conditionally push additional arguments to array |
|
147 if(useScripts) { |
|
148 [arguments addObjectsFromArray: |
|
149 [NSArray arrayWithObjects: |
|
150 @"--up", upscriptPath, |
|
151 @"--down", downscriptPath, |
|
152 @"--up-restart", |
|
153 nil |
|
154 ] |
|
155 ]; |
|
156 } |
|
157 |
|
158 loadKexts(); |
|
159 |
|
160 NSTask* task = [[[NSTask alloc] init] autorelease]; |
|
161 [task setLaunchPath:openvpnPath]; |
|
162 [task setArguments:arguments]; |
|
163 |
|
164 becomeRoot(); |
|
165 [task launch]; |
|
166 [task waitUntilExit]; |
|
167 |
|
168 [upscriptPath release]; |
|
169 [downscriptPath release]; |
|
170 } |
|
171 |
|
172 //Returns having killed an openvpn process, or complains and exits |
|
173 void killOneOpenvpn(pid_t pid) |
|
174 { |
|
175 int didnotKill; |
|
176 |
|
177 if(isOpenvpn(pid)) { |
|
178 becomeRoot(); |
|
179 didnotKill = kill(pid, SIGTERM); |
|
180 if (didnotKill) { |
|
181 fprintf(stderr, "Error: Unable to kill openvpn process %d\n", pid); |
|
182 [pool drain]; |
|
183 exit(2); |
|
184 } |
|
185 } else { |
|
186 fprintf(stderr, "Error: Process %d is not an openvpn process\n", pid); |
|
187 [pool drain]; |
|
188 exit(2); |
|
189 } |
|
190 } |
|
191 |
|
192 //Kills all openvpn processes and returns the number of processes that were killed. May complain and exit if can't become root or some openvpn processes can't be killed |
|
193 int killAllOpenvpn(void) |
|
194 { |
|
195 int count = 0, |
|
196 i = 0, |
|
197 nKilled = 0, //# of openvpn processes succesfully killed |
|
198 nNotKilled = 0, //# of openvpn processes not killed |
|
199 didnotKill; //return value from kill() -- zero indicates killed successfully |
|
200 |
|
201 struct kinfo_proc* info = NULL; |
|
202 |
|
203 getProcesses(&info, &count); |
|
204 |
|
205 for (i = 0; i < count; i++) { |
|
206 char* process_name = info[i].kp_proc.p_comm; |
|
207 pid_t pid = info[i].kp_proc.p_pid; |
|
208 if(strcmp(process_name, "openvpn") == 0) { |
|
209 becomeRoot(); |
|
210 didnotKill = kill(pid, SIGTERM); |
|
211 if (didnotKill) { |
|
212 fprintf(stderr, "Error: Unable to kill openvpn process %d\n", pid); |
|
213 nNotKilled++; |
|
214 } else { |
|
215 nKilled++; |
|
216 } |
|
217 } |
|
218 } |
|
219 |
|
220 free(info); |
|
221 |
|
222 if (nNotKilled) { |
|
223 // An error message for each openvpn process that wasn't killed has already been output |
|
224 [pool drain]; |
|
225 exit(2); |
|
226 } |
|
227 |
|
228 return(nKilled); |
|
229 } |
|
230 |
|
231 //Tries to load kexts -- no indication of failure. May complain and exit if can't become root |
|
232 void loadKexts(void) |
|
233 { |
|
234 NSString* tapPath = [execPath stringByAppendingPathComponent: @"tap.kext"]; |
|
235 NSString* tunPath = [execPath stringByAppendingPathComponent: @"tun.kext"]; |
|
236 NSTask* task = [[[NSTask alloc] init] autorelease]; |
|
237 NSArray* arguments = [NSArray arrayWithObjects:tapPath, tunPath, nil]; |
|
238 |
|
239 [task setLaunchPath:@"/sbin/kextload"]; |
|
240 |
|
241 [task setArguments:arguments]; |
|
242 |
|
243 becomeRoot(); |
|
244 [task launch]; |
|
245 [task waitUntilExit]; |
|
246 } |
|
247 |
|
248 //Returns as root, having setuid(0) if necessary; complains and exits if can't become root |
|
249 void becomeRoot(void) |
|
250 { |
|
251 if (getuid() != 0) { |
|
252 if ( setuid(0) ) { |
|
253 fprintf(stderr, "Error: Unable to become root\n" |
|
254 "You must have run Tunnelblick and entered an administrator password at least once to use openvpnstart\n"); |
|
255 [pool drain]; |
|
256 exit(2); |
|
257 } |
|
258 } |
|
259 } |
|
260 |
|
261 //Fills in process information |
|
262 void getProcesses(struct kinfo_proc** procs, int* number) |
|
263 { |
|
264 int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; |
|
265 struct kinfo_proc* info; |
|
266 size_t length; |
|
267 int level = 3; |
|
268 |
|
269 if (sysctl(mib, level, NULL, &length, NULL, 0) < 0) return; |
|
270 if (!(info = malloc(length))) return; |
|
271 if (sysctl(mib, level, info, &length, NULL, 0) < 0) { |
|
272 free(info); |
|
273 return; |
|
274 } |
|
275 *procs = info; |
|
276 *number = length / sizeof(struct kinfo_proc); |
|
277 } |
|
278 |
|
279 //Returns TRUE if process is an openvpn process (i.e., process name = "openvpn"), otherwise returns FALSE |
|
280 BOOL isOpenvpn(pid_t pid) |
|
281 { |
|
282 BOOL is_openvpn = FALSE; |
|
283 int count = 0, |
|
284 i = 0; |
|
285 struct kinfo_proc* info = NULL; |
|
286 |
|
287 getProcesses(&info, &count); |
|
288 for (i = 0; i < count; i++) { |
|
289 char* process_name = info[i].kp_proc.p_comm; |
|
290 pid_t thisPid = info[i].kp_proc.p_pid; |
|
291 if (pid == thisPid) { |
|
292 if (strcmp(process_name, "openvpn")==0) { |
|
293 is_openvpn = TRUE; |
|
294 } else { |
|
295 is_openvpn = FALSE; |
|
296 } |
|
297 break; |
|
298 } |
|
299 } |
|
300 free(info); |
|
301 return is_openvpn; |
|
302 } |
|
303 |
|
304 //Returns NO if configuration file is secure, otherwise complains and exits |
|
305 BOOL configNeedsRepair(void) |
|
306 { |
|
307 NSFileManager* fileManager = [NSFileManager defaultManager]; |
|
308 NSDictionary* fileAttributes = [fileManager fileAttributesAtPath:configPath traverseLink:YES]; |
|
309 |
|
310 if (fileAttributes == nil) { |
|
311 fprintf(stderr, "Error: %s does not exist\n", [configPath UTF8String]); |
|
312 [pool drain]; |
|
313 exit(2); |
|
314 } |
|
315 |
|
316 unsigned long perms = [fileAttributes filePosixPermissions]; |
|
317 NSString* octalString = [NSString stringWithFormat:@"%o", perms]; |
|
318 NSNumber* fileOwner = [fileAttributes fileOwnerAccountID]; |
|
319 |
|
320 if ( (![octalString isEqualToString:@"644"]) || (![fileOwner isEqualToNumber:[NSNumber numberWithInt:0]])) { |
|
321 NSString* errMsg = [NSString stringWithFormat:@"Error: File %@ is owned by %@ and has permissions %@\n" |
|
322 "Configuration files must be owned by root:wheel with permissions 0644\n" |
|
323 "To skip this check, use 'skipCheck' -- type './openvpnstart' (with no arguments) for details)\n", |
|
324 configPath, fileOwner, octalString]; |
|
325 fprintf(stderr, [errMsg UTF8String]); |
|
326 [pool drain]; |
|
327 exit(2); |
|
328 } |
|
329 return NO; |
|
330 } |