| |
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 } |