1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/tunblick/openvpnstart.m Wed Jul 29 11:23:17 2009 +0200 1.3 @@ -0,0 +1,330 @@ 1.4 +/* 1.5 + * Copyright (c) 2004-2006 Angelo Laub 1.6 + * Contributions by Dirk Theisen 1.7 + * 1.8 + * This program is free software; you can redistribute it and/or modify 1.9 + * it under the terms of the GNU General Public License version 2 1.10 + * as published by the Free Software Foundation. 1.11 + * 1.12 + * This program is distributed in the hope that it will be useful, 1.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 1.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1.15 + * GNU General Public License for more details. 1.16 + * 1.17 + * You should have received a copy of the GNU General Public License 1.18 + * along with this program (see the file COPYING included with this 1.19 + * distribution); if not, write to the Free Software Foundation, Inc., 1.20 + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 1.21 + */ 1.22 + 1.23 +#import <Foundation/Foundation.h> 1.24 +#import <Foundation/NSDebug.h> 1.25 +#import <sys/types.h> 1.26 +#import <sys/stat.h> 1.27 +#import <unistd.h> 1.28 +#import <sys/sysctl.h> 1.29 +#import <signal.h> 1.30 + 1.31 +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 1.32 +void killOneOpenvpn (pid_t pid); //Returns having killed an openvpn process, or complains and exits 1.33 +int killAllOpenvpn (void); //Kills all openvpn processes and returns the number of processes that were killed. May complain and exit 1.34 + 1.35 +void loadKexts (void); //Tries to load kexts -- no indication of failure. May complain and exit if can't become root 1.36 +void becomeRoot (void); //Returns as root, having setuid(0) if necessary; complains and exits if can't become root 1.37 + 1.38 +void getProcesses (struct kinfo_proc** procs, int* number); //Fills in process information 1.39 +BOOL isOpenvpn (pid_t pid); //Returns TRUE if process is an openvpn process (i.e., process name = "openvpn") 1.40 +BOOL configNeedsRepair (void); //Returns NO if configuration file is secure, otherwise complains and exits 1.41 + 1.42 +NSString* execPath; //Path to folder containing this executable, openvpn, tap.kext, tun.kext, client.up.osx.sh, and client.down.osx.sh 1.43 +NSString* configPath; //Path to configuration file (in ~/Library/openvpn) 1.44 +NSAutoreleasePool* pool; 1.45 + 1.46 +int main(int argc, char* argv[]) 1.47 +{ 1.48 + pool = [[NSAutoreleasePool alloc] init]; 1.49 + 1.50 + BOOL syntaxError = TRUE; 1.51 + 1.52 + if (argc > 1) { 1.53 + char* command = argv[1]; 1.54 + if( strcmp(command, "killall") == 0 ) { 1.55 + if (argc == 2) { 1.56 + int nKilled; 1.57 + nKilled = killAllOpenvpn(); 1.58 + if (nKilled) { 1.59 + printf("%d openvpn processes killed\n", nKilled); 1.60 + } else { 1.61 + fprintf(stderr, "%d openvpn processes killed\n", nKilled); 1.62 + } 1.63 + syntaxError = FALSE; 1.64 + } 1.65 + } else if( strcmp(command, "kill") == 0 ) { 1.66 + if (argc == 3) { 1.67 + pid_t pid = (pid_t) atoi(argv[2]); 1.68 + killOneOpenvpn(pid); 1.69 + syntaxError = FALSE; 1.70 + } 1.71 + } else if( strcmp(command, "start") == 0 ) { 1.72 + if ( (argc > 3) && (argc < 7) ) { 1.73 + NSString* configFile = [NSString stringWithUTF8String:argv[2]]; 1.74 + execPath = [[NSString stringWithUTF8String:argv[0]] stringByDeletingLastPathComponent]; 1.75 + if(strlen(argv[3]) < 6 ) { 1.76 + unsigned int port = atoi(argv[3]); 1.77 + if (port<=65535) { 1.78 + BOOL useScripts = FALSE; if( (argc > 4) && (atoi(argv[4]) == 1) ) useScripts = TRUE; 1.79 + BOOL skipCheck = FALSE; if( (argc > 5) && (atoi(argv[5]) == 1) ) skipCheck = TRUE; 1.80 + startVPN(configFile, port, useScripts, skipCheck); 1.81 + syntaxError = FALSE; 1.82 + } 1.83 + } 1.84 + } 1.85 + } 1.86 + } 1.87 + 1.88 + if (syntaxError) { 1.89 + fprintf(stderr, "Error: Syntax error. Usage:\n\n" 1.90 + 1.91 + "\t./openvpnstart killall\n" 1.92 + "\t./openvpnstart kill processId\n" 1.93 + "\t./openvpnstart start configName mgtPort [useScripts [skipCheck] ]\n\n" 1.94 + 1.95 + "Where:\n" 1.96 + "\tprocessId is the process ID of the openvpn process to kill\n" 1.97 + "\tconfigName is the name of the configuration file (which must be in ~/Library/openvpn)\n" 1.98 + "\tmgtPort is the port number (0-65535) to use for managing the connection\n" 1.99 + "\tuseScripts is 1 to run the client.up.osx.sh script before connecting, and client.down.osx.sh after disconnecting\n" 1.100 + "\t (The scripts are in Tunnelblick.app/Contents/Resources/)\n" 1.101 + "\tskipCheck is 1 to skip checking ownership and permissions of the configuration file\n\n" 1.102 + 1.103 + "useScripts and skipCheck each default to 0.\n\n" 1.104 + 1.105 + "The normal return code is 0. If an error occurs a message is sent to stderr and a code of 2 is returned.\n" 1.106 + 1.107 + "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" 1.108 + 1.109 + "Tunnelblick must have been run and an administrator password entered at least once before openvpnstart can be used." 1.110 + ); 1.111 + [pool drain]; 1.112 + exit(2); 1.113 + } 1.114 + 1.115 + [pool drain]; 1.116 + exit(0); 1.117 +} 1.118 + 1.119 +//Tries to start an openvpn connection -- no indication of failure. May complain and exit if can't become root 1.120 +void startVPN(NSString* configFile, int port, BOOL useScripts, BOOL skipCheck) 1.121 +{ 1.122 + NSString* directoryPath = [NSHomeDirectory() stringByAppendingPathComponent:@"/Library/openvpn"]; 1.123 + configPath = [directoryPath stringByAppendingPathComponent:configFile]; 1.124 + NSString* openvpnPath = [execPath stringByAppendingPathComponent: @"openvpn"]; 1.125 + NSMutableString* upscriptPath = [[execPath stringByAppendingPathComponent: @"client.up.osx.sh"] mutableCopy]; 1.126 + NSMutableString* downscriptPath = [[execPath stringByAppendingPathComponent: @"client.down.osx.sh"] mutableCopy]; 1.127 + [upscriptPath replaceOccurrencesOfString:@" " withString:@"\\ " options:NSLiteralSearch range:NSMakeRange(0, [upscriptPath length])]; 1.128 + [downscriptPath replaceOccurrencesOfString:@" " withString:@"\\ " options:NSLiteralSearch range:NSMakeRange(0, [downscriptPath length])]; 1.129 + 1.130 + if ( ! skipCheck ) { 1.131 + if(configNeedsRepair()) { 1.132 + [pool drain]; 1.133 + exit(2); 1.134 + } 1.135 + } 1.136 + 1.137 + // default arguments to openvpn command line 1.138 + NSMutableArray* arguments = [NSMutableArray arrayWithObjects: 1.139 + @"--management-query-passwords", 1.140 + @"--cd", directoryPath, 1.141 + @"--daemon", 1.142 + @"--management-hold", 1.143 + @"--management", @"127.0.0.1", [NSString stringWithFormat:@"%d", port], 1.144 + @"--config", configPath, 1.145 + @"--script-security", @"2", // allow us to call the up and down scripts or scripts defined in config 1.146 + nil 1.147 + ]; 1.148 + 1.149 + // conditionally push additional arguments to array 1.150 + if(useScripts) { 1.151 + [arguments addObjectsFromArray: 1.152 + [NSArray arrayWithObjects: 1.153 + @"--up", upscriptPath, 1.154 + @"--down", downscriptPath, 1.155 + @"--up-restart", 1.156 + nil 1.157 + ] 1.158 + ]; 1.159 + } 1.160 + 1.161 + loadKexts(); 1.162 + 1.163 + NSTask* task = [[[NSTask alloc] init] autorelease]; 1.164 + [task setLaunchPath:openvpnPath]; 1.165 + [task setArguments:arguments]; 1.166 + 1.167 + becomeRoot(); 1.168 + [task launch]; 1.169 + [task waitUntilExit]; 1.170 + 1.171 + [upscriptPath release]; 1.172 + [downscriptPath release]; 1.173 +} 1.174 + 1.175 +//Returns having killed an openvpn process, or complains and exits 1.176 +void killOneOpenvpn(pid_t pid) 1.177 +{ 1.178 + int didnotKill; 1.179 + 1.180 + if(isOpenvpn(pid)) { 1.181 + becomeRoot(); 1.182 + didnotKill = kill(pid, SIGTERM); 1.183 + if (didnotKill) { 1.184 + fprintf(stderr, "Error: Unable to kill openvpn process %d\n", pid); 1.185 + [pool drain]; 1.186 + exit(2); 1.187 + } 1.188 + } else { 1.189 + fprintf(stderr, "Error: Process %d is not an openvpn process\n", pid); 1.190 + [pool drain]; 1.191 + exit(2); 1.192 + } 1.193 +} 1.194 + 1.195 +//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 1.196 +int killAllOpenvpn(void) 1.197 +{ 1.198 + int count = 0, 1.199 + i = 0, 1.200 + nKilled = 0, //# of openvpn processes succesfully killed 1.201 + nNotKilled = 0, //# of openvpn processes not killed 1.202 + didnotKill; //return value from kill() -- zero indicates killed successfully 1.203 + 1.204 + struct kinfo_proc* info = NULL; 1.205 + 1.206 + getProcesses(&info, &count); 1.207 + 1.208 + for (i = 0; i < count; i++) { 1.209 + char* process_name = info[i].kp_proc.p_comm; 1.210 + pid_t pid = info[i].kp_proc.p_pid; 1.211 + if(strcmp(process_name, "openvpn") == 0) { 1.212 + becomeRoot(); 1.213 + didnotKill = kill(pid, SIGTERM); 1.214 + if (didnotKill) { 1.215 + fprintf(stderr, "Error: Unable to kill openvpn process %d\n", pid); 1.216 + nNotKilled++; 1.217 + } else { 1.218 + nKilled++; 1.219 + } 1.220 + } 1.221 + } 1.222 + 1.223 + free(info); 1.224 + 1.225 + if (nNotKilled) { 1.226 + // An error message for each openvpn process that wasn't killed has already been output 1.227 + [pool drain]; 1.228 + exit(2); 1.229 + } 1.230 + 1.231 + return(nKilled); 1.232 +} 1.233 + 1.234 +//Tries to load kexts -- no indication of failure. May complain and exit if can't become root 1.235 +void loadKexts(void) 1.236 +{ 1.237 + NSString* tapPath = [execPath stringByAppendingPathComponent: @"tap.kext"]; 1.238 + NSString* tunPath = [execPath stringByAppendingPathComponent: @"tun.kext"]; 1.239 + NSTask* task = [[[NSTask alloc] init] autorelease]; 1.240 + NSArray* arguments = [NSArray arrayWithObjects:tapPath, tunPath, nil]; 1.241 + 1.242 + [task setLaunchPath:@"/sbin/kextload"]; 1.243 + 1.244 + [task setArguments:arguments]; 1.245 + 1.246 + becomeRoot(); 1.247 + [task launch]; 1.248 + [task waitUntilExit]; 1.249 +} 1.250 + 1.251 +//Returns as root, having setuid(0) if necessary; complains and exits if can't become root 1.252 +void becomeRoot(void) 1.253 +{ 1.254 + if (getuid() != 0) { 1.255 + if ( setuid(0) ) { 1.256 + fprintf(stderr, "Error: Unable to become root\n" 1.257 + "You must have run Tunnelblick and entered an administrator password at least once to use openvpnstart\n"); 1.258 + [pool drain]; 1.259 + exit(2); 1.260 + } 1.261 + } 1.262 +} 1.263 + 1.264 +//Fills in process information 1.265 +void getProcesses(struct kinfo_proc** procs, int* number) 1.266 +{ 1.267 + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; 1.268 + struct kinfo_proc* info; 1.269 + size_t length; 1.270 + int level = 3; 1.271 + 1.272 + if (sysctl(mib, level, NULL, &length, NULL, 0) < 0) return; 1.273 + if (!(info = malloc(length))) return; 1.274 + if (sysctl(mib, level, info, &length, NULL, 0) < 0) { 1.275 + free(info); 1.276 + return; 1.277 + } 1.278 + *procs = info; 1.279 + *number = length / sizeof(struct kinfo_proc); 1.280 +} 1.281 + 1.282 +//Returns TRUE if process is an openvpn process (i.e., process name = "openvpn"), otherwise returns FALSE 1.283 +BOOL isOpenvpn(pid_t pid) 1.284 +{ 1.285 + BOOL is_openvpn = FALSE; 1.286 + int count = 0, 1.287 + i = 0; 1.288 + struct kinfo_proc* info = NULL; 1.289 + 1.290 + getProcesses(&info, &count); 1.291 + for (i = 0; i < count; i++) { 1.292 + char* process_name = info[i].kp_proc.p_comm; 1.293 + pid_t thisPid = info[i].kp_proc.p_pid; 1.294 + if (pid == thisPid) { 1.295 + if (strcmp(process_name, "openvpn")==0) { 1.296 + is_openvpn = TRUE; 1.297 + } else { 1.298 + is_openvpn = FALSE; 1.299 + } 1.300 + break; 1.301 + } 1.302 + } 1.303 + free(info); 1.304 + return is_openvpn; 1.305 +} 1.306 + 1.307 +//Returns NO if configuration file is secure, otherwise complains and exits 1.308 +BOOL configNeedsRepair(void) 1.309 +{ 1.310 + NSFileManager* fileManager = [NSFileManager defaultManager]; 1.311 + NSDictionary* fileAttributes = [fileManager fileAttributesAtPath:configPath traverseLink:YES]; 1.312 + 1.313 + if (fileAttributes == nil) { 1.314 + fprintf(stderr, "Error: %s does not exist\n", [configPath UTF8String]); 1.315 + [pool drain]; 1.316 + exit(2); 1.317 + } 1.318 + 1.319 + unsigned long perms = [fileAttributes filePosixPermissions]; 1.320 + NSString* octalString = [NSString stringWithFormat:@"%o", perms]; 1.321 + NSNumber* fileOwner = [fileAttributes fileOwnerAccountID]; 1.322 + 1.323 + if ( (![octalString isEqualToString:@"644"]) || (![fileOwner isEqualToNumber:[NSNumber numberWithInt:0]])) { 1.324 + NSString* errMsg = [NSString stringWithFormat:@"Error: File %@ is owned by %@ and has permissions %@\n" 1.325 + "Configuration files must be owned by root:wheel with permissions 0644\n" 1.326 + "To skip this check, use 'skipCheck' -- type './openvpnstart' (with no arguments) for details)\n", 1.327 + configPath, fileOwner, octalString]; 1.328 + fprintf(stderr, [errMsg UTF8String]); 1.329 + [pool drain]; 1.330 + exit(2); 1.331 + } 1.332 + return NO; 1.333 +}