tunblick/openvpnstart.m

Mon, 16 Jan 2012 23:08:14 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Mon, 16 Jan 2012 23:08:14 +0100
changeset 23
d783b433388d
parent 1
1a5334dfb21d
permissions
-rw-r--r--

Inconclusively complete possibly missing fields. This change introduces
inconsistencies difficult to correct given incomplete documentation of
IPKG and OPKG packaging standards.

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

mercurial