tunblick/openvpnstart.m

Mon, 16 Jan 2012 23:08:14 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Mon, 16 Jan 2012 23:08:14 +0100
changeset 23
d783b433388d
parent 1
1a5334dfb21d
permissions
-rw-r--r--

Inconclusively complete possibly missing fields. This change introduces
inconsistencies difficult to correct given incomplete documentation of
IPKG and OPKG packaging standards.

     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 								 nil
   143 								 ];
   145 	// conditionally push additional arguments to array
   146 	if(useScripts) {
   147 		[arguments addObjectsFromArray:
   148 		 [NSArray arrayWithObjects:
   149 		  @"--up", upscriptPath,
   150 		  @"--down", downscriptPath,
   151           @"--up-restart",
   152 		  nil
   153 		 ]
   154 		];
   155 	}
   157 	loadKexts();
   159 	NSTask* task = [[[NSTask alloc] init] autorelease];
   160 	[task setLaunchPath:openvpnPath];
   161 	[task setArguments:arguments];
   163 	becomeRoot();
   164 	[task launch];
   165 	[task waitUntilExit];
   167 	[upscriptPath release];
   168 	[downscriptPath release];
   169 }
   171 //Returns having killed an openvpn process, or complains and exits
   172 void killOneOpenvpn(pid_t pid)
   173 {
   174 	int didnotKill;
   176 	if(isOpenvpn(pid)) {
   177 		becomeRoot();
   178 		didnotKill = kill(pid, SIGTERM);
   179 		if (didnotKill) {
   180 			fprintf(stderr, "Error: Unable to kill openvpn process %d\n", pid);
   181 			[pool drain];
   182 			exit(2);
   183 		}
   184 	} else {
   185 		fprintf(stderr, "Error: Process %d is not an openvpn process\n", pid);
   186 		[pool drain];
   187 		exit(2);
   188 	}
   189 }
   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
   192 int killAllOpenvpn(void)
   193 {
   194 	int	count		= 0,
   195 		i			= 0,
   196 		nKilled		= 0,		//# of openvpn processes succesfully killed
   197 		nNotKilled	= 0,		//# of openvpn processes not killed
   198 		didnotKill;				//return value from kill() -- zero indicates killed successfully
   200 	struct kinfo_proc*	info	= NULL;
   202 	getProcesses(&info, &count);
   204 	for (i = 0; i < count; i++) {
   205 		char* process_name = info[i].kp_proc.p_comm;
   206 		pid_t pid = info[i].kp_proc.p_pid;
   207 		if(strcmp(process_name, "openvpn") == 0) {
   208 			becomeRoot();
   209 			didnotKill = kill(pid, SIGTERM);
   210 			if (didnotKill) {
   211 				fprintf(stderr, "Error: Unable to kill openvpn process %d\n", pid);
   212 				nNotKilled++;
   213 			} else {
   214 				nKilled++;
   215 			}
   216 		}
   217 	}
   219 	free(info);
   221 	if (nNotKilled) {
   222 		// An error message for each openvpn process that wasn't killed has already been output
   223 		[pool drain];
   224 		exit(2);
   225 	}
   227 	return(nKilled);
   228 }
   230 //Tries to load kexts -- no indication of failure. May complain and exit if can't become root
   231 void loadKexts(void)
   232 {
   233 	NSString*	tapPath		= [execPath stringByAppendingPathComponent: @"tap.kext"];
   234 	NSString*	tunPath		= [execPath stringByAppendingPathComponent: @"tun.kext"];
   235 	NSTask*		task		= [[[NSTask alloc] init] autorelease];
   236 	NSArray*	arguments	= [NSArray arrayWithObjects:tapPath, tunPath, nil];
   238 	[task setLaunchPath:@"/sbin/kextload"];
   240 	[task setArguments:arguments];
   242 	becomeRoot();
   243 	[task launch];
   244 	[task waitUntilExit];
   245 }
   247 //Returns as root, having setuid(0) if necessary; complains and exits if can't become root
   248 void becomeRoot(void)
   249 {
   250 	if (getuid()  != 0) {
   251 		if (  setuid(0)  ) {
   252 			fprintf(stderr, "Error: Unable to become root\n"
   253 							"You must have run Tunnelblick and entered an administrator password at least once to use openvpnstart\n");
   254 			[pool drain];
   255 			exit(2);
   256 		}
   257 	}
   258 }
   260 //Fills in process information
   261 void getProcesses(struct kinfo_proc** procs, int* number)
   262 {
   263 	int					mib[4]	= { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
   264     struct	kinfo_proc* info;
   265 	size_t				length;
   266     int					level	= 3;
   268     if (sysctl(mib, level, NULL, &length, NULL, 0) < 0) return;
   269     if (!(info = malloc(length))) return;
   270     if (sysctl(mib, level, info, &length, NULL, 0) < 0) {
   271         free(info);
   272         return;
   273     }
   274 	*procs = info;
   275 	*number = length / sizeof(struct kinfo_proc);
   276 }
   278 //Returns TRUE if process is an openvpn process (i.e., process name = "openvpn"), otherwise returns FALSE
   279 BOOL isOpenvpn(pid_t pid)
   280 {
   281 	BOOL				is_openvpn	= FALSE;
   282 	int					count		= 0,
   283 	i			= 0;
   284 	struct kinfo_proc*	info		= NULL;
   286 	getProcesses(&info, &count);
   287     for (i = 0; i < count; i++) {
   288         char* process_name = info[i].kp_proc.p_comm;
   289         pid_t thisPid = info[i].kp_proc.p_pid;
   290         if (pid == thisPid) {
   291 			if (strcmp(process_name, "openvpn")==0) {
   292 				is_openvpn = TRUE;
   293 			} else {
   294 				is_openvpn = FALSE;
   295 			}
   296 			break;
   297 		}
   298     }    
   299     free(info);
   300 	return is_openvpn;
   301 }
   303 //Returns NO if configuration file is secure, otherwise complains and exits
   304 BOOL configNeedsRepair(void)
   305 {
   306 	NSFileManager*	fileManager		= [NSFileManager defaultManager];
   307 	NSDictionary*	fileAttributes	= [fileManager fileAttributesAtPath:configPath traverseLink:YES];
   309 	if (fileAttributes == nil) {
   310 		fprintf(stderr, "Error: %s does not exist\n", [configPath UTF8String]);
   311 		[pool drain];
   312 		exit(2);
   313 	}
   315 	unsigned long	perms			= [fileAttributes filePosixPermissions];
   316 	NSString*		octalString		= [NSString stringWithFormat:@"%o", perms];
   317 	NSNumber*		fileOwner		= [fileAttributes fileOwnerAccountID];
   319 	if ( (![octalString isEqualToString:@"644"])  || (![fileOwner isEqualToNumber:[NSNumber numberWithInt:0]])) {
   320 		NSString* errMsg = [NSString stringWithFormat:@"Error: File %@ is owned by %@ and has permissions %@\n"
   321 							"Configuration files must be owned by root:wheel with permissions 0644\n"
   322 							"To skip this check, use 'skipCheck' -- type './openvpnstart' (with no arguments) for details)\n",
   323 							configPath, fileOwner, octalString];
   324 		fprintf(stderr, [errMsg UTF8String]);
   325 		[pool drain];
   326 		exit(2);
   327 	}
   328 	return NO;
   329 }

mercurial