Wed, 10 Feb 2010 21:25:01 +0100
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 | } |