svn cleanup
[anytun.git] / openvpn / plugin / down-root / down-root.c
blobf19d8578992be22cede716e8bce8490bf1d95cbf
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.
8 * Copyright (C) 2002-2005 OpenVPN Solutions LLC <info@openvpn.net>
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.
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.
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
26 * OpenVPN plugin module to do privileged down-script execution.
29 #include <stdio.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <stdlib.h>
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <sys/wait.h>
36 #include <fcntl.h>
37 #include <signal.h>
38 #include <syslog.h>
40 #include "openvpn-plugin.h"
42 #define DEBUG(verb) ((verb) >= 7)
44 /* Command codes for foreground -> background communication */
45 #define COMMAND_RUN_SCRIPT 0
46 #define COMMAND_EXIT 1
48 /* Response codes for background -> foreground communication */
49 #define RESPONSE_INIT_SUCCEEDED 10
50 #define RESPONSE_INIT_FAILED 11
51 #define RESPONSE_SCRIPT_SUCCEEDED 12
52 #define RESPONSE_SCRIPT_FAILED 13
54 /* Background process function */
55 static void down_root_server (const int fd, char *command, const char *argv[], const char *envp[], const int verb);
58 * Plugin state, used by foreground
60 struct down_root_context
62 /* Foreground's socket to background process */
63 int foreground_fd;
65 /* Process ID of background process */
66 pid_t background_pid;
68 /* Verbosity level of OpenVPN */
69 int verb;
71 /* down command */
72 char *command;
76 * Given an environmental variable name, search
77 * the envp array for its value, returning it
78 * if found or NULL otherwise.
80 static const char *
81 get_env (const char *name, const char *envp[])
83 if (envp)
85 int i;
86 const int namelen = strlen (name);
87 for (i = 0; envp[i]; ++i)
89 if (!strncmp (envp[i], name, namelen))
91 const char *cp = envp[i] + namelen;
92 if (*cp == '=')
93 return cp + 1;
97 return NULL;
101 * Return the length of a string array
103 static int
104 string_array_len (const char *array[])
106 int i = 0;
107 if (array)
109 while (array[i])
110 ++i;
112 return i;
116 * Socket read/write functions.
119 static int
120 recv_control (int fd)
122 unsigned char c;
123 const ssize_t size = read (fd, &c, sizeof (c));
124 if (size == sizeof (c))
125 return c;
126 else
127 return -1;
130 static int
131 send_control (int fd, int code)
133 unsigned char c = (unsigned char) code;
134 const ssize_t size = write (fd, &c, sizeof (c));
135 if (size == sizeof (c))
136 return (int) size;
137 else
138 return -1;
142 * Daemonize if "daemon" env var is true.
143 * Preserve stderr across daemonization if
144 * "daemon_log_redirect" env var is true.
146 static void
147 daemonize (const char *envp[])
149 const char *daemon_string = get_env ("daemon", envp);
150 if (daemon_string && daemon_string[0] == '1')
152 const char *log_redirect = get_env ("daemon_log_redirect", envp);
153 int fd = -1;
154 if (log_redirect && log_redirect[0] == '1')
155 fd = dup (2);
156 if (daemon (0, 0) < 0)
158 fprintf (stderr, "DOWN-ROOT: daemonization failed\n");
160 else if (fd >= 3)
162 dup2 (fd, 2);
163 close (fd);
169 * Close most of parent's fds.
170 * Keep stdin/stdout/stderr, plus one
171 * other fd which is presumed to be
172 * our pipe back to parent.
173 * Admittedly, a bit of a kludge,
174 * but posix doesn't give us a kind
175 * of FD_CLOEXEC which will stop
176 * fds from crossing a fork().
178 static void
179 close_fds_except (int keep)
181 int i;
182 closelog ();
183 for (i = 3; i <= 100; ++i)
185 if (i != keep)
186 close (i);
191 * Usually we ignore signals, because our parent will
192 * deal with them.
194 static void
195 set_signals (void)
197 signal (SIGTERM, SIG_DFL);
199 signal (SIGINT, SIG_IGN);
200 signal (SIGHUP, SIG_IGN);
201 signal (SIGUSR1, SIG_IGN);
202 signal (SIGUSR2, SIG_IGN);
203 signal (SIGPIPE, SIG_IGN);
207 * convert system() return into a success/failure value
210 system_ok (int stat)
212 #ifdef WIN32
213 return stat == 0;
214 #else
215 return stat != -1 && WIFEXITED (stat) && WEXITSTATUS (stat) == 0;
216 #endif
219 static char *
220 build_command_line (const char *argv[])
222 int size = 0;
223 int n = 0;
224 int i;
225 char *string;
227 /* precompute size */
228 if (argv)
230 for (i = 0; argv[i]; ++i)
232 size += (strlen (argv[i]) + 1); /* string length plus trailing space */
233 ++n;
236 ++size; /* for null terminator */
238 /* allocate memory */
239 string = (char *) malloc (size);
240 if (!string)
242 fprintf (stderr, "DOWN-ROOT: out of memory\n");
243 exit (1);
245 string[0] = '\0';
247 /* build string */
248 for (i = 0; i < n; ++i)
250 strcat (string, argv[i]);
251 if (i + 1 < n)
252 strcat (string, " ");
254 return string;
257 static void
258 free_context (struct down_root_context *context)
260 if (context)
262 if (context->command)
263 free (context->command);
264 free (context);
268 OPENVPN_EXPORT openvpn_plugin_handle_t
269 openvpn_plugin_open_v1 (unsigned int *type_mask, const char *argv[], const char *envp[])
271 struct down_root_context *context;
274 * Allocate our context
276 context = (struct down_root_context *) calloc (1, sizeof (struct down_root_context));
277 context->foreground_fd = -1;
280 * Intercept the --up and --down callbacks
282 *type_mask = OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_UP) | OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_DOWN);
285 * Make sure we have two string arguments: the first is the .so name,
286 * the second is the script command.
288 if (string_array_len (argv) < 2)
290 fprintf (stderr, "DOWN-ROOT: need down script command\n");
291 goto error;
295 * Save our argument in context
297 context->command = build_command_line (&argv[1]);
300 * Get verbosity level from environment
303 const char *verb_string = get_env ("verb", envp);
304 if (verb_string)
305 context->verb = atoi (verb_string);
308 return (openvpn_plugin_handle_t) context;
310 error:
311 free_context (context);
312 return NULL;
315 OPENVPN_EXPORT int
316 openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
318 struct down_root_context *context = (struct down_root_context *) handle;
320 if (type == OPENVPN_PLUGIN_UP && context->foreground_fd == -1) /* fork off a process to hold onto root */
322 pid_t pid;
323 int fd[2];
326 * Make a socket for foreground and background processes
327 * to communicate.
329 if (socketpair (PF_UNIX, SOCK_DGRAM, 0, fd) == -1)
331 fprintf (stderr, "DOWN-ROOT: socketpair call failed\n");
332 return OPENVPN_PLUGIN_FUNC_ERROR;
336 * Fork off the privileged process. It will remain privileged
337 * even after the foreground process drops its privileges.
339 pid = fork ();
341 if (pid)
343 int status;
346 * Foreground Process
349 context->background_pid = pid;
351 /* close our copy of child's socket */
352 close (fd[1]);
354 /* don't let future subprocesses inherit child socket */
355 if (fcntl (fd[0], F_SETFD, FD_CLOEXEC) < 0)
356 fprintf (stderr, "DOWN-ROOT: Set FD_CLOEXEC flag on socket file descriptor failed\n");
358 /* wait for background child process to initialize */
359 status = recv_control (fd[0]);
360 if (status == RESPONSE_INIT_SUCCEEDED)
362 context->foreground_fd = fd[0];
363 return OPENVPN_PLUGIN_FUNC_SUCCESS;
366 else
369 * Background Process
372 /* close all parent fds except our socket back to parent */
373 close_fds_except (fd[1]);
375 /* Ignore most signals (the parent will receive them) */
376 set_signals ();
378 /* Daemonize if --daemon option is set. */
379 daemonize (envp);
381 /* execute the event loop */
382 down_root_server (fd[1], context->command, argv, envp, context->verb);
384 close (fd[1]);
385 exit (0);
386 return 0; /* NOTREACHED */
389 else if (type == OPENVPN_PLUGIN_DOWN && context->foreground_fd >= 0)
391 if (send_control (context->foreground_fd, COMMAND_RUN_SCRIPT) == -1)
393 fprintf (stderr, "DOWN-ROOT: Error sending script execution signal to background process\n");
395 else
397 const int status = recv_control (context->foreground_fd);
398 if (status == RESPONSE_SCRIPT_SUCCEEDED)
399 return OPENVPN_PLUGIN_FUNC_SUCCESS;
400 if (status == -1)
401 fprintf (stderr, "DOWN-ROOT: Error receiving script execution confirmation from background process\n");
404 return OPENVPN_PLUGIN_FUNC_ERROR;
407 OPENVPN_EXPORT void
408 openvpn_plugin_close_v1 (openvpn_plugin_handle_t handle)
410 struct down_root_context *context = (struct down_root_context *) handle;
412 if (DEBUG (context->verb))
413 fprintf (stderr, "DOWN-ROOT: close\n");
415 if (context->foreground_fd >= 0)
417 /* tell background process to exit */
418 if (send_control (context->foreground_fd, COMMAND_EXIT) == -1)
419 fprintf (stderr, "DOWN-ROOT: Error signaling background process to exit\n");
421 /* wait for background process to exit */
422 if (context->background_pid > 0)
423 waitpid (context->background_pid, NULL, 0);
425 close (context->foreground_fd);
426 context->foreground_fd = -1;
429 free_context (context);
432 OPENVPN_EXPORT void
433 openvpn_plugin_abort_v1 (openvpn_plugin_handle_t handle)
435 struct down_root_context *context = (struct down_root_context *) handle;
437 if (context->foreground_fd >= 0)
439 /* tell background process to exit */
440 send_control (context->foreground_fd, COMMAND_EXIT);
441 close (context->foreground_fd);
442 context->foreground_fd = -1;
447 * Background process -- runs with privilege.
449 static void
450 down_root_server (const int fd, char *command, const char *argv[], const char *envp[], const int verb)
452 const char *p[3];
453 char *command_line = NULL;
454 char *argv_cat = NULL;
455 int i;
458 * Do initialization
460 if (DEBUG (verb))
461 fprintf (stderr, "DOWN-ROOT: BACKGROUND: INIT command='%s'\n", command);
464 * Tell foreground that we initialized successfully
466 if (send_control (fd, RESPONSE_INIT_SUCCEEDED) == -1)
468 fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [1]\n");
469 goto done;
473 * Build command line
475 if (string_array_len (argv) >= 2)
476 argv_cat = build_command_line (&argv[1]);
477 else
478 argv_cat = build_command_line (NULL);
479 p[0] = command;
480 p[1] = argv_cat;
481 p[2] = NULL;
482 command_line = build_command_line (p);
485 * Save envp in environment
487 for (i = 0; envp[i]; ++i)
489 putenv ((char *)envp[i]);
493 * Event loop
495 while (1)
497 int command_code;
498 int status;
500 /* get a command from foreground process */
501 command_code = recv_control (fd);
503 if (DEBUG (verb))
504 fprintf (stderr, "DOWN-ROOT: BACKGROUND: received command code: %d\n", command_code);
506 switch (command_code)
508 case COMMAND_RUN_SCRIPT:
509 status = system (command_line);
510 if (system_ok (status)) /* Succeeded */
512 if (send_control (fd, RESPONSE_SCRIPT_SUCCEEDED) == -1)
514 fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [2]\n");
515 goto done;
518 else /* Failed */
520 if (send_control (fd, RESPONSE_SCRIPT_FAILED) == -1)
522 fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [3]\n");
523 goto done;
526 break;
528 case COMMAND_EXIT:
529 goto done;
531 case -1:
532 fprintf (stderr, "DOWN-ROOT: BACKGROUND: read error on command channel\n");
533 goto done;
535 default:
536 fprintf (stderr, "DOWN-ROOT: BACKGROUND: unknown command code: code=%d, exiting\n",
537 command_code);
538 goto done;
542 done:
543 if (argv_cat)
544 free (argv_cat);
545 if (command_line)
546 free (command_line);
547 if (DEBUG (verb))
548 fprintf (stderr, "DOWN-ROOT: BACKGROUND: EXIT\n");
550 return;