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.
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 | } |