Mon, 16 Jan 2012 23:08:14 +0100
Inconclusively complete possibly missing fields. This change introduces
inconsistencies difficult to correct given incomplete documentation of
IPKG and OPKG packaging standards.
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 */
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>
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
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
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
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;
43 int main(int argc, char* argv[])
44 {
45 pool = [[NSAutoreleasePool alloc] init];
47 BOOL syntaxError = TRUE;
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 }
85 if (syntaxError) {
86 fprintf(stderr, "Error: Syntax error. Usage:\n\n"
88 "\t./openvpnstart killall\n"
89 "\t./openvpnstart kill processId\n"
90 "\t./openvpnstart start configName mgtPort [useScripts [skipCheck] ]\n\n"
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"
100 "useScripts and skipCheck each default to 0.\n\n"
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"
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"
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 }
112 [pool drain];
113 exit(0);
114 }
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])];
127 if ( ! skipCheck ) {
128 if(configNeedsRepair()) {
129 [pool drain];
130 exit(2);
131 }
132 }
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 nil
143 ];
145 // conditionally push additional arguments to array
146 if(useScripts) {
147 [arguments addObjectsFromArray:
148 [NSArray arrayWithObjects:
149 @"--up", upscriptPath,
150 @"--down", downscriptPath,
151 @"--up-restart",
152 nil
153 ]
154 ];
155 }
157 loadKexts();
159 NSTask* task = [[[NSTask alloc] init] autorelease];
160 [task setLaunchPath:openvpnPath];
161 [task setArguments:arguments];
163 becomeRoot();
164 [task launch];
165 [task waitUntilExit];
167 [upscriptPath release];
168 [downscriptPath release];
169 }
171 //Returns having killed an openvpn process, or complains and exits
172 void killOneOpenvpn(pid_t pid)
173 {
174 int didnotKill;
176 if(isOpenvpn(pid)) {
177 becomeRoot();
178 didnotKill = kill(pid, SIGTERM);
179 if (didnotKill) {
180 fprintf(stderr, "Error: Unable to kill openvpn process %d\n", pid);
181 [pool drain];
182 exit(2);
183 }
184 } else {
185 fprintf(stderr, "Error: Process %d is not an openvpn process\n", pid);
186 [pool drain];
187 exit(2);
188 }
189 }
191 //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
192 int killAllOpenvpn(void)
193 {
194 int count = 0,
195 i = 0,
196 nKilled = 0, //# of openvpn processes succesfully killed
197 nNotKilled = 0, //# of openvpn processes not killed
198 didnotKill; //return value from kill() -- zero indicates killed successfully
200 struct kinfo_proc* info = NULL;
202 getProcesses(&info, &count);
204 for (i = 0; i < count; i++) {
205 char* process_name = info[i].kp_proc.p_comm;
206 pid_t pid = info[i].kp_proc.p_pid;
207 if(strcmp(process_name, "openvpn") == 0) {
208 becomeRoot();
209 didnotKill = kill(pid, SIGTERM);
210 if (didnotKill) {
211 fprintf(stderr, "Error: Unable to kill openvpn process %d\n", pid);
212 nNotKilled++;
213 } else {
214 nKilled++;
215 }
216 }
217 }
219 free(info);
221 if (nNotKilled) {
222 // An error message for each openvpn process that wasn't killed has already been output
223 [pool drain];
224 exit(2);
225 }
227 return(nKilled);
228 }
230 //Tries to load kexts -- no indication of failure. May complain and exit if can't become root
231 void loadKexts(void)
232 {
233 NSString* tapPath = [execPath stringByAppendingPathComponent: @"tap.kext"];
234 NSString* tunPath = [execPath stringByAppendingPathComponent: @"tun.kext"];
235 NSTask* task = [[[NSTask alloc] init] autorelease];
236 NSArray* arguments = [NSArray arrayWithObjects:tapPath, tunPath, nil];
238 [task setLaunchPath:@"/sbin/kextload"];
240 [task setArguments:arguments];
242 becomeRoot();
243 [task launch];
244 [task waitUntilExit];
245 }
247 //Returns as root, having setuid(0) if necessary; complains and exits if can't become root
248 void becomeRoot(void)
249 {
250 if (getuid() != 0) {
251 if ( setuid(0) ) {
252 fprintf(stderr, "Error: Unable to become root\n"
253 "You must have run Tunnelblick and entered an administrator password at least once to use openvpnstart\n");
254 [pool drain];
255 exit(2);
256 }
257 }
258 }
260 //Fills in process information
261 void getProcesses(struct kinfo_proc** procs, int* number)
262 {
263 int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
264 struct kinfo_proc* info;
265 size_t length;
266 int level = 3;
268 if (sysctl(mib, level, NULL, &length, NULL, 0) < 0) return;
269 if (!(info = malloc(length))) return;
270 if (sysctl(mib, level, info, &length, NULL, 0) < 0) {
271 free(info);
272 return;
273 }
274 *procs = info;
275 *number = length / sizeof(struct kinfo_proc);
276 }
278 //Returns TRUE if process is an openvpn process (i.e., process name = "openvpn"), otherwise returns FALSE
279 BOOL isOpenvpn(pid_t pid)
280 {
281 BOOL is_openvpn = FALSE;
282 int count = 0,
283 i = 0;
284 struct kinfo_proc* info = NULL;
286 getProcesses(&info, &count);
287 for (i = 0; i < count; i++) {
288 char* process_name = info[i].kp_proc.p_comm;
289 pid_t thisPid = info[i].kp_proc.p_pid;
290 if (pid == thisPid) {
291 if (strcmp(process_name, "openvpn")==0) {
292 is_openvpn = TRUE;
293 } else {
294 is_openvpn = FALSE;
295 }
296 break;
297 }
298 }
299 free(info);
300 return is_openvpn;
301 }
303 //Returns NO if configuration file is secure, otherwise complains and exits
304 BOOL configNeedsRepair(void)
305 {
306 NSFileManager* fileManager = [NSFileManager defaultManager];
307 NSDictionary* fileAttributes = [fileManager fileAttributesAtPath:configPath traverseLink:YES];
309 if (fileAttributes == nil) {
310 fprintf(stderr, "Error: %s does not exist\n", [configPath UTF8String]);
311 [pool drain];
312 exit(2);
313 }
315 unsigned long perms = [fileAttributes filePosixPermissions];
316 NSString* octalString = [NSString stringWithFormat:@"%o", perms];
317 NSNumber* fileOwner = [fileAttributes fileOwnerAccountID];
319 if ( (![octalString isEqualToString:@"644"]) || (![fileOwner isEqualToNumber:[NSNumber numberWithInt:0]])) {
320 NSString* errMsg = [NSString stringWithFormat:@"Error: File %@ is owned by %@ and has permissions %@\n"
321 "Configuration files must be owned by root:wheel with permissions 0644\n"
322 "To skip this check, use 'skipCheck' -- type './openvpnstart' (with no arguments) for details)\n",
323 configPath, fileOwner, octalString];
324 fprintf(stderr, [errMsg UTF8String]);
325 [pool drain];
326 exit(2);
327 }
328 return NO;
329 }