big svn cleanup
[anytun.git] / src / openvpn / plugin / auth-pam / auth-pam.c
blob5047b34a4a22437028a24b00f636164cc57af048
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 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) >= 7)
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;
210 * Daemonize if "daemon" env var is true.
211 * Preserve stderr across daemonization if
212 * "daemon_log_redirect" env var is true.
214 static void
215 daemonize (const char *envp[])
217 const char *daemon_string = get_env ("daemon", envp);
218 if (daemon_string && daemon_string[0] == '1')
220 const char *log_redirect = get_env ("daemon_log_redirect", envp);
221 int fd = -1;
222 if (log_redirect && log_redirect[0] == '1')
223 fd = dup (2);
224 if (daemon (0, 0) < 0)
226 fprintf (stderr, "AUTH-PAM: daemonization failed\n");
228 else if (fd >= 3)
230 dup2 (fd, 2);
231 close (fd);
237 * Close most of parent's fds.
238 * Keep stdin/stdout/stderr, plus one
239 * other fd which is presumed to be
240 * our pipe back to parent.
241 * Admittedly, a bit of a kludge,
242 * but posix doesn't give us a kind
243 * of FD_CLOEXEC which will stop
244 * fds from crossing a fork().
246 static void
247 close_fds_except (int keep)
249 int i;
250 closelog ();
251 for (i = 3; i <= 100; ++i)
253 if (i != keep)
254 close (i);
259 * Usually we ignore signals, because our parent will
260 * deal with them.
262 static void
263 set_signals (void)
265 signal (SIGTERM, SIG_DFL);
267 signal (SIGINT, SIG_IGN);
268 signal (SIGHUP, SIG_IGN);
269 signal (SIGUSR1, SIG_IGN);
270 signal (SIGUSR2, SIG_IGN);
271 signal (SIGPIPE, SIG_IGN);
275 * Return 1 if query matches match.
277 static int
278 name_value_match (const char *query, const char *match)
280 while (!isalnum (*query))
282 if (*query == '\0')
283 return 0;
284 ++query;
286 return strncasecmp (match, query, strlen (match)) == 0;
289 OPENVPN_EXPORT openvpn_plugin_handle_t
290 openvpn_plugin_open_v1 (unsigned int *type_mask, const char *argv[], const char *envp[])
292 pid_t pid;
293 int fd[2];
295 struct auth_pam_context *context;
296 struct name_value_list name_value_list;
298 const int base_parms = 2;
301 * Allocate our context
303 context = (struct auth_pam_context *) calloc (1, sizeof (struct auth_pam_context));
304 context->foreground_fd = -1;
307 * Intercept the --auth-user-pass-verify callback.
309 *type_mask = OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY);
312 * Make sure we have two string arguments: the first is the .so name,
313 * the second is the PAM service type.
315 if (string_array_len (argv) < base_parms)
317 fprintf (stderr, "AUTH-PAM: need PAM service parameter\n");
318 goto error;
322 * See if we have optional name/value pairs to match against
323 * PAM module queried fields in the conversation function.
325 name_value_list.len = 0;
326 if (string_array_len (argv) > base_parms)
328 const int nv_len = string_array_len (argv) - base_parms;
329 int i;
331 if ((nv_len & 1) == 1 || (nv_len / 2) > N_NAME_VALUE)
333 fprintf (stderr, "AUTH-PAM: bad name/value list length\n");
334 goto error;
337 name_value_list.len = nv_len / 2;
338 for (i = 0; i < name_value_list.len; ++i)
340 const int base = base_parms + i * 2;
341 name_value_list.data[i].name = argv[base];
342 name_value_list.data[i].value = argv[base+1];
347 * Get verbosity level from environment
350 const char *verb_string = get_env ("verb", envp);
351 if (verb_string)
352 context->verb = atoi (verb_string);
356 * Make a socket for foreground and background processes
357 * to communicate.
359 if (socketpair (PF_UNIX, SOCK_DGRAM, 0, fd) == -1)
361 fprintf (stderr, "AUTH-PAM: socketpair call failed\n");
362 goto error;
366 * Fork off the privileged process. It will remain privileged
367 * even after the foreground process drops its privileges.
369 pid = fork ();
371 if (pid)
373 int status;
376 * Foreground Process
379 context->background_pid = pid;
381 /* close our copy of child's socket */
382 close (fd[1]);
384 /* don't let future subprocesses inherit child socket */
385 if (fcntl (fd[0], F_SETFD, FD_CLOEXEC) < 0)
386 fprintf (stderr, "AUTH-PAM: Set FD_CLOEXEC flag on socket file descriptor failed\n");
388 /* wait for background child process to initialize */
389 status = recv_control (fd[0]);
390 if (status == RESPONSE_INIT_SUCCEEDED)
392 context->foreground_fd = fd[0];
393 return (openvpn_plugin_handle_t) context;
396 else
399 * Background Process
402 /* close all parent fds except our socket back to parent */
403 close_fds_except (fd[1]);
405 /* Ignore most signals (the parent will receive them) */
406 set_signals ();
408 /* Daemonize if --daemon option is set. */
409 daemonize (envp);
411 /* execute the event loop */
412 pam_server (fd[1], argv[1], context->verb, &name_value_list);
414 close (fd[1]);
416 exit (0);
417 return 0; /* NOTREACHED */
420 error:
421 if (context)
422 free (context);
423 return NULL;
426 OPENVPN_EXPORT int
427 openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
429 struct auth_pam_context *context = (struct auth_pam_context *) handle;
431 if (type == OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY && context->foreground_fd >= 0)
433 /* get username/password from envp string array */
434 const char *username = get_env ("username", envp);
435 const char *password = get_env ("password", envp);
437 if (username && strlen (username) > 0 && password)
439 if (send_control (context->foreground_fd, COMMAND_VERIFY) == -1
440 || send_string (context->foreground_fd, username) == -1
441 || send_string (context->foreground_fd, password) == -1)
443 fprintf (stderr, "AUTH-PAM: Error sending auth info to background process\n");
445 else
447 const int status = recv_control (context->foreground_fd);
448 if (status == RESPONSE_VERIFY_SUCCEEDED)
449 return OPENVPN_PLUGIN_FUNC_SUCCESS;
450 if (status == -1)
451 fprintf (stderr, "AUTH-PAM: Error receiving auth confirmation from background process\n");
455 return OPENVPN_PLUGIN_FUNC_ERROR;
458 OPENVPN_EXPORT void
459 openvpn_plugin_close_v1 (openvpn_plugin_handle_t handle)
461 struct auth_pam_context *context = (struct auth_pam_context *) handle;
463 if (DEBUG (context->verb))
464 fprintf (stderr, "AUTH-PAM: close\n");
466 if (context->foreground_fd >= 0)
468 /* tell background process to exit */
469 if (send_control (context->foreground_fd, COMMAND_EXIT) == -1)
470 fprintf (stderr, "AUTH-PAM: Error signaling background process to exit\n");
472 /* wait for background process to exit */
473 if (context->background_pid > 0)
474 waitpid (context->background_pid, NULL, 0);
476 close (context->foreground_fd);
477 context->foreground_fd = -1;
480 free (context);
483 OPENVPN_EXPORT void
484 openvpn_plugin_abort_v1 (openvpn_plugin_handle_t handle)
486 struct auth_pam_context *context = (struct auth_pam_context *) handle;
488 /* tell background process to exit */
489 if (context->foreground_fd >= 0)
491 send_control (context->foreground_fd, COMMAND_EXIT);
492 close (context->foreground_fd);
493 context->foreground_fd = -1;
498 * PAM conversation function
500 static int
501 my_conv (int n, const struct pam_message **msg_array,
502 struct pam_response **response_array, void *appdata_ptr)
504 const struct user_pass *up = ( const struct user_pass *) appdata_ptr;
505 struct pam_response *aresp;
506 int i;
507 int ret = PAM_SUCCESS;
509 *response_array = NULL;
511 if (n <= 0 || n > PAM_MAX_NUM_MSG)
512 return (PAM_CONV_ERR);
513 if ((aresp = calloc (n, sizeof *aresp)) == NULL)
514 return (PAM_BUF_ERR);
516 /* loop through each PAM-module query */
517 for (i = 0; i < n; ++i)
519 const struct pam_message *msg = msg_array[i];
520 aresp[i].resp_retcode = 0;
521 aresp[i].resp = NULL;
523 if (DEBUG (up->verb))
525 fprintf (stderr, "AUTH-PAM: BACKGROUND: my_conv[%d] query='%s' style=%d\n",
527 msg->msg ? msg->msg : "NULL",
528 msg->msg_style);
531 if (up->name_value_list && up->name_value_list->len > 0)
533 /* use name/value list match method */
534 const struct name_value_list *list = up->name_value_list;
535 int j;
537 /* loop through name/value pairs */
538 for (j = 0; j < list->len; ++j)
540 const char *match_name = list->data[j].name;
541 const char *match_value = list->data[j].value;
543 if (name_value_match (msg->msg, match_name))
545 /* found name/value match */
546 const char *return_value = NULL;
548 if (DEBUG (up->verb))
549 fprintf (stderr, "AUTH-PAM: BACKGROUND: name match found, query/match-string ['%s', '%s'] = '%s'\n",
550 msg->msg,
551 match_name,
552 match_value);
554 if (!strcmp (match_value, "USERNAME"))
555 return_value = up->username;
556 else if (!strcmp (match_value, "PASSWORD"))
557 return_value = up->password;
558 else
559 return_value = match_value;
561 aresp[i].resp = strdup (return_value);
562 if (aresp[i].resp == NULL)
563 ret = PAM_CONV_ERR;
564 break;
568 if (j == list->len)
569 ret = PAM_CONV_ERR;
571 else
573 /* use PAM_PROMPT_ECHO_x hints */
574 switch (msg->msg_style)
576 case PAM_PROMPT_ECHO_OFF:
577 aresp[i].resp = strdup (up->password);
578 if (aresp[i].resp == NULL)
579 ret = PAM_CONV_ERR;
580 break;
582 case PAM_PROMPT_ECHO_ON:
583 aresp[i].resp = strdup (up->username);
584 if (aresp[i].resp == NULL)
585 ret = PAM_CONV_ERR;
586 break;
588 case PAM_ERROR_MSG:
589 case PAM_TEXT_INFO:
590 break;
592 default:
593 ret = PAM_CONV_ERR;
594 break;
599 if (ret == PAM_SUCCESS)
600 *response_array = aresp;
601 return ret;
605 * Return 1 if authenticated and 0 if failed.
606 * Called once for every username/password
607 * to be authenticated.
609 static int
610 pam_auth (const char *service, const struct user_pass *up)
612 struct pam_conv conv;
613 pam_handle_t *pamh = NULL;
614 int status = PAM_SUCCESS;
615 int ret = 0;
616 const int name_value_list_provided = (up->name_value_list && up->name_value_list->len > 0);
618 /* Initialize PAM */
619 conv.conv = my_conv;
620 conv.appdata_ptr = (void *)up;
621 status = pam_start (service, name_value_list_provided ? NULL : up->username, &conv, &pamh);
622 if (status == PAM_SUCCESS)
624 /* Call PAM to verify username/password */
625 status = pam_authenticate(pamh, 0);
626 if (status == PAM_SUCCESS)
627 status = pam_acct_mgmt (pamh, 0);
628 if (status == PAM_SUCCESS)
629 ret = 1;
631 /* Output error message if failed */
632 if (!ret)
634 fprintf (stderr, "AUTH-PAM: BACKGROUND: user '%s' failed to authenticate: %s\n",
635 up->username,
636 pam_strerror (pamh, status));
639 /* Close PAM */
640 pam_end (pamh, status);
643 return ret;
647 * Background process -- runs with privilege.
649 static void
650 pam_server (int fd, const char *service, int verb, const struct name_value_list *name_value_list)
652 struct user_pass up;
653 int command;
654 #if DLOPEN_PAM
655 static const char pam_so[] = "libpam.so";
656 #endif
659 * Do initialization
661 if (DEBUG (verb))
662 fprintf (stderr, "AUTH-PAM: BACKGROUND: INIT service='%s'\n", service);
664 #if DLOPEN_PAM
666 * Load PAM shared object
668 if (!dlopen_pam (pam_so))
670 fprintf (stderr, "AUTH-PAM: BACKGROUND: could not load PAM lib %s: %s\n", pam_so, dlerror());
671 send_control (fd, RESPONSE_INIT_FAILED);
672 goto done;
674 #endif
677 * Tell foreground that we initialized successfully
679 if (send_control (fd, RESPONSE_INIT_SUCCEEDED) == -1)
681 fprintf (stderr, "AUTH-PAM: BACKGROUND: write error on response socket [1]\n");
682 goto done;
686 * Event loop
688 while (1)
690 memset (&up, 0, sizeof (up));
691 up.verb = verb;
692 up.name_value_list = name_value_list;
694 /* get a command from foreground process */
695 command = recv_control (fd);
697 if (DEBUG (verb))
698 fprintf (stderr, "AUTH-PAM: BACKGROUND: received command code: %d\n", command);
700 switch (command)
702 case COMMAND_VERIFY:
703 if (recv_string (fd, up.username, sizeof (up.username)) == -1
704 || recv_string (fd, up.password, sizeof (up.password)) == -1)
706 fprintf (stderr, "AUTH-PAM: BACKGROUND: read error on command channel: code=%d, exiting\n",
707 command);
708 goto done;
711 if (DEBUG (verb))
712 fprintf (stderr, "AUTH-PAM: BACKGROUND: USER/PASS: %s/%s\n",
713 up.username, up.password);
715 if (pam_auth (service, &up)) /* Succeeded */
717 if (send_control (fd, RESPONSE_VERIFY_SUCCEEDED) == -1)
719 fprintf (stderr, "AUTH-PAM: BACKGROUND: write error on response socket [2]\n");
720 goto done;
723 else /* Failed */
725 if (send_control (fd, RESPONSE_VERIFY_FAILED) == -1)
727 fprintf (stderr, "AUTH-PAM: BACKGROUND: write error on response socket [3]\n");
728 goto done;
731 break;
733 case COMMAND_EXIT:
734 goto done;
736 case -1:
737 fprintf (stderr, "AUTH-PAM: BACKGROUND: read error on command channel\n");
738 goto done;
740 default:
741 fprintf (stderr, "AUTH-PAM: BACKGROUND: unknown command code: code=%d, exiting\n",
742 command);
743 goto done;
746 done:
748 #if DLOPEN_PAM
749 dlclose_pam ();
750 #endif
751 if (DEBUG (verb))
752 fprintf (stderr, "AUTH-PAM: BACKGROUND: EXIT\n");
754 return;