tunblick/openvpnstart.m

Wed, 29 Jul 2009 11:23:17 +0200

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 29 Jul 2009 11:23:17 +0200
changeset 1
1a5334dfb21d
child 5
f83da8c347c4
permissions
-rw-r--r--

Import package vendor original sources for necessary manipulations.

     1 /*
     2  * Copyright (c) 2004-2006 Angelo Laub
     3  * Contributions by Dirk Theisen
     4  *
     5  * This program is free software; you can redistribute it and/or modify
     6  * it under the terms of the GNU General Public License version 2
     7  * as published by the Free Software Foundation.
     8  *
     9  * This program is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License
    15  * along with this program (see the file COPYING included with this
    16  * distribution); if not, write to the Free Software Foundation, Inc.,
    17  * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    18  */
    20 #import <Foundation/Foundation.h>
    21 #import <Foundation/NSDebug.h>
    22 #import <sys/types.h>
    23 #import <sys/stat.h>
    24 #import <unistd.h>
    25 #import <sys/sysctl.h>
    26 #import <signal.h>
    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
    29 void	killOneOpenvpn		(pid_t pid);	//Returns having killed an openvpn process, or complains and exits
    30 int		killAllOpenvpn		(void);			//Kills all openvpn processes and returns the number of processes that were killed. May complain and exit
    32 void	loadKexts			(void);			//Tries to load kexts -- no indication of failure. May complain and exit if can't become root
    33 void	becomeRoot			(void);			//Returns as root, having setuid(0) if necessary; complains and exits if can't become root
    35 void	getProcesses		(struct kinfo_proc** procs, int* number);	//Fills in process information
    36 BOOL	isOpenvpn			(pid_t pid);	//Returns TRUE if process is an openvpn process (i.e., process name = "openvpn")
    37 BOOL	configNeedsRepair	(void);			//Returns NO if configuration file is secure, otherwise complains and exits
    39 NSString*					execPath;		//Path to folder containing this executable, openvpn, tap.kext, tun.kext, client.up.osx.sh, and client.down.osx.sh
    40 NSString*					configPath;		//Path to configuration file (in ~/Library/openvpn)
    41 NSAutoreleasePool*			pool;
    43 int main(int argc, char* argv[])
    44 {
    45     pool = [[NSAutoreleasePool alloc] init];
    47 	BOOL	syntaxError	= TRUE;
    49 	if (argc > 1) {
    50 		char* command = argv[1];
    51 		if( strcmp(command, "killall") == 0 ) {
    52 			if (argc == 2) {
    53 				int nKilled;
    54 				nKilled = killAllOpenvpn();
    55 				if (nKilled) {
    56 					printf("%d openvpn processes killed\n", nKilled);
    57 				} else {
    58 					fprintf(stderr, "%d openvpn processes killed\n", nKilled);
    59 				}
    60 				syntaxError = FALSE;
    61 			}
    62 		} else if( strcmp(command, "kill") == 0 ) {
    63 			if (argc == 3) {
    64 				pid_t pid = (pid_t) atoi(argv[2]);
    65 				killOneOpenvpn(pid);
    66 				syntaxError = FALSE;
    67 			}
    68 		} else if( strcmp(command, "start") == 0 ) {
    69 			if (  (argc > 3) && (argc < 7)  ) {
    70 				NSString* configFile = [NSString stringWithUTF8String:argv[2]];
    71 				execPath = [[NSString stringWithUTF8String:argv[0]] stringByDeletingLastPathComponent];
    72 				if(strlen(argv[3]) < 6 ) {
    73 					unsigned int port = atoi(argv[3]);
    74 					if (port<=65535) {
    75 						BOOL useScripts = FALSE; if( (argc > 4) && (atoi(argv[4]) == 1) ) useScripts = TRUE;
    76 						BOOL skipCheck  = FALSE; if( (argc > 5) && (atoi(argv[5]) == 1) ) skipCheck  = TRUE;
    77 						startVPN(configFile, port, useScripts, skipCheck);
    78 						syntaxError = FALSE;
    79 					}
    80 				}
    81 			}
    82 		}
    83 	}
    85 	if (syntaxError) {
    86 		fprintf(stderr, "Error: Syntax error. Usage:\n\n"
    88 				"\t./openvpnstart killall\n"
    89 				"\t./openvpnstart kill   processId\n"
    90 				"\t./openvpnstart start  configName  mgtPort  [useScripts  [skipCheck]  ]\n\n"
    92 				"Where:\n"
    93 				"\tprocessId  is the process ID of the openvpn process to kill\n"
    94 				"\tconfigName is the name of the configuration file (which must be in ~/Library/openvpn)\n"
    95 				"\tmgtPort    is the port number (0-65535) to use for managing the connection\n"
    96 				"\tuseScripts is 1 to run the client.up.osx.sh script before connecting, and client.down.osx.sh after disconnecting\n"
    97 				"\t           (The scripts are in Tunnelblick.app/Contents/Resources/)\n"
    98 				"\tskipCheck  is 1 to skip checking ownership and permissions of the configuration file\n\n"
   100 				"useScripts and skipCheck each default to 0.\n\n"
   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"
   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"
   106 				"Tunnelblick must have been run and an administrator password entered at least once before openvpnstart can be used."
   107 				);
   108 		[pool drain];
   109 		exit(2);
   110 	}
   112 	[pool drain];
   113 	exit(0);
   114 }
   116 //Tries to start an openvpn connection -- no indication of failure. May complain and exit if can't become root
   117 void startVPN(NSString* configFile, int port, BOOL useScripts, BOOL skipCheck)
   118 {
   119 	NSString*			directoryPath	= [NSHomeDirectory() stringByAppendingPathComponent:@"/Library/openvpn"];
   120 						configPath		= [directoryPath stringByAppendingPathComponent:configFile];
   121 	NSString*			openvpnPath		= [execPath stringByAppendingPathComponent: @"openvpn"];
   122 	NSMutableString*	upscriptPath	= [[execPath stringByAppendingPathComponent: @"client.up.osx.sh"] mutableCopy];
   123 	NSMutableString*	downscriptPath	= [[execPath stringByAppendingPathComponent: @"client.down.osx.sh"] mutableCopy];
   124 	[upscriptPath replaceOccurrencesOfString:@" " withString:@"\\ " options:NSLiteralSearch range:NSMakeRange(0, [upscriptPath length])];
   125 	[downscriptPath replaceOccurrencesOfString:@" " withString:@"\\ " options:NSLiteralSearch range:NSMakeRange(0, [downscriptPath length])];
   127 	if ( ! skipCheck ) {
   128 		if(configNeedsRepair()) {
   129 			[pool drain];
   130 			exit(2);
   131 		}
   132 	}
   134 	// default arguments to openvpn command line
   135 	NSMutableArray* arguments = [NSMutableArray arrayWithObjects:
   136 								 @"--management-query-passwords",  
   137 								 @"--cd", directoryPath, 
   138 								 @"--daemon", 
   139 								 @"--management-hold", 
   140 								 @"--management", @"127.0.0.1", [NSString stringWithFormat:@"%d", port],  
   141 								 @"--config", configPath,
   142 								 @"--script-security", @"2", // allow us to call the up and down scripts or scripts defined in config
   143 								 nil
   144 								 ];
   146 	// conditionally push additional arguments to array
   147 	if(useScripts) {
   148 		[arguments addObjectsFromArray:
   149 		 [NSArray arrayWithObjects:
   150 		  @"--up", upscriptPath,
   151 		  @"--down", downscriptPath,
   152           @"--up-restart",
   153 		  nil
   154 		 ]
   155 		];
   156 	}
   158 	loadKexts();
   160 	NSTask* task = [[[NSTask alloc] init] autorelease];
   161 	[task setLaunchPath:openvpnPath];
   162 	[task setArguments:arguments];
   164 	becomeRoot();
   165 	[task launch];
   166 	[task waitUntilExit];
   168 	[upscriptPath release];
   169 	[downscriptPath release];
   170 }
   172 //Returns having killed an openvpn process, or complains and exits
   173 void killOneOpenvpn(pid_t pid)
   174 {
   175 	int didnotKill;
   177 	if(isOpenvpn(pid)) {
   178 		becomeRoot();
   179 		didnotKill = kill(pid, SIGTERM);
   180 		if (didnotKill) {
   181 			fprintf(stderr, "Error: Unable to kill openvpn process %d\n", pid);
   182 			[pool drain];
   183 			exit(2);
   184 		}
   185 	} else {
   186 		fprintf(stderr, "Error: Process %d is not an openvpn process\n", pid);
   187 		[pool drain];
   188 		exit(2);
   189 	}
   190 }
   192 //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
   193 int killAllOpenvpn(void)
   194 {
   195 	int	count		= 0,
   196 		i			= 0,
   197 		nKilled		= 0,		//# of openvpn processes succesfully killed
   198 		nNotKilled	= 0,		//# of openvpn processes not killed
   199 		didnotKill;				//return value from kill() -- zero indicates killed successfully
   201 	struct kinfo_proc*	info	= NULL;
   203 	getProcesses(&info, &count);
   205 	for (i = 0; i < count; i++) {
   206 		char* process_name = info[i].kp_proc.p_comm;
   207 		pid_t pid = info[i].kp_proc.p_pid;
   208 		if(strcmp(process_name, "openvpn") == 0) {
   209 			becomeRoot();
   210 			didnotKill = kill(pid, SIGTERM);
   211 			if (didnotKill) {
   212 				fprintf(stderr, "Error: Unable to kill openvpn process %d\n", pid);
   213 				nNotKilled++;
   214 			} else {
   215 				nKilled++;
   216 			}
   217 		}
   218 	}
   220 	free(info);
   222 	if (nNotKilled) {
   223 		// An error message for each openvpn process that wasn't killed has already been output
   224 		[pool drain];
   225 		exit(2);
   226 	}
   228 	return(nKilled);
   229 }
   231 //Tries to load kexts -- no indication of failure. May complain and exit if can't become root
   232 void loadKexts(void)
   233 {
   234 	NSString*	tapPath		= [execPath stringByAppendingPathComponent: @"tap.kext"];
   235 	NSString*	tunPath		= [execPath stringByAppendingPathComponent: @"tun.kext"];
   236 	NSTask*		task		= [[[NSTask alloc] init] autorelease];
   237 	NSArray*	arguments	= [NSArray arrayWithObjects:tapPath, tunPath, nil];
   239 	[task setLaunchPath:@"/sbin/kextload"];
   241 	[task setArguments:arguments];
   243 	becomeRoot();
   244 	[task launch];
   245 	[task waitUntilExit];
   246 }
   248 //Returns as root, having setuid(0) if necessary; complains and exits if can't become root
   249 void becomeRoot(void)
   250 {
   251 	if (getuid()  != 0) {
   252 		if (  setuid(0)  ) {
   253 			fprintf(stderr, "Error: Unable to become root\n"
   254 							"You must have run Tunnelblick and entered an administrator password at least once to use openvpnstart\n");
   255 			[pool drain];
   256 			exit(2);
   257 		}
   258 	}
   259 }
   261 //Fills in process information
   262 void getProcesses(struct kinfo_proc** procs, int* number)
   263 {
   264 	int					mib[4]	= { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
   265     struct	kinfo_proc* info;
   266 	size_t				length;
   267     int					level	= 3;
   269     if (sysctl(mib, level, NULL, &length, NULL, 0) < 0) return;
   270     if (!(info = malloc(length))) return;
   271     if (sysctl(mib, level, info, &length, NULL, 0) < 0) {
   272         free(info);
   273         return;
   274     }
   275 	*procs = info;
   276 	*number = length / sizeof(struct kinfo_proc);
   277 }
   279 //Returns TRUE if process is an openvpn process (i.e., process name = "openvpn"), otherwise returns FALSE
   280 BOOL isOpenvpn(pid_t pid)
   281 {
   282 	BOOL				is_openvpn	= FALSE;
   283 	int					count		= 0,
   284 	i			= 0;
   285 	struct kinfo_proc*	info		= NULL;
   287 	getProcesses(&info, &count);
   288     for (i = 0; i < count; i++) {
   289         char* process_name = info[i].kp_proc.p_comm;
   290         pid_t thisPid = info[i].kp_proc.p_pid;
   291         if (pid == thisPid) {
   292 			if (strcmp(process_name, "openvpn")==0) {
   293 				is_openvpn = TRUE;
   294 			} else {
   295 				is_openvpn = FALSE;
   296 			}
   297 			break;
   298 		}
   299     }    
   300     free(info);
   301 	return is_openvpn;
   302 }
   304 //Returns NO if configuration file is secure, otherwise complains and exits
   305 BOOL configNeedsRepair(void)
   306 {
   307 	NSFileManager*	fileManager		= [NSFileManager defaultManager];
   308 	NSDictionary*	fileAttributes	= [fileManager fileAttributesAtPath:configPath traverseLink:YES];
   310 	if (fileAttributes == nil) {
   311 		fprintf(stderr, "Error: %s does not exist\n", [configPath UTF8String]);
   312 		[pool drain];
   313 		exit(2);
   314 	}
   316 	unsigned long	perms			= [fileAttributes filePosixPermissions];
   317 	NSString*		octalString		= [NSString stringWithFormat:@"%o", perms];
   318 	NSNumber*		fileOwner		= [fileAttributes fileOwnerAccountID];
   320 	if ( (![octalString isEqualToString:@"644"])  || (![fileOwner isEqualToNumber:[NSNumber numberWithInt:0]])) {
   321 		NSString* errMsg = [NSString stringWithFormat:@"Error: File %@ is owned by %@ and has permissions %@\n"
   322 							"Configuration files must be owned by root:wheel with permissions 0644\n"
   323 							"To skip this check, use 'skipCheck' -- type './openvpnstart' (with no arguments) for details)\n",
   324 							configPath, fileOwner, octalString];
   325 		fprintf(stderr, [errMsg UTF8String]);
   326 		[pool drain];
   327 		exit(2);
   328 	}
   329 	return NO;
   330 }

mercurial