OPENVPN_EXPORT int openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[]) { struct auth_pam_context *context = (struct auth_pam_context *) handle; if (type == OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY && context->foreground_fd >= 0) { /* get username/password from envp string array */ const char *username = get_env ("username", envp); const char *password = get_env ("password", envp); const char *common_name = get_env ("common_name", envp) ? get_env ("common_name", envp) : ""; if (username && strlen (username) > 0 && password) { if (send_control (context->foreground_fd, COMMAND_VERIFY) == -1 || send_string (context->foreground_fd, username) == -1 || send_string (context->foreground_fd, password) == -1 || send_string (context->foreground_fd, common_name) == -1) { fprintf (stderr, "AUTH-PAM: Error sending auth info to background process\n"); } else { const int status = recv_control (context->foreground_fd); if (status == RESPONSE_VERIFY_SUCCEEDED) return OPENVPN_PLUGIN_FUNC_SUCCESS; if (status == -1) fprintf (stderr, "AUTH-PAM: Error receiving auth confirmation from background process\n"); } } } return OPENVPN_PLUGIN_FUNC_ERROR; }
/* * Called from the main OpenVPN process to enable the port * share proxy. */ struct port_share * port_share_open (const char *host, const int port, const int max_initial_buf, const char *journal_dir) { pid_t pid; socket_descriptor_t fd[2]; in_addr_t hostaddr; struct port_share *ps; ALLOC_OBJ_CLEAR (ps, struct port_share); ps->foreground_fd = -1; ps->background_pid = -1; /* * Get host's IP address */ hostaddr = getaddr (GETADDR_RESOLVE|GETADDR_HOST_ORDER|GETADDR_FATAL, host, 0, NULL, NULL); /* * Make a socket for foreground and background processes * to communicate. */ if (socketpair (PF_UNIX, SOCK_DGRAM, 0, fd) == -1) { msg (M_WARN, "PORT SHARE: socketpair call failed"); goto error; } /* * Fork off background proxy process. */ pid = fork (); if (pid) { int status; /* * Foreground Process */ ps->background_pid = pid; /* close our copy of child's socket */ openvpn_close_socket (fd[1]); /* don't let future subprocesses inherit child socket */ set_cloexec (fd[0]); /* wait for background child process to initialize */ status = recv_control (fd[0]); if (status == RESPONSE_INIT_SUCCEEDED) { /* note that this will cause possible EAGAIN when writing to control socket if proxy process is backlogged */ set_nonblock (fd[0]); ps->foreground_fd = fd[0]; return ps; } else { msg (M_ERR, "PORT SHARE: unexpected init recv_control status=%d", status); } } else { /* * Background Process */ /* Ignore most signals (the parent will receive them) */ set_signals (); /* Let msg know that we forked */ msg_forked (); #ifdef ENABLE_MANAGEMENT /* Don't interact with management interface */ management = NULL; #endif /* close all parent fds except our socket back to parent */ close_fds_except (fd[1]); /* no blocking on control channel back to parent */ set_nonblock (fd[1]); /* initialize prng */ prng_init (NULL, 0); /* execute the event loop */ port_share_proxy (hostaddr, port, fd[1], max_initial_buf, journal_dir); openvpn_close_socket (fd[1]); exit (0); return 0; /* NOTREACHED */ } error: port_share_close (ps); return NULL; }
/* * Background process -- runs with privilege. */ static void pam_server (int fd, const char *service, int verb, const struct name_value_list *name_value_list) { struct user_pass up; int command; #ifdef USE_PAM_DLOPEN static const char pam_so[] = "libpam.so"; #endif /* * Do initialization */ if (DEBUG (verb)) fprintf (stderr, "AUTH-PAM: BACKGROUND: INIT service='%s'\n", service); #ifdef USE_PAM_DLOPEN /* * Load PAM shared object */ if (!dlopen_pam (pam_so)) { fprintf (stderr, "AUTH-PAM: BACKGROUND: could not load PAM lib %s: %s\n", pam_so, dlerror()); send_control (fd, RESPONSE_INIT_FAILED); goto done; } #endif /* * Tell foreground that we initialized successfully */ if (send_control (fd, RESPONSE_INIT_SUCCEEDED) == -1) { fprintf (stderr, "AUTH-PAM: BACKGROUND: write error on response socket [1]\n"); goto done; } /* * Event loop */ while (1) { memset (&up, 0, sizeof (up)); up.verb = verb; up.name_value_list = name_value_list; /* get a command from foreground process */ command = recv_control (fd); if (DEBUG (verb)) fprintf (stderr, "AUTH-PAM: BACKGROUND: received command code: %d\n", command); switch (command) { case COMMAND_VERIFY: if (recv_string (fd, up.username, sizeof (up.username)) == -1 || recv_string (fd, up.password, sizeof (up.password)) == -1 || recv_string (fd, up.common_name, sizeof (up.common_name)) == -1) { fprintf (stderr, "AUTH-PAM: BACKGROUND: read error on command channel: code=%d, exiting\n", command); goto done; } if (DEBUG (verb)) { #if 0 fprintf (stderr, "AUTH-PAM: BACKGROUND: USER/PASS: %s/%s\n", up.username, up.password); #else fprintf (stderr, "AUTH-PAM: BACKGROUND: USER: %s\n", up.username); #endif } if (pam_auth (service, &up)) /* Succeeded */ { if (send_control (fd, RESPONSE_VERIFY_SUCCEEDED) == -1) { fprintf (stderr, "AUTH-PAM: BACKGROUND: write error on response socket [2]\n"); goto done; } } else /* Failed */ { if (send_control (fd, RESPONSE_VERIFY_FAILED) == -1) { fprintf (stderr, "AUTH-PAM: BACKGROUND: write error on response socket [3]\n"); goto done; } } break; case COMMAND_EXIT: goto done; case -1: fprintf (stderr, "AUTH-PAM: BACKGROUND: read error on command channel\n"); goto done; default: fprintf (stderr, "AUTH-PAM: BACKGROUND: unknown command code: code=%d, exiting\n", command); goto done; } } done: #ifdef USE_PAM_DLOPEN dlclose_pam (); #endif if (DEBUG (verb)) fprintf (stderr, "AUTH-PAM: BACKGROUND: EXIT\n"); return; }
OPENVPN_EXPORT openvpn_plugin_handle_t openvpn_plugin_open_v1 (unsigned int *type_mask, const char *argv[], const char *envp[]) { pid_t pid; int fd[2]; struct auth_pam_context *context; struct name_value_list name_value_list; const int base_parms = 2; /* * Allocate our context */ context = (struct auth_pam_context *) calloc (1, sizeof (struct auth_pam_context)); if (!context) goto error; context->foreground_fd = -1; /* * Intercept the --auth-user-pass-verify callback. */ *type_mask = OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY); /* * Make sure we have two string arguments: the first is the .so name, * the second is the PAM service type. */ if (string_array_len (argv) < base_parms) { fprintf (stderr, "AUTH-PAM: need PAM service parameter\n"); goto error; } /* * See if we have optional name/value pairs to match against * PAM module queried fields in the conversation function. */ name_value_list.len = 0; if (string_array_len (argv) > base_parms) { const int nv_len = string_array_len (argv) - base_parms; int i; if ((nv_len & 1) == 1 || (nv_len / 2) > N_NAME_VALUE) { fprintf (stderr, "AUTH-PAM: bad name/value list length\n"); goto error; } name_value_list.len = nv_len / 2; for (i = 0; i < name_value_list.len; ++i) { const int base = base_parms + i * 2; name_value_list.data[i].name = argv[base]; name_value_list.data[i].value = argv[base+1]; } } /* * Get verbosity level from environment */ { const char *verb_string = get_env ("verb", envp); if (verb_string) context->verb = atoi (verb_string); } /* * Make a socket for foreground and background processes * to communicate. */ if (socketpair (PF_UNIX, SOCK_DGRAM, 0, fd) == -1) { fprintf (stderr, "AUTH-PAM: socketpair call failed\n"); goto error; } /* * Fork off the privileged process. It will remain privileged * even after the foreground process drops its privileges. */ pid = fork (); if (pid) { int status; /* * Foreground Process */ context->background_pid = pid; /* close our copy of child's socket */ close (fd[1]); /* don't let future subprocesses inherit child socket */ if (fcntl (fd[0], F_SETFD, FD_CLOEXEC) < 0) fprintf (stderr, "AUTH-PAM: Set FD_CLOEXEC flag on socket file descriptor failed\n"); /* wait for background child process to initialize */ status = recv_control (fd[0]); if (status == RESPONSE_INIT_SUCCEEDED) { context->foreground_fd = fd[0]; return (openvpn_plugin_handle_t) context; } } else { /* * Background Process */ /* close all parent fds except our socket back to parent */ close_fds_except (fd[1]); /* Ignore most signals (the parent will receive them) */ set_signals (); #ifdef DO_DAEMONIZE /* Daemonize if --daemon option is set. */ daemonize (envp); #endif /* execute the event loop */ pam_server (fd[1], argv[1], context->verb, &name_value_list); close (fd[1]); exit (0); return 0; /* NOTREACHED */ } error: if (context) free (context); return NULL; }
/* * Background process -- runs with privilege. */ static void down_root_server (const int fd, char *command, const char *argv[], const char *envp[], const int verb) { const char *p[3]; char *command_line = NULL; char *argv_cat = NULL; int i; /* * Do initialization */ if (DEBUG (verb)) fprintf (stderr, "DOWN-ROOT: BACKGROUND: INIT command='%s'\n", command); /* * Tell foreground that we initialized successfully */ if (send_control (fd, RESPONSE_INIT_SUCCEEDED) == -1) { fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [1]\n"); goto done; } /* * Build command line */ if (string_array_len (argv) >= 2) argv_cat = build_command_line (&argv[1]); else argv_cat = build_command_line (NULL); p[0] = command; p[1] = argv_cat; p[2] = NULL; command_line = build_command_line (p); /* * Save envp in environment */ for (i = 0; envp[i]; ++i) { putenv ((char *)envp[i]); } /* * Event loop */ while (1) { int command_code; int status; /* get a command from foreground process */ command_code = recv_control (fd); if (DEBUG (verb)) fprintf (stderr, "DOWN-ROOT: BACKGROUND: received command code: %d\n", command_code); switch (command_code) { case COMMAND_RUN_SCRIPT: status = system (command_line); if (system_ok (status)) /* Succeeded */ { if (send_control (fd, RESPONSE_SCRIPT_SUCCEEDED) == -1) { fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [2]\n"); goto done; } } else /* Failed */ { if (send_control (fd, RESPONSE_SCRIPT_FAILED) == -1) { fprintf (stderr, "DOWN-ROOT: BACKGROUND: write error on response socket [3]\n"); goto done; } } break; case COMMAND_EXIT: goto done; case -1: fprintf (stderr, "DOWN-ROOT: BACKGROUND: read error on command channel\n"); goto done; default: fprintf (stderr, "DOWN-ROOT: BACKGROUND: unknown command code: code=%d, exiting\n", command_code); goto done; } } done: if (argv_cat) free (argv_cat); if (command_line) free (command_line); if (DEBUG (verb)) fprintf (stderr, "DOWN-ROOT: BACKGROUND: EXIT\n"); return; }
OPENVPN_EXPORT int openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[]) { struct down_root_context *context = (struct down_root_context *) handle; if (type == OPENVPN_PLUGIN_UP && context->foreground_fd == -1) /* fork off a process to hold onto root */ { pid_t pid; int fd[2]; /* * Make a socket for foreground and background processes * to communicate. */ if (socketpair (PF_UNIX, SOCK_DGRAM, 0, fd) == -1) { fprintf (stderr, "DOWN-ROOT: socketpair call failed\n"); return OPENVPN_PLUGIN_FUNC_ERROR; } /* * Fork off the privileged process. It will remain privileged * even after the foreground process drops its privileges. */ pid = fork (); if (pid) { int status; /* * Foreground Process */ context->background_pid = pid; /* close our copy of child's socket */ close (fd[1]); /* don't let future subprocesses inherit child socket */ if (fcntl (fd[0], F_SETFD, FD_CLOEXEC) < 0) fprintf (stderr, "DOWN-ROOT: Set FD_CLOEXEC flag on socket file descriptor failed\n"); /* wait for background child process to initialize */ status = recv_control (fd[0]); if (status == RESPONSE_INIT_SUCCEEDED) { context->foreground_fd = fd[0]; return OPENVPN_PLUGIN_FUNC_SUCCESS; } } else { /* * Background Process */ /* close all parent fds except our socket back to parent */ close_fds_except (fd[1]); /* Ignore most signals (the parent will receive them) */ set_signals (); /* Daemonize if --daemon option is set. */ daemonize (envp); /* execute the event loop */ down_root_server (fd[1], context->command, argv, envp, context->verb); close (fd[1]); exit (0); return 0; /* NOTREACHED */ } } else if (type == OPENVPN_PLUGIN_DOWN && context->foreground_fd >= 0) { if (send_control (context->foreground_fd, COMMAND_RUN_SCRIPT) == -1) { fprintf (stderr, "DOWN-ROOT: Error sending script execution signal to background process\n"); } else { const int status = recv_control (context->foreground_fd); if (status == RESPONSE_SCRIPT_SUCCEEDED) return OPENVPN_PLUGIN_FUNC_SUCCESS; if (status == -1) fprintf (stderr, "DOWN-ROOT: Error receiving script execution confirmation from background process\n"); } } return OPENVPN_PLUGIN_FUNC_ERROR; }
/* * Background process -- runs with privilege. */ static void down_root_server(const int fd, char *const *argv, char *const *envp, const int verb) { /* * Do initialization */ if (DEBUG(verb)) { fprintf(stderr, "DOWN-ROOT: BACKGROUND: INIT command='%s'\n", argv[0]); } /* * Tell foreground that we initialized successfully */ if (send_control(fd, RESPONSE_INIT_SUCCEEDED) == -1) { warn("DOWN-ROOT: BACKGROUND: write error on response socket [1]"); goto done; } /* * Event loop */ while (1) { int command_code; int exit_code = -1; /* get a command from foreground process */ command_code = recv_control(fd); if (DEBUG(verb)) { fprintf(stderr, "DOWN-ROOT: BACKGROUND: received command code: %d\n", command_code); } switch (command_code) { case COMMAND_RUN_SCRIPT: if ( (exit_code = run_script(argv, envp)) == 0) /* Succeeded */ { if (send_control(fd, RESPONSE_SCRIPT_SUCCEEDED) == -1) { warn("DOWN-ROOT: BACKGROUND: write error on response socket [2]"); goto done; } } else /* Failed */ { fprintf(stderr, "DOWN-ROOT: BACKGROUND: %s exited with exit code %i\n", argv[0], exit_code); if (send_control(fd, RESPONSE_SCRIPT_FAILED) == -1) { warn("DOWN-ROOT: BACKGROUND: write error on response socket [3]"); goto done; } } break; case COMMAND_EXIT: goto done; case -1: warn("DOWN-ROOT: BACKGROUND: read error on command channel"); goto done; default: fprintf(stderr, "DOWN-ROOT: BACKGROUND: unknown command code: code=%d, exiting\n", command_code); goto done; } } done: if (DEBUG(verb)) { fprintf(stderr, "DOWN-ROOT: BACKGROUND: EXIT\n"); } return; }