tunblick/openvpnstart.m

Wed, 10 Feb 2010 21:25:01 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 10 Feb 2010 21:25:01 +0100
changeset 18
8ec65b8f6e2c
parent 1
1a5334dfb21d
permissions
-rw-r--r--

Extend uac_auth() of the UAC module to workaround CSEQ problems.
This logic is meant to complement that of changeset 17, which
added rich authentication credentials to the gw table and its
associated logic in the LCR module.

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