Upgrade to OpenVPN 2.1.0
[tomato.git] / release / src / router / openvpn / plugin / down-root / down-root.c
blob7cf8f181a0099c6a0e4342985ec6e22efe75a06c
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-2009 OpenVPN Technologies, Inc. <sales@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 if (!context)
278 goto error;
279 context->foreground_fd = -1;
282 * Intercept the --up and --down callbacks
284 *type_mask = OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_UP) | OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_DOWN);
287 * Make sure we have two string arguments: the first is the .so name,
288 * the second is the script command.
290 if (string_array_len (argv) < 2)
292 fprintf (stderr, "DOWN-ROOT: need down script command\n");
293 goto error;
297 * Save our argument in context
299 context->command = build_command_line (&argv[1]);
302 * Get verbosity level from environment
305 const char *verb_string = get_env ("verb", envp);
306 if (verb_string)
307 context->verb = atoi (verb_string);
310 return (openvpn_plugin_handle_t) context;
312 error:
313 free_context (context);
314 return NULL;
317 OPENVPN_EXPORT int
318 openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
320 struct down_root_context *context = (struct down_root_context *) handle;
322 if (type == OPENVPN_PLUGIN_UP && context->foreground_fd == -1) /* fork off a process to hold onto root */
324 pid_t pid;
325 int fd[2];
328 * Make a socket for foreground and background processes
329 * to communicate.
331 if (socketpair (PF_UNIX, SOCK_DGRAM, 0, fd) == -1)
333 fprintf (stderr, "DOWN-ROOT: socketpair call failed\n");
334 return OPENVPN_PLUGIN_FUNC_ERROR;
338 * Fork off the privileged process. It will remain privileged
339 * even after the foreground process drops its privileges.
341 pid = fork ();
343 if (pid)
345 int status;
348 * Foreground Process
351 context->background_pid = pid;
353 /* close our copy of child's socket */
354 close (fd[1]);
356 /* don't let future subprocesses inherit child socket */
357 if (fcntl (fd[0], F_SETFD, FD_CLOEXEC) < 0)
358 fprintf (stderr, "DOWN-ROOT: Set FD_CLOEXEC flag on socket file descriptor failed\n");
360 /* wait for background child process to initialize */
361 status = recv_control (fd[0]);
362 if (status == RESPONSE_INIT_SUCCEEDED)
364 context->foreground_fd = fd[0];
365 return OPENVPN_PLUGIN_FUNC_SUCCESS;
368 else
371 * Background Process
374 /* close all parent fds except our socket back to parent */
375 close_fds_except (fd[1]);
377 /* Ignore most signals (the parent will receive them) */
378 set_signals ();
380 /* Daemonize if --daemon option is set. */
381 daemonize (envp);
383 /* execute the event loop */
384 down_root_server (fd[1], context->command, argv, envp, context->verb);
386 close (fd[1]);
387 exit (0);
388 return 0; /* NOTREACHED */
391 else if (type == OPENVPN_PLUGIN_DOWN && context->foreground_fd >= 0)
393 if (send_control (context->foreground_fd, COMMAND_RUN_SCRIPT) == -1)
395 fprintf (stderr, "DOWN-ROOT: Error sending script execution signal to background process\n");
397 else
399 const int status = recv_control (context->foreground_fd);
400 if (status == RESPONSE_SCRIPT_SUCCEEDED)
401 return OPENVPN_PLUGIN_FUNC_SUCCESS;
402 if (status == -1)
403 fprintf (stderr, "DOWN-ROOT: Error receiving script execution confirmation from background process\n");
406 return OPENVPN_PLUGIN_FUNC_ERROR;
409 OPENVPN_EXPORT void
410 openvpn_plugin_close_v1 (openvpn_plugin_handle_t handle)
412 struct down_root_context *context = (struct down_root_context *) handle;
414 if (DEBUG (context->verb))
415 fprintf (stderr, "DOWN-ROOT: close\n");
417 if (context->foreground_fd >= 0)
419 /* tell background process to exit */
420 if (send_control (context->foreground_fd, COMMAND_EXIT) == -1)
421 fprintf (stderr, "DOWN-ROOT: Error signaling background process to exit\n");
423 /* wait for background process to exit */
424 if (context->background_pid > 0)
425 waitpid (context->background_pid, NULL, 0);
427 close (context->foreground_fd);
428 context->foreground_fd = -1;
431 free_context (context);
434 OPENVPN_EXPORT void
435 openvpn_plugin_abort_v1 (openvpn_plugin_handle_t handle)
437 struct down_root_context *context = (struct down_root_context *) handle;
439 if (context && context->foreground_fd >= 0)
441 /* tell background process to exit */
442 send_control (context->foreground_fd, COMMAND_EXIT);
443 close (context->foreground_fd);
444 context->foreground_fd = -1;
449 * Background process -- runs with privilege.
451 static void
452 down_root_server (const int fd, char *command, const char *argv[], const char *envp[], const int verb)
454 const char *p[3];
455 char *command_line = NULL;
456 char *argv_cat = NULL;
457 int i;
460 * Do initialization
462 if (DEBUG (verb))
463 fprintf (stderr, "DOWN-ROOT: BACKGROUND: INIT command='%s'\n", command);
466 * Tell foreground that we initialized successfully
468 if (send_control (fd, RESPONSE_INIT_SUCCEEDED) == -1)
470 fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [1]\n");
471 goto done;
475 * Build command line
477 if (string_array_len (argv) >= 2)
478 argv_cat = build_command_line (&argv[1]);
479 else
480 argv_cat = build_command_line (NULL);
481 p[0] = command;
482 p[1] = argv_cat;
483 p[2] = NULL;
484 command_line = build_command_line (p);
487 * Save envp in environment
489 for (i = 0; envp[i]; ++i)
491 putenv ((char *)envp[i]);
495 * Event loop
497 while (1)
499 int command_code;
500 int status;
502 /* get a command from foreground process */
503 command_code = recv_control (fd);
505 if (DEBUG (verb))
506 fprintf (stderr, "DOWN-ROOT: BACKGROUND: received command code: %d\n", command_code);
508 switch (command_code)
510 case COMMAND_RUN_SCRIPT:
511 status = system (command_line);
512 if (system_ok (status)) /* Succeeded */
514 if (send_control (fd, RESPONSE_SCRIPT_SUCCEEDED) == -1)
516 fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [2]\n");
517 goto done;
520 else /* Failed */
522 if (send_control (fd, RESPONSE_SCRIPT_FAILED) == -1)
524 fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [3]\n");
525 goto done;
528 break;
530 case COMMAND_EXIT:
531 goto done;
533 case -1:
534 fprintf (stderr, "DOWN-ROOT: BACKGROUND: read error on command channel\n");
535 goto done;
537 default:
538 fprintf (stderr, "DOWN-ROOT: BACKGROUND: unknown command code: code=%d, exiting\n",
539 command_code);
540 goto done;
544 done:
545 if (argv_cat)
546 free (argv_cat);
547 if (command_line)
548 free (command_line);
549 if (DEBUG (verb))
550 fprintf (stderr, "DOWN-ROOT: BACKGROUND: EXIT\n");
552 return;