tunblick/openvpnstart.m

changeset 4
f137c6757b85
child 5
f83da8c347c4
equal deleted inserted replaced
-1:000000000000 0:0f10f2510448
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 }

mercurial