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-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
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) >= 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 */
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
);
212 * Daemonize if "daemon" env var is true.
213 * Preserve stderr across daemonization if
214 * "daemon_log_redirect" env var is true.
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
);
224 if (log_redirect
&& log_redirect
[0] == '1')
226 if (daemon (0, 0) < 0)
228 fprintf (stderr
, "AUTH-PAM: daemonization failed\n");
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().
251 close_fds_except (int keep
)
255 for (i
= 3; i
<= 100; ++i
)
263 * Usually we ignore signals, because our parent will
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.
282 name_value_match (const char *query
, const char *match
)
284 while (!isalnum (*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
[])
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
));
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");
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
;
337 if ((nv_len
& 1) == 1 || (nv_len
/ 2) > N_NAME_VALUE
)
339 fprintf (stderr
, "AUTH-PAM: bad name/value list length\n");
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
);
358 context
->verb
= atoi (verb_string
);
362 * Make a socket for foreground and background processes
365 if (socketpair (PF_UNIX
, SOCK_DGRAM
, 0, fd
) == -1)
367 fprintf (stderr
, "AUTH-PAM: socketpair call failed\n");
372 * Fork off the privileged process. It will remain privileged
373 * even after the foreground process drops its privileges.
385 context
->background_pid
= pid
;
387 /* close our copy of child's socket */
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
;
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) */
415 /* Daemonize if --daemon option is set. */
419 /* execute the event loop */
420 pam_server (fd
[1], argv
[1], context
->verb
, &name_value_list
);
425 return 0; /* NOTREACHED */
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");
455 const int status
= recv_control (context
->foreground_fd
);
456 if (status
== RESPONSE_VERIFY_SUCCEEDED
)
457 return OPENVPN_PLUGIN_FUNC_SUCCESS
;
459 fprintf (stderr
, "AUTH-PAM: Error receiving auth confirmation from background process\n");
463 return OPENVPN_PLUGIN_FUNC_ERROR
;
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;
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
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
;
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",
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
;
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",
562 if (!strcmp (match_value
, "USERNAME"))
563 return_value
= up
->username
;
564 else if (!strcmp (match_value
, "PASSWORD"))
565 return_value
= up
->password
;
567 return_value
= match_value
;
569 aresp
[i
].resp
= strdup (return_value
);
570 if (aresp
[i
].resp
== NULL
)
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
)
590 case PAM_PROMPT_ECHO_ON
:
591 aresp
[i
].resp
= strdup (up
->username
);
592 if (aresp
[i
].resp
== NULL
)
607 if (ret
== PAM_SUCCESS
)
608 *response_array
= aresp
;
613 * Return 1 if authenticated and 0 if failed.
614 * Called once for every username/password
615 * to be authenticated.
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
;
624 const int name_value_list_provided
= (up
->name_value_list
&& up
->name_value_list
->len
> 0);
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
)
639 /* Output error message if failed */
642 fprintf (stderr
, "AUTH-PAM: BACKGROUND: user '%s' failed to authenticate: %s\n",
644 pam_strerror (pamh
, status
));
648 pam_end (pamh
, status
);
655 * Background process -- runs with privilege.
658 pam_server (int fd
, const char *service
, int verb
, const struct name_value_list
*name_value_list
)
663 static const char pam_so
[] = "libpam.so";
670 fprintf (stderr
, "AUTH-PAM: BACKGROUND: INIT service='%s'\n", service
);
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
);
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");
698 memset (&up
, 0, sizeof (up
));
700 up
.name_value_list
= name_value_list
;
702 /* get a command from foreground process */
703 command
= recv_control (fd
);
706 fprintf (stderr
, "AUTH-PAM: BACKGROUND: received command code: %d\n", command
);
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",
722 fprintf (stderr
, "AUTH-PAM: BACKGROUND: USER/PASS: %s/%s\n",
723 up
.username
, up
.password
);
725 fprintf (stderr
, "AUTH-PAM: BACKGROUND: USER: %s\n", up
.username
);
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");
739 if (send_control (fd
, RESPONSE_VERIFY_FAILED
) == -1)
741 fprintf (stderr
, "AUTH-PAM: BACKGROUND: write error on response socket [3]\n");
751 fprintf (stderr
, "AUTH-PAM: BACKGROUND: read error on command channel\n");
755 fprintf (stderr
, "AUTH-PAM: BACKGROUND: unknown command code: code=%d, exiting\n",
766 fprintf (stderr
, "AUTH-PAM: BACKGROUND: EXIT\n");