Upgrade to OpenVPN 2.1.0
[tomato.git] / release / src / router / openvpn / plugin / auth-pam / auth-pam.c
blobb029f85cb4882b0c5696de49d464ad44300cf174
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 PAM authentication using a split
27 * privilege model.
30 #if DLOPEN_PAM
31 #include <dlfcn.h>
32 #include "pamdl.h"
33 #else
34 #include <security/pam_appl.h>
35 #endif
37 #include <stdio.h>
38 #include <string.h>
39 #include <ctype.h>
40 #include <unistd.h>
41 #include <stdlib.h>
42 #include <sys/types.h>
43 #include <sys/socket.h>
44 #include <sys/wait.h>
45 #include <fcntl.h>
46 #include <signal.h>
47 #include <syslog.h>
49 #include "openvpn-plugin.h"
51 #define DEBUG(verb) ((verb) >= 4)
53 /* Command codes for foreground -> background communication */
54 #define COMMAND_VERIFY 0
55 #define COMMAND_EXIT 1
57 /* Response codes for background -> foreground communication */
58 #define RESPONSE_INIT_SUCCEEDED 10
59 #define RESPONSE_INIT_FAILED 11
60 #define RESPONSE_VERIFY_SUCCEEDED 12
61 #define RESPONSE_VERIFY_FAILED 13
64 * Plugin state, used by foreground
66 struct auth_pam_context
68 /* Foreground's socket to background process */
69 int foreground_fd;
71 /* Process ID of background process */
72 pid_t background_pid;
74 /* Verbosity level of OpenVPN */
75 int verb;
79 * Name/Value pairs for conversation function.
80 * Special Values:
82 * "USERNAME" -- substitute client-supplied username
83 * "PASSWORD" -- substitute client-specified password
86 #define N_NAME_VALUE 16
88 struct name_value {
89 const char *name;
90 const char *value;
93 struct name_value_list {
94 int len;
95 struct name_value data[N_NAME_VALUE];
99 * Used to pass the username/password
100 * to the PAM conversation function.
102 struct user_pass {
103 int verb;
105 char username[128];
106 char password[128];
108 const struct name_value_list *name_value_list;
111 /* Background process function */
112 static void pam_server (int fd, const char *service, int verb, const struct name_value_list *name_value_list);
115 * Given an environmental variable name, search
116 * the envp array for its value, returning it
117 * if found or NULL otherwise.
119 static const char *
120 get_env (const char *name, const char *envp[])
122 if (envp)
124 int i;
125 const int namelen = strlen (name);
126 for (i = 0; envp[i]; ++i)
128 if (!strncmp (envp[i], name, namelen))
130 const char *cp = envp[i] + namelen;
131 if (*cp == '=')
132 return cp + 1;
136 return NULL;
140 * Return the length of a string array
142 static int
143 string_array_len (const char *array[])
145 int i = 0;
146 if (array)
148 while (array[i])
149 ++i;
151 return i;
155 * Socket read/write functions.
158 static int
159 recv_control (int fd)
161 unsigned char c;
162 const ssize_t size = read (fd, &c, sizeof (c));
163 if (size == sizeof (c))
164 return c;
165 else
167 /*fprintf (stderr, "AUTH-PAM: DEBUG recv_control.read=%d\n", (int)size);*/
168 return -1;
172 static int
173 send_control (int fd, int code)
175 unsigned char c = (unsigned char) code;
176 const ssize_t size = write (fd, &c, sizeof (c));
177 if (size == sizeof (c))
178 return (int) size;
179 else
180 return -1;
183 static int
184 recv_string (int fd, char *buffer, int len)
186 if (len > 0)
188 ssize_t size;
189 memset (buffer, 0, len);
190 size = read (fd, buffer, len);
191 buffer[len-1] = 0;
192 if (size >= 1)
193 return (int)size;
195 return -1;
198 static int
199 send_string (int fd, const char *string)
201 const int len = strlen (string) + 1;
202 const ssize_t size = write (fd, string, len);
203 if (size == len)
204 return (int) size;
205 else
206 return -1;
209 #ifdef DO_DAEMONIZE
212 * Daemonize if "daemon" env var is true.
213 * Preserve stderr across daemonization if
214 * "daemon_log_redirect" env var is true.
216 static void
217 daemonize (const char *envp[])
219 const char *daemon_string = get_env ("daemon", envp);
220 if (daemon_string && daemon_string[0] == '1')
222 const char *log_redirect = get_env ("daemon_log_redirect", envp);
223 int fd = -1;
224 if (log_redirect && log_redirect[0] == '1')
225 fd = dup (2);
226 if (daemon (0, 0) < 0)
228 fprintf (stderr, "AUTH-PAM: daemonization failed\n");
230 else if (fd >= 3)
232 dup2 (fd, 2);
233 close (fd);
238 #endif
241 * Close most of parent's fds.
242 * Keep stdin/stdout/stderr, plus one
243 * other fd which is presumed to be
244 * our pipe back to parent.
245 * Admittedly, a bit of a kludge,
246 * but posix doesn't give us a kind
247 * of FD_CLOEXEC which will stop
248 * fds from crossing a fork().
250 static void
251 close_fds_except (int keep)
253 int i;
254 closelog ();
255 for (i = 3; i <= 100; ++i)
257 if (i != keep)
258 close (i);
263 * Usually we ignore signals, because our parent will
264 * deal with them.
266 static void
267 set_signals (void)
269 signal (SIGTERM, SIG_DFL);
271 signal (SIGINT, SIG_IGN);
272 signal (SIGHUP, SIG_IGN);
273 signal (SIGUSR1, SIG_IGN);
274 signal (SIGUSR2, SIG_IGN);
275 signal (SIGPIPE, SIG_IGN);
279 * Return 1 if query matches match.
281 static int
282 name_value_match (const char *query, const char *match)
284 while (!isalnum (*query))
286 if (*query == '\0')
287 return 0;
288 ++query;
290 return strncasecmp (match, query, strlen (match)) == 0;
293 OPENVPN_EXPORT openvpn_plugin_handle_t
294 openvpn_plugin_open_v1 (unsigned int *type_mask, const char *argv[], const char *envp[])
296 pid_t pid;
297 int fd[2];
299 struct auth_pam_context *context;
300 struct name_value_list name_value_list;
302 const int base_parms = 2;
305 * Allocate our context
307 context = (struct auth_pam_context *) calloc (1, sizeof (struct auth_pam_context));
308 if (!context)
309 goto error;
310 context->foreground_fd = -1;
313 * Intercept the --auth-user-pass-verify callback.
315 *type_mask = OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY);
318 * Make sure we have two string arguments: the first is the .so name,
319 * the second is the PAM service type.
321 if (string_array_len (argv) < base_parms)
323 fprintf (stderr, "AUTH-PAM: need PAM service parameter\n");
324 goto error;
328 * See if we have optional name/value pairs to match against
329 * PAM module queried fields in the conversation function.
331 name_value_list.len = 0;
332 if (string_array_len (argv) > base_parms)
334 const int nv_len = string_array_len (argv) - base_parms;
335 int i;
337 if ((nv_len & 1) == 1 || (nv_len / 2) > N_NAME_VALUE)
339 fprintf (stderr, "AUTH-PAM: bad name/value list length\n");
340 goto error;
343 name_value_list.len = nv_len / 2;
344 for (i = 0; i < name_value_list.len; ++i)
346 const int base = base_parms + i * 2;
347 name_value_list.data[i].name = argv[base];
348 name_value_list.data[i].value = argv[base+1];
353 * Get verbosity level from environment
356 const char *verb_string = get_env ("verb", envp);
357 if (verb_string)
358 context->verb = atoi (verb_string);
362 * Make a socket for foreground and background processes
363 * to communicate.
365 if (socketpair (PF_UNIX, SOCK_DGRAM, 0, fd) == -1)
367 fprintf (stderr, "AUTH-PAM: socketpair call failed\n");
368 goto error;
372 * Fork off the privileged process. It will remain privileged
373 * even after the foreground process drops its privileges.
375 pid = fork ();
377 if (pid)
379 int status;
382 * Foreground Process
385 context->background_pid = pid;
387 /* close our copy of child's socket */
388 close (fd[1]);
390 /* don't let future subprocesses inherit child socket */
391 if (fcntl (fd[0], F_SETFD, FD_CLOEXEC) < 0)
392 fprintf (stderr, "AUTH-PAM: Set FD_CLOEXEC flag on socket file descriptor failed\n");
394 /* wait for background child process to initialize */
395 status = recv_control (fd[0]);
396 if (status == RESPONSE_INIT_SUCCEEDED)
398 context->foreground_fd = fd[0];
399 return (openvpn_plugin_handle_t) context;
402 else
405 * Background Process
408 /* close all parent fds except our socket back to parent */
409 close_fds_except (fd[1]);
411 /* Ignore most signals (the parent will receive them) */
412 set_signals ();
414 #ifdef DO_DAEMONIZE
415 /* Daemonize if --daemon option is set. */
416 daemonize (envp);
417 #endif
419 /* execute the event loop */
420 pam_server (fd[1], argv[1], context->verb, &name_value_list);
422 close (fd[1]);
424 exit (0);
425 return 0; /* NOTREACHED */
428 error:
429 if (context)
430 free (context);
431 return NULL;
434 OPENVPN_EXPORT int
435 openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
437 struct auth_pam_context *context = (struct auth_pam_context *) handle;
439 if (type == OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY && context->foreground_fd >= 0)
441 /* get username/password from envp string array */
442 const char *username = get_env ("username", envp);
443 const char *password = get_env ("password", envp);
445 if (username && strlen (username) > 0 && password)
447 if (send_control (context->foreground_fd, COMMAND_VERIFY) == -1
448 || send_string (context->foreground_fd, username) == -1
449 || send_string (context->foreground_fd, password) == -1)
451 fprintf (stderr, "AUTH-PAM: Error sending auth info to background process\n");
453 else
455 const int status = recv_control (context->foreground_fd);
456 if (status == RESPONSE_VERIFY_SUCCEEDED)
457 return OPENVPN_PLUGIN_FUNC_SUCCESS;
458 if (status == -1)
459 fprintf (stderr, "AUTH-PAM: Error receiving auth confirmation from background process\n");
463 return OPENVPN_PLUGIN_FUNC_ERROR;
466 OPENVPN_EXPORT void
467 openvpn_plugin_close_v1 (openvpn_plugin_handle_t handle)
469 struct auth_pam_context *context = (struct auth_pam_context *) handle;
471 if (DEBUG (context->verb))
472 fprintf (stderr, "AUTH-PAM: close\n");
474 if (context->foreground_fd >= 0)
476 /* tell background process to exit */
477 if (send_control (context->foreground_fd, COMMAND_EXIT) == -1)
478 fprintf (stderr, "AUTH-PAM: Error signaling background process to exit\n");
480 /* wait for background process to exit */
481 if (context->background_pid > 0)
482 waitpid (context->background_pid, NULL, 0);
484 close (context->foreground_fd);
485 context->foreground_fd = -1;
488 free (context);
491 OPENVPN_EXPORT void
492 openvpn_plugin_abort_v1 (openvpn_plugin_handle_t handle)
494 struct auth_pam_context *context = (struct auth_pam_context *) handle;
496 /* tell background process to exit */
497 if (context && context->foreground_fd >= 0)
499 send_control (context->foreground_fd, COMMAND_EXIT);
500 close (context->foreground_fd);
501 context->foreground_fd = -1;
506 * PAM conversation function
508 static int
509 my_conv (int n, const struct pam_message **msg_array,
510 struct pam_response **response_array, void *appdata_ptr)
512 const struct user_pass *up = ( const struct user_pass *) appdata_ptr;
513 struct pam_response *aresp;
514 int i;
515 int ret = PAM_SUCCESS;
517 *response_array = NULL;
519 if (n <= 0 || n > PAM_MAX_NUM_MSG)
520 return (PAM_CONV_ERR);
521 if ((aresp = calloc (n, sizeof *aresp)) == NULL)
522 return (PAM_BUF_ERR);
524 /* loop through each PAM-module query */
525 for (i = 0; i < n; ++i)
527 const struct pam_message *msg = msg_array[i];
528 aresp[i].resp_retcode = 0;
529 aresp[i].resp = NULL;
531 if (DEBUG (up->verb))
533 fprintf (stderr, "AUTH-PAM: BACKGROUND: my_conv[%d] query='%s' style=%d\n",
535 msg->msg ? msg->msg : "NULL",
536 msg->msg_style);
539 if (up->name_value_list && up->name_value_list->len > 0)
541 /* use name/value list match method */
542 const struct name_value_list *list = up->name_value_list;
543 int j;
545 /* loop through name/value pairs */
546 for (j = 0; j < list->len; ++j)
548 const char *match_name = list->data[j].name;
549 const char *match_value = list->data[j].value;
551 if (name_value_match (msg->msg, match_name))
553 /* found name/value match */
554 const char *return_value = NULL;
556 if (DEBUG (up->verb))
557 fprintf (stderr, "AUTH-PAM: BACKGROUND: name match found, query/match-string ['%s', '%s'] = '%s'\n",
558 msg->msg,
559 match_name,
560 match_value);
562 if (!strcmp (match_value, "USERNAME"))
563 return_value = up->username;
564 else if (!strcmp (match_value, "PASSWORD"))
565 return_value = up->password;
566 else
567 return_value = match_value;
569 aresp[i].resp = strdup (return_value);
570 if (aresp[i].resp == NULL)
571 ret = PAM_CONV_ERR;
572 break;
576 if (j == list->len)
577 ret = PAM_CONV_ERR;
579 else
581 /* use PAM_PROMPT_ECHO_x hints */
582 switch (msg->msg_style)
584 case PAM_PROMPT_ECHO_OFF:
585 aresp[i].resp = strdup (up->password);
586 if (aresp[i].resp == NULL)
587 ret = PAM_CONV_ERR;
588 break;
590 case PAM_PROMPT_ECHO_ON:
591 aresp[i].resp = strdup (up->username);
592 if (aresp[i].resp == NULL)
593 ret = PAM_CONV_ERR;
594 break;
596 case PAM_ERROR_MSG:
597 case PAM_TEXT_INFO:
598 break;
600 default:
601 ret = PAM_CONV_ERR;
602 break;
607 if (ret == PAM_SUCCESS)
608 *response_array = aresp;
609 return ret;
613 * Return 1 if authenticated and 0 if failed.
614 * Called once for every username/password
615 * to be authenticated.
617 static int
618 pam_auth (const char *service, const struct user_pass *up)
620 struct pam_conv conv;
621 pam_handle_t *pamh = NULL;
622 int status = PAM_SUCCESS;
623 int ret = 0;
624 const int name_value_list_provided = (up->name_value_list && up->name_value_list->len > 0);
626 /* Initialize PAM */
627 conv.conv = my_conv;
628 conv.appdata_ptr = (void *)up;
629 status = pam_start (service, name_value_list_provided ? NULL : up->username, &conv, &pamh);
630 if (status == PAM_SUCCESS)
632 /* Call PAM to verify username/password */
633 status = pam_authenticate(pamh, 0);
634 if (status == PAM_SUCCESS)
635 status = pam_acct_mgmt (pamh, 0);
636 if (status == PAM_SUCCESS)
637 ret = 1;
639 /* Output error message if failed */
640 if (!ret)
642 fprintf (stderr, "AUTH-PAM: BACKGROUND: user '%s' failed to authenticate: %s\n",
643 up->username,
644 pam_strerror (pamh, status));
647 /* Close PAM */
648 pam_end (pamh, status);
651 return ret;
655 * Background process -- runs with privilege.
657 static void
658 pam_server (int fd, const char *service, int verb, const struct name_value_list *name_value_list)
660 struct user_pass up;
661 int command;
662 #if DLOPEN_PAM
663 static const char pam_so[] = "libpam.so";
664 #endif
667 * Do initialization
669 if (DEBUG (verb))
670 fprintf (stderr, "AUTH-PAM: BACKGROUND: INIT service='%s'\n", service);
672 #if DLOPEN_PAM
674 * Load PAM shared object
676 if (!dlopen_pam (pam_so))
678 fprintf (stderr, "AUTH-PAM: BACKGROUND: could not load PAM lib %s: %s\n", pam_so, dlerror());
679 send_control (fd, RESPONSE_INIT_FAILED);
680 goto done;
682 #endif
685 * Tell foreground that we initialized successfully
687 if (send_control (fd, RESPONSE_INIT_SUCCEEDED) == -1)
689 fprintf (stderr, "AUTH-PAM: BACKGROUND: write error on response socket [1]\n");
690 goto done;
694 * Event loop
696 while (1)
698 memset (&up, 0, sizeof (up));
699 up.verb = verb;
700 up.name_value_list = name_value_list;
702 /* get a command from foreground process */
703 command = recv_control (fd);
705 if (DEBUG (verb))
706 fprintf (stderr, "AUTH-PAM: BACKGROUND: received command code: %d\n", command);
708 switch (command)
710 case COMMAND_VERIFY:
711 if (recv_string (fd, up.username, sizeof (up.username)) == -1
712 || recv_string (fd, up.password, sizeof (up.password)) == -1)
714 fprintf (stderr, "AUTH-PAM: BACKGROUND: read error on command channel: code=%d, exiting\n",
715 command);
716 goto done;
719 if (DEBUG (verb))
721 #if 0
722 fprintf (stderr, "AUTH-PAM: BACKGROUND: USER/PASS: %s/%s\n",
723 up.username, up.password);
724 #else
725 fprintf (stderr, "AUTH-PAM: BACKGROUND: USER: %s\n", up.username);
726 #endif
729 if (pam_auth (service, &up)) /* Succeeded */
731 if (send_control (fd, RESPONSE_VERIFY_SUCCEEDED) == -1)
733 fprintf (stderr, "AUTH-PAM: BACKGROUND: write error on response socket [2]\n");
734 goto done;
737 else /* Failed */
739 if (send_control (fd, RESPONSE_VERIFY_FAILED) == -1)
741 fprintf (stderr, "AUTH-PAM: BACKGROUND: write error on response socket [3]\n");
742 goto done;
745 break;
747 case COMMAND_EXIT:
748 goto done;
750 case -1:
751 fprintf (stderr, "AUTH-PAM: BACKGROUND: read error on command channel\n");
752 goto done;
754 default:
755 fprintf (stderr, "AUTH-PAM: BACKGROUND: unknown command code: code=%d, exiting\n",
756 command);
757 goto done;
760 done:
762 #if DLOPEN_PAM
763 dlclose_pam ();
764 #endif
765 if (DEBUG (verb))
766 fprintf (stderr, "AUTH-PAM: BACKGROUND: EXIT\n");
768 return;