michael@1: /* michael@1: * Copyright (c) 2004-2006 Angelo Laub michael@1: * Contributions by Dirk Theisen michael@1: * michael@1: * This program is free software; you can redistribute it and/or modify michael@1: * it under the terms of the GNU General Public License version 2 michael@1: * as published by the Free Software Foundation. michael@1: * michael@1: * This program is distributed in the hope that it will be useful, michael@1: * but WITHOUT ANY WARRANTY; without even the implied warranty of michael@1: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the michael@1: * GNU General Public License for more details. michael@1: * michael@1: * You should have received a copy of the GNU General Public License michael@1: * along with this program (see the file COPYING included with this michael@1: * distribution); if not, write to the Free Software Foundation, Inc., michael@1: * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA michael@1: */ michael@1: michael@1: #import michael@1: #import michael@1: #import michael@1: #import michael@1: #import michael@1: #import michael@1: #import michael@1: michael@1: 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: void killOneOpenvpn (pid_t pid); //Returns having killed an openvpn process, or complains and exits michael@1: int killAllOpenvpn (void); //Kills all openvpn processes and returns the number of processes that were killed. May complain and exit michael@1: michael@1: void loadKexts (void); //Tries to load kexts -- no indication of failure. May complain and exit if can't become root michael@1: void becomeRoot (void); //Returns as root, having setuid(0) if necessary; complains and exits if can't become root michael@1: michael@1: void getProcesses (struct kinfo_proc** procs, int* number); //Fills in process information michael@1: BOOL isOpenvpn (pid_t pid); //Returns TRUE if process is an openvpn process (i.e., process name = "openvpn") michael@1: BOOL configNeedsRepair (void); //Returns NO if configuration file is secure, otherwise complains and exits michael@1: michael@1: NSString* execPath; //Path to folder containing this executable, openvpn, tap.kext, tun.kext, client.up.osx.sh, and client.down.osx.sh michael@1: NSString* configPath; //Path to configuration file (in ~/Library/openvpn) michael@1: NSAutoreleasePool* pool; michael@1: michael@1: int main(int argc, char* argv[]) michael@1: { michael@1: pool = [[NSAutoreleasePool alloc] init]; michael@1: michael@1: BOOL syntaxError = TRUE; michael@1: michael@1: if (argc > 1) { michael@1: char* command = argv[1]; michael@1: if( strcmp(command, "killall") == 0 ) { michael@1: if (argc == 2) { michael@1: int nKilled; michael@1: nKilled = killAllOpenvpn(); michael@1: if (nKilled) { michael@1: printf("%d openvpn processes killed\n", nKilled); michael@1: } else { michael@1: fprintf(stderr, "%d openvpn processes killed\n", nKilled); michael@1: } michael@1: syntaxError = FALSE; michael@1: } michael@1: } else if( strcmp(command, "kill") == 0 ) { michael@1: if (argc == 3) { michael@1: pid_t pid = (pid_t) atoi(argv[2]); michael@1: killOneOpenvpn(pid); michael@1: syntaxError = FALSE; michael@1: } michael@1: } else if( strcmp(command, "start") == 0 ) { michael@1: if ( (argc > 3) && (argc < 7) ) { michael@1: NSString* configFile = [NSString stringWithUTF8String:argv[2]]; michael@1: execPath = [[NSString stringWithUTF8String:argv[0]] stringByDeletingLastPathComponent]; michael@1: if(strlen(argv[3]) < 6 ) { michael@1: unsigned int port = atoi(argv[3]); michael@1: if (port<=65535) { michael@1: BOOL useScripts = FALSE; if( (argc > 4) && (atoi(argv[4]) == 1) ) useScripts = TRUE; michael@1: BOOL skipCheck = FALSE; if( (argc > 5) && (atoi(argv[5]) == 1) ) skipCheck = TRUE; michael@1: startVPN(configFile, port, useScripts, skipCheck); michael@1: syntaxError = FALSE; michael@1: } michael@1: } michael@1: } michael@1: } michael@1: } michael@1: michael@1: if (syntaxError) { michael@1: fprintf(stderr, "Error: Syntax error. Usage:\n\n" michael@1: michael@1: "\t./openvpnstart killall\n" michael@1: "\t./openvpnstart kill processId\n" michael@1: "\t./openvpnstart start configName mgtPort [useScripts [skipCheck] ]\n\n" michael@1: michael@1: "Where:\n" michael@1: "\tprocessId is the process ID of the openvpn process to kill\n" michael@1: "\tconfigName is the name of the configuration file (which must be in ~/Library/openvpn)\n" michael@1: "\tmgtPort is the port number (0-65535) to use for managing the connection\n" michael@1: "\tuseScripts is 1 to run the client.up.osx.sh script before connecting, and client.down.osx.sh after disconnecting\n" michael@1: "\t (The scripts are in Tunnelblick.app/Contents/Resources/)\n" michael@1: "\tskipCheck is 1 to skip checking ownership and permissions of the configuration file\n\n" michael@1: michael@1: "useScripts and skipCheck each default to 0.\n\n" michael@1: michael@1: "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: michael@1: "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: michael@1: "Tunnelblick must have been run and an administrator password entered at least once before openvpnstart can be used." michael@1: ); michael@1: [pool drain]; michael@1: exit(2); michael@1: } michael@1: michael@1: [pool drain]; michael@1: exit(0); michael@1: } michael@1: michael@1: //Tries to start an openvpn connection -- no indication of failure. May complain and exit if can't become root michael@1: void startVPN(NSString* configFile, int port, BOOL useScripts, BOOL skipCheck) michael@1: { michael@1: NSString* directoryPath = [NSHomeDirectory() stringByAppendingPathComponent:@"/Library/openvpn"]; michael@1: configPath = [directoryPath stringByAppendingPathComponent:configFile]; michael@1: NSString* openvpnPath = [execPath stringByAppendingPathComponent: @"openvpn"]; michael@1: NSMutableString* upscriptPath = [[execPath stringByAppendingPathComponent: @"client.up.osx.sh"] mutableCopy]; michael@1: NSMutableString* downscriptPath = [[execPath stringByAppendingPathComponent: @"client.down.osx.sh"] mutableCopy]; michael@1: [upscriptPath replaceOccurrencesOfString:@" " withString:@"\\ " options:NSLiteralSearch range:NSMakeRange(0, [upscriptPath length])]; michael@1: [downscriptPath replaceOccurrencesOfString:@" " withString:@"\\ " options:NSLiteralSearch range:NSMakeRange(0, [downscriptPath length])]; michael@1: michael@1: if ( ! skipCheck ) { michael@1: if(configNeedsRepair()) { michael@1: [pool drain]; michael@1: exit(2); michael@1: } michael@1: } michael@1: michael@1: // default arguments to openvpn command line michael@1: NSMutableArray* arguments = [NSMutableArray arrayWithObjects: michael@1: @"--management-query-passwords", michael@1: @"--cd", directoryPath, michael@1: @"--daemon", michael@1: @"--management-hold", michael@1: @"--management", @"127.0.0.1", [NSString stringWithFormat:@"%d", port], michael@1: @"--config", configPath, michael@1: nil michael@1: ]; michael@1: michael@1: // conditionally push additional arguments to array michael@1: if(useScripts) { michael@1: [arguments addObjectsFromArray: michael@1: [NSArray arrayWithObjects: michael@1: @"--up", upscriptPath, michael@1: @"--down", downscriptPath, michael@1: @"--up-restart", michael@1: nil michael@1: ] michael@1: ]; michael@1: } michael@1: michael@1: loadKexts(); michael@1: michael@1: NSTask* task = [[[NSTask alloc] init] autorelease]; michael@1: [task setLaunchPath:openvpnPath]; michael@1: [task setArguments:arguments]; michael@1: michael@1: becomeRoot(); michael@1: [task launch]; michael@1: [task waitUntilExit]; michael@1: michael@1: [upscriptPath release]; michael@1: [downscriptPath release]; michael@1: } michael@1: michael@1: //Returns having killed an openvpn process, or complains and exits michael@1: void killOneOpenvpn(pid_t pid) michael@1: { michael@1: int didnotKill; michael@1: michael@1: if(isOpenvpn(pid)) { michael@1: becomeRoot(); michael@1: didnotKill = kill(pid, SIGTERM); michael@1: if (didnotKill) { michael@1: fprintf(stderr, "Error: Unable to kill openvpn process %d\n", pid); michael@1: [pool drain]; michael@1: exit(2); michael@1: } michael@1: } else { michael@1: fprintf(stderr, "Error: Process %d is not an openvpn process\n", pid); michael@1: [pool drain]; michael@1: exit(2); michael@1: } michael@1: } michael@1: michael@1: //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: int killAllOpenvpn(void) michael@1: { michael@1: int count = 0, michael@1: i = 0, michael@1: nKilled = 0, //# of openvpn processes succesfully killed michael@1: nNotKilled = 0, //# of openvpn processes not killed michael@1: didnotKill; //return value from kill() -- zero indicates killed successfully michael@1: michael@1: struct kinfo_proc* info = NULL; michael@1: michael@1: getProcesses(&info, &count); michael@1: michael@1: for (i = 0; i < count; i++) { michael@1: char* process_name = info[i].kp_proc.p_comm; michael@1: pid_t pid = info[i].kp_proc.p_pid; michael@1: if(strcmp(process_name, "openvpn") == 0) { michael@1: becomeRoot(); michael@1: didnotKill = kill(pid, SIGTERM); michael@1: if (didnotKill) { michael@1: fprintf(stderr, "Error: Unable to kill openvpn process %d\n", pid); michael@1: nNotKilled++; michael@1: } else { michael@1: nKilled++; michael@1: } michael@1: } michael@1: } michael@1: michael@1: free(info); michael@1: michael@1: if (nNotKilled) { michael@1: // An error message for each openvpn process that wasn't killed has already been output michael@1: [pool drain]; michael@1: exit(2); michael@1: } michael@1: michael@1: return(nKilled); michael@1: } michael@1: michael@1: //Tries to load kexts -- no indication of failure. May complain and exit if can't become root michael@1: void loadKexts(void) michael@1: { michael@1: NSString* tapPath = [execPath stringByAppendingPathComponent: @"tap.kext"]; michael@1: NSString* tunPath = [execPath stringByAppendingPathComponent: @"tun.kext"]; michael@1: NSTask* task = [[[NSTask alloc] init] autorelease]; michael@1: NSArray* arguments = [NSArray arrayWithObjects:tapPath, tunPath, nil]; michael@1: michael@1: [task setLaunchPath:@"/sbin/kextload"]; michael@1: michael@1: [task setArguments:arguments]; michael@1: michael@1: becomeRoot(); michael@1: [task launch]; michael@1: [task waitUntilExit]; michael@1: } michael@1: michael@1: //Returns as root, having setuid(0) if necessary; complains and exits if can't become root michael@1: void becomeRoot(void) michael@1: { michael@1: if (getuid() != 0) { michael@1: if ( setuid(0) ) { michael@1: fprintf(stderr, "Error: Unable to become root\n" michael@1: "You must have run Tunnelblick and entered an administrator password at least once to use openvpnstart\n"); michael@1: [pool drain]; michael@1: exit(2); michael@1: } michael@1: } michael@1: } michael@1: michael@1: //Fills in process information michael@1: void getProcesses(struct kinfo_proc** procs, int* number) michael@1: { michael@1: int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; michael@1: struct kinfo_proc* info; michael@1: size_t length; michael@1: int level = 3; michael@1: michael@1: if (sysctl(mib, level, NULL, &length, NULL, 0) < 0) return; michael@1: if (!(info = malloc(length))) return; michael@1: if (sysctl(mib, level, info, &length, NULL, 0) < 0) { michael@1: free(info); michael@1: return; michael@1: } michael@1: *procs = info; michael@1: *number = length / sizeof(struct kinfo_proc); michael@1: } michael@1: michael@1: //Returns TRUE if process is an openvpn process (i.e., process name = "openvpn"), otherwise returns FALSE michael@1: BOOL isOpenvpn(pid_t pid) michael@1: { michael@1: BOOL is_openvpn = FALSE; michael@1: int count = 0, michael@1: i = 0; michael@1: struct kinfo_proc* info = NULL; michael@1: michael@1: getProcesses(&info, &count); michael@1: for (i = 0; i < count; i++) { michael@1: char* process_name = info[i].kp_proc.p_comm; michael@1: pid_t thisPid = info[i].kp_proc.p_pid; michael@1: if (pid == thisPid) { michael@1: if (strcmp(process_name, "openvpn")==0) { michael@1: is_openvpn = TRUE; michael@1: } else { michael@1: is_openvpn = FALSE; michael@1: } michael@1: break; michael@1: } michael@1: } michael@1: free(info); michael@1: return is_openvpn; michael@1: } michael@1: michael@1: //Returns NO if configuration file is secure, otherwise complains and exits michael@1: BOOL configNeedsRepair(void) michael@1: { michael@1: NSFileManager* fileManager = [NSFileManager defaultManager]; michael@1: NSDictionary* fileAttributes = [fileManager fileAttributesAtPath:configPath traverseLink:YES]; michael@1: michael@1: if (fileAttributes == nil) { michael@1: fprintf(stderr, "Error: %s does not exist\n", [configPath UTF8String]); michael@1: [pool drain]; michael@1: exit(2); michael@1: } michael@1: michael@1: unsigned long perms = [fileAttributes filePosixPermissions]; michael@1: NSString* octalString = [NSString stringWithFormat:@"%o", perms]; michael@1: NSNumber* fileOwner = [fileAttributes fileOwnerAccountID]; michael@1: michael@1: if ( (![octalString isEqualToString:@"644"]) || (![fileOwner isEqualToNumber:[NSNumber numberWithInt:0]])) { michael@1: NSString* errMsg = [NSString stringWithFormat:@"Error: File %@ is owned by %@ and has permissions %@\n" michael@1: "Configuration files must be owned by root:wheel with permissions 0644\n" michael@1: "To skip this check, use 'skipCheck' -- type './openvpnstart' (with no arguments) for details)\n", michael@1: configPath, fileOwner, octalString]; michael@1: fprintf(stderr, [errMsg UTF8String]); michael@1: [pool drain]; michael@1: exit(2); michael@1: } michael@1: return NO; michael@1: }