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
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
34 #include <security/pam_appl.h>
42 #include <sys/types.h>
43 #include <sys/socket.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 */
71 /* Process ID of background process */
74 /* Verbosity level of OpenVPN */
79 * Name/Value pairs for conversation function.
82 * "USERNAME" -- substitute client-supplied username
83 * "PASSWORD" -- substitute client-specified password
86 #define N_NAME_VALUE 16
93 struct name_value_list
{
95 struct name_value data
[N_NAME_VALUE
];
99 * Used to pass the username/password
100 * to the PAM conversation function.
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.
120 get_env (const char *name
, const char *envp
[])
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
;
140 * Return the length of a string array
143 string_array_len (const char *array
[])
155 * Socket read/write functions.
159 recv_control (int fd
)
162 const ssize_t size
= read (fd
, &c
, sizeof (c
));
163 if (size
== sizeof (c
))
167 /*fprintf (stderr, "AUTH-PAM: DEBUG recv_control.read=%d\n", (int)size);*/
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
))
184 recv_string (int fd
, char *buffer
, int len
)
189 memset (buffer
, 0, len
);
190 size
= read (fd
, buffer
, len
);
199 send_string (int fd
, const char *string
)
201 const int len
= strlen (string
) + 1;
202 const ssize_t size
= write (fd
, string
, len
);
210 * Daemonize if "daemon" env var is true.
211 * Preserve stderr across daemonization if
212 * "daemon_log_redirect" env var is true.
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
);
222 if (log_redirect
&& log_redirect
[0] == '1')
224 if (daemon (0, 0) < 0)
226 fprintf (stderr
, "AUTH-PAM: daemonization failed\n");
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().
247 close_fds_except (int keep
)
251 for (i
= 3; i
<= 100; ++i
)
259 * Usually we ignore signals, because our parent will
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.
278 name_value_match (const char *query
, const char *match
)
280 while (!isalnum (*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
[])
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");
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
;
331 if ((nv_len
& 1) == 1 || (nv_len
/ 2) > N_NAME_VALUE
)
333 fprintf (stderr
, "AUTH-PAM: bad name/value list length\n");
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
);
352 context
->verb
= atoi (verb_string
);
356 * Make a socket for foreground and background processes
359 if (socketpair (PF_UNIX
, SOCK_DGRAM
, 0, fd
) == -1)
361 fprintf (stderr
, "AUTH-PAM: socketpair call failed\n");
366 * Fork off the privileged process. It will remain privileged
367 * even after the foreground process drops its privileges.
379 context
->background_pid
= pid
;
381 /* close our copy of child's socket */
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
;
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) */
408 /* Daemonize if --daemon option is set. */
411 /* execute the event loop */
412 pam_server (fd
[1], argv
[1], context
->verb
, &name_value_list
);
417 return 0; /* NOTREACHED */
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");
447 const int status
= recv_control (context
->foreground_fd
);
448 if (status
== RESPONSE_VERIFY_SUCCEEDED
)
449 return OPENVPN_PLUGIN_FUNC_SUCCESS
;
451 fprintf (stderr
, "AUTH-PAM: Error receiving auth confirmation from background process\n");
455 return OPENVPN_PLUGIN_FUNC_ERROR
;
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;
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
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
;
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",
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
;
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",
554 if (!strcmp (match_value
, "USERNAME"))
555 return_value
= up
->username
;
556 else if (!strcmp (match_value
, "PASSWORD"))
557 return_value
= up
->password
;
559 return_value
= match_value
;
561 aresp
[i
].resp
= strdup (return_value
);
562 if (aresp
[i
].resp
== NULL
)
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
)
582 case PAM_PROMPT_ECHO_ON
:
583 aresp
[i
].resp
= strdup (up
->username
);
584 if (aresp
[i
].resp
== NULL
)
599 if (ret
== PAM_SUCCESS
)
600 *response_array
= aresp
;
605 * Return 1 if authenticated and 0 if failed.
606 * Called once for every username/password
607 * to be authenticated.
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
;
616 const int name_value_list_provided
= (up
->name_value_list
&& up
->name_value_list
->len
> 0);
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
)
631 /* Output error message if failed */
634 fprintf (stderr
, "AUTH-PAM: BACKGROUND: user '%s' failed to authenticate: %s\n",
636 pam_strerror (pamh
, status
));
640 pam_end (pamh
, status
);
647 * Background process -- runs with privilege.
650 pam_server (int fd
, const char *service
, int verb
, const struct name_value_list
*name_value_list
)
655 static const char pam_so
[] = "libpam.so";
662 fprintf (stderr
, "AUTH-PAM: BACKGROUND: INIT service='%s'\n", service
);
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
);
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");
690 memset (&up
, 0, sizeof (up
));
692 up
.name_value_list
= name_value_list
;
694 /* get a command from foreground process */
695 command
= recv_control (fd
);
698 fprintf (stderr
, "AUTH-PAM: BACKGROUND: received command code: %d\n", command
);
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",
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");
725 if (send_control (fd
, RESPONSE_VERIFY_FAILED
) == -1)
727 fprintf (stderr
, "AUTH-PAM: BACKGROUND: write error on response socket [3]\n");
737 fprintf (stderr
, "AUTH-PAM: BACKGROUND: read error on command channel\n");
741 fprintf (stderr
, "AUTH-PAM: BACKGROUND: unknown command code: code=%d, exiting\n",
752 fprintf (stderr
, "AUTH-PAM: BACKGROUND: EXIT\n");