1/* 2 * OpenVPN -- An application to securely tunnel IP networks 3 * over a single TCP/UDP port, with support for SSL/TLS-based 4 * session authentication and key exchange, 5 * packet encryption, packet authentication, and 6 * packet compression. 7 * 8 * Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net> 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License version 2 12 * as published by the Free Software Foundation. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License 20 * along with this program (see the file COPYING included with this 21 * distribution); if not, write to the Free Software Foundation, Inc., 22 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23 */ 24 25/* 26 * OpenVPN plugin module to do privileged down-script execution. 27 */ 28 29#ifdef HAVE_CONFIG_H 30#include <config.h> 31#endif 32 33#include <stdio.h> 34#include <string.h> 35#include <unistd.h> 36#include <stdlib.h> 37#include <sys/types.h> 38#include <sys/socket.h> 39#include <sys/wait.h> 40#include <fcntl.h> 41#include <signal.h> 42#include <syslog.h> 43 44#include <openvpn-plugin.h> 45 46#define DEBUG(verb) ((verb) >= 7) 47 48/* Command codes for foreground -> background communication */ 49#define COMMAND_RUN_SCRIPT 0 50#define COMMAND_EXIT 1 51 52/* Response codes for background -> foreground communication */ 53#define RESPONSE_INIT_SUCCEEDED 10 54#define RESPONSE_INIT_FAILED 11 55#define RESPONSE_SCRIPT_SUCCEEDED 12 56#define RESPONSE_SCRIPT_FAILED 13 57 58/* Background process function */ 59static void down_root_server (const int fd, char *command, const char *argv[], const char *envp[], const int verb); 60 61/* 62 * Plugin state, used by foreground 63 */ 64struct down_root_context 65{ 66 /* Foreground's socket to background process */ 67 int foreground_fd; 68 69 /* Process ID of background process */ 70 pid_t background_pid; 71 72 /* Verbosity level of OpenVPN */ 73 int verb; 74 75 /* down command */ 76 char *command; 77}; 78 79/* 80 * Given an environmental variable name, search 81 * the envp array for its value, returning it 82 * if found or NULL otherwise. 83 */ 84static const char * 85get_env (const char *name, const char *envp[]) 86{ 87 if (envp) 88 { 89 int i; 90 const int namelen = strlen (name); 91 for (i = 0; envp[i]; ++i) 92 { 93 if (!strncmp (envp[i], name, namelen)) 94 { 95 const char *cp = envp[i] + namelen; 96 if (*cp == '=') 97 return cp + 1; 98 } 99 } 100 } 101 return NULL; 102} 103 104/* 105 * Return the length of a string array 106 */ 107static int 108string_array_len (const char *array[]) 109{ 110 int i = 0; 111 if (array) 112 { 113 while (array[i]) 114 ++i; 115 } 116 return i; 117} 118 119/* 120 * Socket read/write functions. 121 */ 122 123static int 124recv_control (int fd) 125{ 126 unsigned char c; 127 const ssize_t size = read (fd, &c, sizeof (c)); 128 if (size == sizeof (c)) 129 return c; 130 else 131 return -1; 132} 133 134static int 135send_control (int fd, int code) 136{ 137 unsigned char c = (unsigned char) code; 138 const ssize_t size = write (fd, &c, sizeof (c)); 139 if (size == sizeof (c)) 140 return (int) size; 141 else 142 return -1; 143} 144 145/* 146 * Daemonize if "daemon" env var is true. 147 * Preserve stderr across daemonization if 148 * "daemon_log_redirect" env var is true. 149 */ 150static void 151daemonize (const char *envp[]) 152{ 153 const char *daemon_string = get_env ("daemon", envp); 154 if (daemon_string && daemon_string[0] == '1') 155 { 156 const char *log_redirect = get_env ("daemon_log_redirect", envp); 157 int fd = -1; 158 if (log_redirect && log_redirect[0] == '1') 159 fd = dup (2); 160 if (daemon (0, 0) < 0) 161 { 162 fprintf (stderr, "DOWN-ROOT: daemonization failed\n"); 163 } 164 else if (fd >= 3) 165 { 166 dup2 (fd, 2); 167 close (fd); 168 } 169 } 170} 171 172/* 173 * Close most of parent's fds. 174 * Keep stdin/stdout/stderr, plus one 175 * other fd which is presumed to be 176 * our pipe back to parent. 177 * Admittedly, a bit of a kludge, 178 * but posix doesn't give us a kind 179 * of FD_CLOEXEC which will stop 180 * fds from crossing a fork(). 181 */ 182static void 183close_fds_except (int keep) 184{ 185 int i; 186 closelog (); 187 for (i = 3; i <= 100; ++i) 188 { 189 if (i != keep) 190 close (i); 191 } 192} 193 194/* 195 * Usually we ignore signals, because our parent will 196 * deal with them. 197 */ 198static void 199set_signals (void) 200{ 201 signal (SIGTERM, SIG_DFL); 202 203 signal (SIGINT, SIG_IGN); 204 signal (SIGHUP, SIG_IGN); 205 signal (SIGUSR1, SIG_IGN); 206 signal (SIGUSR2, SIG_IGN); 207 signal (SIGPIPE, SIG_IGN); 208} 209 210/* 211 * convert system() return into a success/failure value 212 */ 213int 214system_ok (int stat) 215{ 216#ifdef WIN32 217 return stat == 0; 218#else 219 return stat != -1 && WIFEXITED (stat) && WEXITSTATUS (stat) == 0; 220#endif 221} 222 223static char * 224build_command_line (const char *argv[]) 225{ 226 int size = 0; 227 int n = 0; 228 int i; 229 char *string; 230 231 /* precompute size */ 232 if (argv) 233 { 234 for (i = 0; argv[i]; ++i) 235 { 236 size += (strlen (argv[i]) + 1); /* string length plus trailing space */ 237 ++n; 238 } 239 } 240 ++size; /* for null terminator */ 241 242 /* allocate memory */ 243 string = (char *) malloc (size); 244 if (!string) 245 { 246 fprintf (stderr, "DOWN-ROOT: out of memory\n"); 247 exit (1); 248 } 249 string[0] = '\0'; 250 251 /* build string */ 252 for (i = 0; i < n; ++i) 253 { 254 strcat (string, argv[i]); 255 if (i + 1 < n) 256 strcat (string, " "); 257 } 258 return string; 259} 260 261static void 262free_context (struct down_root_context *context) 263{ 264 if (context) 265 { 266 if (context->command) 267 free (context->command); 268 free (context); 269 } 270} 271 272OPENVPN_EXPORT openvpn_plugin_handle_t 273openvpn_plugin_open_v1 (unsigned int *type_mask, const char *argv[], const char *envp[]) 274{ 275 struct down_root_context *context; 276 277 /* 278 * Allocate our context 279 */ 280 context = (struct down_root_context *) calloc (1, sizeof (struct down_root_context)); 281 if (!context) 282 goto error; 283 context->foreground_fd = -1; 284 285 /* 286 * Intercept the --up and --down callbacks 287 */ 288 *type_mask = OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_UP) | OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_DOWN); 289 290 /* 291 * Make sure we have two string arguments: the first is the .so name, 292 * the second is the script command. 293 */ 294 if (string_array_len (argv) < 2) 295 { 296 fprintf (stderr, "DOWN-ROOT: need down script command\n"); 297 goto error; 298 } 299 300 /* 301 * Save our argument in context 302 */ 303 context->command = build_command_line (&argv[1]); 304 305 /* 306 * Get verbosity level from environment 307 */ 308 { 309 const char *verb_string = get_env ("verb", envp); 310 if (verb_string) 311 context->verb = atoi (verb_string); 312 } 313 314 return (openvpn_plugin_handle_t) context; 315 316 error: 317 free_context (context); 318 return NULL; 319} 320 321OPENVPN_EXPORT int 322openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[]) 323{ 324 struct down_root_context *context = (struct down_root_context *) handle; 325 326 if (type == OPENVPN_PLUGIN_UP && context->foreground_fd == -1) /* fork off a process to hold onto root */ 327 { 328 pid_t pid; 329 int fd[2]; 330 331 /* 332 * Make a socket for foreground and background processes 333 * to communicate. 334 */ 335 if (socketpair (PF_UNIX, SOCK_DGRAM, 0, fd) == -1) 336 { 337 fprintf (stderr, "DOWN-ROOT: socketpair call failed\n"); 338 return OPENVPN_PLUGIN_FUNC_ERROR; 339 } 340 341 /* 342 * Fork off the privileged process. It will remain privileged 343 * even after the foreground process drops its privileges. 344 */ 345 pid = fork (); 346 347 if (pid) 348 { 349 int status; 350 351 /* 352 * Foreground Process 353 */ 354 355 context->background_pid = pid; 356 357 /* close our copy of child's socket */ 358 close (fd[1]); 359 360 /* don't let future subprocesses inherit child socket */ 361 if (fcntl (fd[0], F_SETFD, FD_CLOEXEC) < 0) 362 fprintf (stderr, "DOWN-ROOT: Set FD_CLOEXEC flag on socket file descriptor failed\n"); 363 364 /* wait for background child process to initialize */ 365 status = recv_control (fd[0]); 366 if (status == RESPONSE_INIT_SUCCEEDED) 367 { 368 context->foreground_fd = fd[0]; 369 return OPENVPN_PLUGIN_FUNC_SUCCESS; 370 } 371 } 372 else 373 { 374 /* 375 * Background Process 376 */ 377 378 /* close all parent fds except our socket back to parent */ 379 close_fds_except (fd[1]); 380 381 /* Ignore most signals (the parent will receive them) */ 382 set_signals (); 383 384 /* Daemonize if --daemon option is set. */ 385 daemonize (envp); 386 387 /* execute the event loop */ 388 down_root_server (fd[1], context->command, argv, envp, context->verb); 389 390 close (fd[1]); 391 exit (0); 392 return 0; /* NOTREACHED */ 393 } 394 } 395 else if (type == OPENVPN_PLUGIN_DOWN && context->foreground_fd >= 0) 396 { 397 if (send_control (context->foreground_fd, COMMAND_RUN_SCRIPT) == -1) 398 { 399 fprintf (stderr, "DOWN-ROOT: Error sending script execution signal to background process\n"); 400 } 401 else 402 { 403 const int status = recv_control (context->foreground_fd); 404 if (status == RESPONSE_SCRIPT_SUCCEEDED) 405 return OPENVPN_PLUGIN_FUNC_SUCCESS; 406 if (status == -1) 407 fprintf (stderr, "DOWN-ROOT: Error receiving script execution confirmation from background process\n"); 408 } 409 } 410 return OPENVPN_PLUGIN_FUNC_ERROR; 411} 412 413OPENVPN_EXPORT void 414openvpn_plugin_close_v1 (openvpn_plugin_handle_t handle) 415{ 416 struct down_root_context *context = (struct down_root_context *) handle; 417 418 if (DEBUG (context->verb)) 419 fprintf (stderr, "DOWN-ROOT: close\n"); 420 421 if (context->foreground_fd >= 0) 422 { 423 /* tell background process to exit */ 424 if (send_control (context->foreground_fd, COMMAND_EXIT) == -1) 425 fprintf (stderr, "DOWN-ROOT: Error signaling background process to exit\n"); 426 427 /* wait for background process to exit */ 428 if (context->background_pid > 0) 429 waitpid (context->background_pid, NULL, 0); 430 431 close (context->foreground_fd); 432 context->foreground_fd = -1; 433 } 434 435 free_context (context); 436} 437 438OPENVPN_EXPORT void 439openvpn_plugin_abort_v1 (openvpn_plugin_handle_t handle) 440{ 441 struct down_root_context *context = (struct down_root_context *) handle; 442 443 if (context && context->foreground_fd >= 0) 444 { 445 /* tell background process to exit */ 446 send_control (context->foreground_fd, COMMAND_EXIT); 447 close (context->foreground_fd); 448 context->foreground_fd = -1; 449 } 450} 451 452/* 453 * Background process -- runs with privilege. 454 */ 455static void 456down_root_server (const int fd, char *command, const char *argv[], const char *envp[], const int verb) 457{ 458 const char *p[3]; 459 char *command_line = NULL; 460 char *argv_cat = NULL; 461 int i; 462 463 /* 464 * Do initialization 465 */ 466 if (DEBUG (verb)) 467 fprintf (stderr, "DOWN-ROOT: BACKGROUND: INIT command='%s'\n", command); 468 469 /* 470 * Tell foreground that we initialized successfully 471 */ 472 if (send_control (fd, RESPONSE_INIT_SUCCEEDED) == -1) 473 { 474 fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [1]\n"); 475 goto done; 476 } 477 478 /* 479 * Build command line 480 */ 481 if (string_array_len (argv) >= 2) 482 argv_cat = build_command_line (&argv[1]); 483 else 484 argv_cat = build_command_line (NULL); 485 p[0] = command; 486 p[1] = argv_cat; 487 p[2] = NULL; 488 command_line = build_command_line (p); 489 490 /* 491 * Save envp in environment 492 */ 493 for (i = 0; envp[i]; ++i) 494 { 495 putenv ((char *)envp[i]); 496 } 497 498 /* 499 * Event loop 500 */ 501 while (1) 502 { 503 int command_code; 504 int status; 505 506 /* get a command from foreground process */ 507 command_code = recv_control (fd); 508 509 if (DEBUG (verb)) 510 fprintf (stderr, "DOWN-ROOT: BACKGROUND: received command code: %d\n", command_code); 511 512 switch (command_code) 513 { 514 case COMMAND_RUN_SCRIPT: 515 status = system (command_line); 516 if (system_ok (status)) /* Succeeded */ 517 { 518 if (send_control (fd, RESPONSE_SCRIPT_SUCCEEDED) == -1) 519 { 520 fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [2]\n"); 521 goto done; 522 } 523 } 524 else /* Failed */ 525 { 526 if (send_control (fd, RESPONSE_SCRIPT_FAILED) == -1) 527 { 528 fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [3]\n"); 529 goto done; 530 } 531 } 532 break; 533 534 case COMMAND_EXIT: 535 goto done; 536 537 case -1: 538 fprintf (stderr, "DOWN-ROOT: BACKGROUND: read error on command channel\n"); 539 goto done; 540 541 default: 542 fprintf (stderr, "DOWN-ROOT: BACKGROUND: unknown command code: code=%d, exiting\n", 543 command_code); 544 goto done; 545 } 546 } 547 548 done: 549 if (argv_cat) 550 free (argv_cat); 551 if (command_line) 552 free (command_line); 553 if (DEBUG (verb)) 554 fprintf (stderr, "DOWN-ROOT: BACKGROUND: EXIT\n"); 555 556 return; 557} 558