OpenVPN
auth-pam.c
Go to the documentation of this file.
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.
7  *
8  * Copyright (C) 2002-2024 OpenVPN Inc <sales@openvpn.net>
9  * Copyright (C) 2016-2024 Selva Nair <selva.nair@gmail.com>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License version 2
13  * as published by the Free Software Foundation.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License along
21  * with this program; if not, write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24 
25 /*
26  * OpenVPN plugin module to do PAM authentication using a split
27  * privilege model.
28  */
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
32 
33 #include <security/pam_appl.h>
34 
35 #ifdef USE_PAM_DLOPEN
36 #include "pamdl.h"
37 #endif
38 
39 #include <stdio.h>
40 #include <string.h>
41 #include <ctype.h>
42 #include <unistd.h>
43 #include <stdlib.h>
44 #include <sys/types.h>
45 #include <sys/socket.h>
46 #include <sys/wait.h>
47 #include <fcntl.h>
48 #include <signal.h>
49 #include <syslog.h>
50 #include <limits.h>
51 #include "utils.h"
52 #include <arpa/inet.h>
53 #include <openvpn-plugin.h>
54 
55 #define DEBUG(verb) ((verb) >= 4)
56 
57 /* Command codes for foreground -> background communication */
58 #define COMMAND_VERIFY 0
59 #define COMMAND_EXIT 1
60 
61 /* Response codes for background -> foreground communication */
62 #define RESPONSE_INIT_SUCCEEDED 10
63 #define RESPONSE_INIT_FAILED 11
64 #define RESPONSE_VERIFY_SUCCEEDED 12
65 #define RESPONSE_VERIFY_FAILED 13
66 #define RESPONSE_DEFER 14
67 
68 /* Pointers to functions exported from openvpn */
69 static plugin_log_t plugin_log = NULL;
72 
73 /* module name for plugin_log() */
74 static char *MODULE = "AUTH-PAM";
75 
76 /*
77  * Plugin state, used by foreground
78  */
80 {
81  /* Foreground's socket to background process */
83 
84  /* Process ID of background process */
86 
87  /* Verbosity level of OpenVPN */
88  int verb;
89 };
90 
91 /*
92  * Name/Value pairs for conversation function.
93  * Special Values:
94  *
95  * "USERNAME" -- substitute client-supplied username
96  * "PASSWORD" -- substitute client-specified password
97  * "COMMONNAME" -- substitute client certificate common name
98  * "OTP" -- substitute static challenge response if available
99  */
100 
101 #define N_NAME_VALUE 16
102 
103 struct name_value {
104  const char *name;
105  const char *value;
106 };
107 
109  int len;
111 };
112 
113 /*
114  * Used to pass the username/password
115  * to the PAM conversation function.
116  */
117 struct user_pass {
118  int verb;
119 
120  char username[128];
121  char password[128];
122  char common_name[128];
123  char response[128];
124  char remote[INET6_ADDRSTRLEN];
125 
127 };
128 
129 /* Background process function */
130 static void pam_server(int fd, const char *service, int verb, const struct name_value_list *name_value_list);
131 
132 
133 /*
134  * Socket read/write functions.
135  */
136 
137 static int
139 {
140  unsigned char c;
141  const ssize_t size = read(fd, &c, sizeof(c));
142  if (size == sizeof(c))
143  {
144  return c;
145  }
146  else
147  {
148  /*fprintf (stderr, "AUTH-PAM: DEBUG recv_control.read=%d\n", (int)size);*/
149  return -1;
150  }
151 }
152 
153 static int
154 send_control(int fd, int code)
155 {
156  unsigned char c = (unsigned char) code;
157  const ssize_t size = write(fd, &c, sizeof(c));
158  if (size == sizeof(c))
159  {
160  return (int) size;
161  }
162  else
163  {
164  return -1;
165  }
166 }
167 
168 static int
169 recv_string(int fd, char *buffer, int len)
170 {
171  if (len > 0)
172  {
173  ssize_t size;
174  memset(buffer, 0, len);
175  size = read(fd, buffer, len);
176  buffer[len-1] = 0;
177  if (size >= 1)
178  {
179  return (int)size;
180  }
181  }
182  return -1;
183 }
184 
185 static int
186 send_string(int fd, const char *string)
187 {
188  const int len = strlen(string) + 1;
189  const ssize_t size = write(fd, string, len);
190  if (size == len)
191  {
192  return (int) size;
193  }
194  else
195  {
196  return -1;
197  }
198 }
199 
200 #ifdef DO_DAEMONIZE
201 
202 /*
203  * Daemonize if "daemon" env var is true.
204  * Preserve stderr across daemonization if
205  * "daemon_log_redirect" env var is true.
206  */
207 static void
208 daemonize(const char *envp[])
209 {
210  const char *daemon_string = get_env("daemon", envp);
211  if (daemon_string && daemon_string[0] == '1')
212  {
213  const char *log_redirect = get_env("daemon_log_redirect", envp);
214  int fd = -1;
215  if (log_redirect && log_redirect[0] == '1')
216  {
217  fd = dup(2);
218  }
219 #if defined(__APPLE__) && defined(__clang__)
220 #pragma clang diagnostic push
221 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
222 #endif
223  if (daemon(0, 0) < 0)
224  {
225  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "daemonization failed");
226  }
227 #if defined(__APPLE__) && defined(__clang__)
228 #pragma clang diagnostic pop
229 #endif
230  else if (fd >= 3)
231  {
232  dup2(fd, 2);
233  close(fd);
234  }
235  }
236 }
237 
238 #endif /* ifdef DO_DAEMONIZE */
239 
240 /*
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().
249  */
250 static void
252 {
253  int i;
254  closelog();
255  for (i = 3; i <= 100; ++i)
256  {
257  if (i != keep)
258  {
259  close(i);
260  }
261  }
262 }
263 
264 /*
265  * Usually we ignore signals, because our parent will
266  * deal with them.
267  */
268 static void
270 {
271  signal(SIGTERM, SIG_DFL);
272 
273  signal(SIGINT, SIG_IGN);
274  signal(SIGHUP, SIG_IGN);
275  signal(SIGUSR1, SIG_IGN);
276  signal(SIGUSR2, SIG_IGN);
277  signal(SIGPIPE, SIG_IGN);
278 }
279 
280 /*
281  * Return 1 if query matches match.
282  */
283 static int
284 name_value_match(const char *query, const char *match)
285 {
286  while (!isalnum(*query))
287  {
288  if (*query == '\0')
289  {
290  return 0;
291  }
292  ++query;
293  }
294  return strncasecmp(match, query, strlen(match)) == 0;
295 }
296 
297 /*
298  * Split and decode up->password in the form SCRV1:base64_pass:base64_response
299  * into pass and response and save in up->password and up->response.
300  * If the password is not in the expected format, input is not changed.
301  */
302 static void
304 {
305  const int skip = strlen("SCRV1:");
306  if (strncmp(up->password, "SCRV1:", skip) != 0)
307  {
308  return;
309  }
310 
311  char *tmp = strdup(up->password);
312  if (!tmp)
313  {
314  plugin_log(PLOG_ERR, MODULE, "out of memory parsing static challenge password");
315  goto out;
316  }
317 
318  char *pass = tmp + skip;
319  char *resp = strchr(pass, ':');
320  if (!resp) /* string not in SCRV1:xx:yy format */
321  {
322  goto out;
323  }
324  *resp++ = '\0';
325 
326  int n = plugin_base64_decode(pass, up->password, sizeof(up->password)-1);
327  if (n >= 0)
328  {
329  up->password[n] = '\0';
330  n = plugin_base64_decode(resp, up->response, sizeof(up->response)-1);
331  if (n >= 0)
332  {
333  up->response[n] = '\0';
334  if (DEBUG(up->verb))
335  {
336  plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: parsed static challenge password");
337  }
338  goto out;
339  }
340  }
341 
342  /* decode error: reinstate original value of up->password and return */
343  plugin_secure_memzero(up->password, sizeof(up->password));
344  plugin_secure_memzero(up->response, sizeof(up->response));
345  strcpy(up->password, tmp); /* tmp is guaranteed to fit in up->password */
346 
347  plugin_log(PLOG_ERR, MODULE, "base64 decode error while parsing static challenge password");
348 
349 out:
350  if (tmp)
351  {
352  plugin_secure_memzero(tmp, strlen(tmp));
353  free(tmp);
354  }
355 }
356 
357 OPENVPN_EXPORT int
358 openvpn_plugin_open_v3(const int v3structver,
359  struct openvpn_plugin_args_open_in const *args,
361 {
362  pid_t pid;
363  int fd[2];
364 
365  struct auth_pam_context *context;
367 
368  const int base_parms = 2;
369 
370  const char **argv = args->argv;
371  const char **envp = args->envp;
372 
373  /* Check API compatibility -- struct version 5 or higher needed */
374  if (v3structver < 5)
375  {
376  fprintf(stderr, "AUTH-PAM: This plugin is incompatible with the running version of OpenVPN\n");
378  }
379 
380  /*
381  * Allocate our context
382  */
383  context = (struct auth_pam_context *) calloc(1, sizeof(struct auth_pam_context));
384  if (!context)
385  {
386  goto error;
387  }
388  context->foreground_fd = -1;
389 
390  /*
391  * Intercept the --auth-user-pass-verify callback.
392  */
394 
395  /* Save global pointers to functions exported from openvpn */
399 
400  /*
401  * Make sure we have two string arguments: the first is the .so name,
402  * the second is the PAM service type.
403  */
404  if (string_array_len(argv) < base_parms)
405  {
406  plugin_log(PLOG_ERR, MODULE, "need PAM service parameter");
407  goto error;
408  }
409 
410  /*
411  * See if we have optional name/value pairs to match against
412  * PAM module queried fields in the conversation function.
413  */
414  name_value_list.len = 0;
415  if (string_array_len(argv) > base_parms)
416  {
417  const int nv_len = string_array_len(argv) - base_parms;
418  int i;
419 
420  if ((nv_len & 1) == 1 || (nv_len / 2) > N_NAME_VALUE)
421  {
422  plugin_log(PLOG_ERR, MODULE, "bad name/value list length");
423  goto error;
424  }
425 
426  name_value_list.len = nv_len / 2;
427  for (i = 0; i < name_value_list.len; ++i)
428  {
429  const int base = base_parms + i * 2;
430  name_value_list.data[i].name = argv[base];
431  name_value_list.data[i].value = argv[base+1];
432  }
433  }
434 
435  /*
436  * Get verbosity level from environment
437  */
438  {
439  const char *verb_string = get_env("verb", envp);
440  if (verb_string)
441  {
442  context->verb = atoi(verb_string);
443  }
444  }
445 
446  /*
447  * Make a socket for foreground and background processes
448  * to communicate.
449  */
450  if (socketpair(PF_UNIX, SOCK_DGRAM, 0, fd) == -1)
451  {
452  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "socketpair call failed");
453  goto error;
454  }
455 
456  /*
457  * Fork off the privileged process. It will remain privileged
458  * even after the foreground process drops its privileges.
459  */
460  pid = fork();
461 
462  if (pid)
463  {
464  int status;
465 
466  /*
467  * Foreground Process
468  */
469 
470  context->background_pid = pid;
471 
472  /* close our copy of child's socket */
473  close(fd[1]);
474 
475  /* don't let future subprocesses inherit child socket */
476  if (fcntl(fd[0], F_SETFD, FD_CLOEXEC) < 0)
477  {
478  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "Set FD_CLOEXEC flag on socket file descriptor failed");
479  }
480 
481  /* wait for background child process to initialize */
482  status = recv_control(fd[0]);
484  {
485  context->foreground_fd = fd[0];
487  plugin_log( PLOG_NOTE, MODULE, "initialization succeeded (fg)" );
489  }
490  }
491  else
492  {
493  /*
494  * Background Process
495  */
496 
497  /* close all parent fds except our socket back to parent */
498  close_fds_except(fd[1]);
499 
500  /* Ignore most signals (the parent will receive them) */
501  set_signals();
502 
503 #ifdef DO_DAEMONIZE
504  /* Daemonize if --daemon option is set. */
505  daemonize(envp);
506 #endif
507 
508  /* execute the event loop */
509  pam_server(fd[1], argv[1], context->verb, &name_value_list);
510 
511  close(fd[1]);
512 
513  exit(0);
514  return 0; /* NOTREACHED */
515  }
516 
517 error:
518  free(context);
520 }
521 
522 OPENVPN_EXPORT int
523 openvpn_plugin_func_v1(openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
524 {
525  struct auth_pam_context *context = (struct auth_pam_context *) handle;
526 
527  if (type == OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY && context->foreground_fd >= 0)
528  {
529  /* get username/password from envp string array */
530  const char *username = get_env("username", envp);
531  const char *password = get_env("password", envp);
532  const char *common_name = get_env("common_name", envp) ? get_env("common_name", envp) : "";
533  const char *remote = get_env("untrusted_ip6", envp);
534 
535  if (remote == NULL)
536  {
537  remote = get_env("untrusted_ip", envp);
538  }
539 
540  if (remote == NULL)
541  {
542  remote = "";
543  }
544 
545  /* should we do deferred auth?
546  * yes, if there is "auth_control_file" and "deferred_auth_pam" env
547  */
548  const char *auth_control_file = get_env("auth_control_file", envp);
549  const char *deferred_auth_pam = get_env("deferred_auth_pam", envp);
550  if (auth_control_file != NULL && deferred_auth_pam != NULL)
551  {
552  if (DEBUG(context->verb))
553  {
554  plugin_log(PLOG_NOTE, MODULE, "do deferred auth '%s'",
555  auth_control_file);
556  }
557  }
558  else
559  {
560  auth_control_file = "";
561  }
562 
563  if (username && strlen(username) > 0 && password)
564  {
565  if (send_control(context->foreground_fd, COMMAND_VERIFY) == -1
566  || send_string(context->foreground_fd, username) == -1
567  || send_string(context->foreground_fd, password) == -1
568  || send_string(context->foreground_fd, common_name) == -1
569  || send_string(context->foreground_fd, auth_control_file) == -1
570  || send_string(context->foreground_fd, remote) == -1)
571  {
572  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "Error sending auth info to background process");
573  }
574  else
575  {
576  const int status = recv_control(context->foreground_fd);
578  {
580  }
581  if (status == RESPONSE_DEFER)
582  {
583  if (DEBUG(context->verb))
584  {
585  plugin_log(PLOG_NOTE, MODULE, "deferred authentication");
586  }
588  }
589  if (status == -1)
590  {
591  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "Error receiving auth confirmation from background process");
592  }
593  }
594  }
595  }
597 }
598 
599 OPENVPN_EXPORT void
601 {
602  struct auth_pam_context *context = (struct auth_pam_context *) handle;
603 
604  if (DEBUG(context->verb))
605  {
606  plugin_log(PLOG_NOTE, MODULE, "close");
607  }
608 
609  if (context->foreground_fd >= 0)
610  {
611  /* tell background process to exit */
612  if (send_control(context->foreground_fd, COMMAND_EXIT) == -1)
613  {
614  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "Error signaling background process to exit");
615  }
616 
617  /* wait for background process to exit */
618  if (context->background_pid > 0)
619  {
620  waitpid(context->background_pid, NULL, 0);
621  }
622 
623  close(context->foreground_fd);
624  context->foreground_fd = -1;
625  }
626 
627  free(context);
628 }
629 
630 OPENVPN_EXPORT void
632 {
633  struct auth_pam_context *context = (struct auth_pam_context *) handle;
634 
635  /* tell background process to exit */
636  if (context && context->foreground_fd >= 0)
637  {
638  send_control(context->foreground_fd, COMMAND_EXIT);
639  close(context->foreground_fd);
640  context->foreground_fd = -1;
641  }
642 }
643 
644 /*
645  * PAM conversation function
646  */
647 static int
648 my_conv(int n, const struct pam_message **msg_array,
649  struct pam_response **response_array, void *appdata_ptr)
650 {
651  const struct user_pass *up = ( const struct user_pass *) appdata_ptr;
652  struct pam_response *aresp;
653  int i;
654  int ret = PAM_SUCCESS;
655 
656  *response_array = NULL;
657 
658  if (n <= 0 || n > PAM_MAX_NUM_MSG)
659  {
660  return (PAM_CONV_ERR);
661  }
662  if ((aresp = calloc(n, sizeof *aresp)) == NULL)
663  {
664  return (PAM_BUF_ERR);
665  }
666 
667  /* loop through each PAM-module query */
668  for (i = 0; i < n; ++i)
669  {
670  const struct pam_message *msg = msg_array[i];
671  aresp[i].resp_retcode = 0;
672  aresp[i].resp = NULL;
673 
674  if (DEBUG(up->verb))
675  {
676  plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: my_conv[%d] query='%s' style=%d",
677  i,
678  msg->msg ? msg->msg : "NULL",
679  msg->msg_style);
680  }
681 
682  if (up->name_value_list && up->name_value_list->len > 0)
683  {
684  /* use name/value list match method */
685  const struct name_value_list *list = up->name_value_list;
686  int j;
687 
688  /* loop through name/value pairs */
689  for (j = 0; j < list->len; ++j)
690  {
691  const char *match_name = list->data[j].name;
692  const char *match_value = list->data[j].value;
693 
694  if (name_value_match(msg->msg, match_name))
695  {
696  /* found name/value match */
697  aresp[i].resp = NULL;
698 
699  if (DEBUG(up->verb))
700  {
701  plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: name match found, query/match-string ['%s', '%s'] = '%s'",
702  msg->msg,
703  match_name,
704  match_value);
705  }
706 
707  if (strstr(match_value, "USERNAME"))
708  {
709  aresp[i].resp = searchandreplace(match_value, "USERNAME", up->username);
710  }
711  else if (strstr(match_value, "PASSWORD"))
712  {
713  aresp[i].resp = searchandreplace(match_value, "PASSWORD", up->password);
714  }
715  else if (strstr(match_value, "COMMONNAME"))
716  {
717  aresp[i].resp = searchandreplace(match_value, "COMMONNAME", up->common_name);
718  }
719  else if (strstr(match_value, "OTP"))
720  {
721  aresp[i].resp = searchandreplace(match_value, "OTP", up->response);
722  }
723  else
724  {
725  aresp[i].resp = strdup(match_value);
726  }
727 
728  if (aresp[i].resp == NULL)
729  {
730  ret = PAM_CONV_ERR;
731  }
732  break;
733  }
734  }
735 
736  if (j == list->len)
737  {
738  ret = PAM_CONV_ERR;
739  }
740  }
741  else
742  {
743  /* use PAM_PROMPT_ECHO_x hints */
744  switch (msg->msg_style)
745  {
746  case PAM_PROMPT_ECHO_OFF:
747  aresp[i].resp = strdup(up->password);
748  if (aresp[i].resp == NULL)
749  {
750  ret = PAM_CONV_ERR;
751  }
752  break;
753 
754  case PAM_PROMPT_ECHO_ON:
755  aresp[i].resp = strdup(up->username);
756  if (aresp[i].resp == NULL)
757  {
758  ret = PAM_CONV_ERR;
759  }
760  break;
761 
762  case PAM_ERROR_MSG:
763  case PAM_TEXT_INFO:
764  break;
765 
766  default:
767  ret = PAM_CONV_ERR;
768  break;
769  }
770  }
771  }
772 
773  if (ret == PAM_SUCCESS)
774  {
775  *response_array = aresp;
776  }
777  else
778  {
779  free(aresp);
780  }
781 
782  return ret;
783 }
784 
785 /*
786  * Return 1 if authenticated and 0 if failed.
787  * Called once for every username/password
788  * to be authenticated.
789  */
790 static int
791 pam_auth(const char *service, const struct user_pass *up)
792 {
793  struct pam_conv conv;
794  pam_handle_t *pamh = NULL;
795  int status = PAM_SUCCESS;
796  int ret = 0;
797  const int name_value_list_provided = (up->name_value_list && up->name_value_list->len > 0);
798 
799  /* Initialize PAM */
800  conv.conv = my_conv;
801  conv.appdata_ptr = (void *)up;
802  status = pam_start(service, name_value_list_provided ? NULL : up->username, &conv, &pamh);
803  if (status == PAM_SUCCESS)
804  {
805  /* Set PAM_RHOST environment variable */
806  if (*(up->remote))
807  {
808  status = pam_set_item(pamh, PAM_RHOST, up->remote);
809  }
810  /* Call PAM to verify username/password */
811  if (status == PAM_SUCCESS)
812  {
813  status = pam_authenticate(pamh, 0);
814  }
815  if (status == PAM_SUCCESS)
816  {
817  status = pam_acct_mgmt(pamh, 0);
818  }
819  if (status == PAM_SUCCESS)
820  {
821  ret = 1;
822  }
823 
824  /* Output error message if failed */
825  if (!ret)
826  {
827  plugin_log(PLOG_ERR, MODULE, "BACKGROUND: user '%s' failed to authenticate: %s",
828  up->username,
829  pam_strerror(pamh, status));
830  }
831 
832  /* Close PAM */
833  pam_end(pamh, status);
834  }
835 
836  return ret;
837 }
838 
839 /*
840  * deferred auth handler
841  * - fork() (twice, to avoid the need for async wait / SIGCHLD handling)
842  * - query PAM stack via pam_auth()
843  * - send response back to OpenVPN via "ac_file_name"
844  *
845  * parent process returns "0" for "fork() and wait() succeeded",
846  * "-1" for "something went wrong, abort program"
847  */
848 
849 static void
850 do_deferred_pam_auth(int fd, const char *ac_file_name,
851  const char *service, const struct user_pass *up)
852 {
853  if (send_control(fd, RESPONSE_DEFER) == -1)
854  {
855  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: write error on response socket [4]");
856  return;
857  }
858 
859  /* double forking so we do not need to wait() for async auth kids */
860  pid_t p1 = fork();
861 
862  if (p1 < 0)
863  {
864  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: fork(1) failed");
865  return;
866  }
867  if (p1 != 0) /* parent */
868  {
869  waitpid(p1, NULL, 0);
870  return; /* parent's job succeeded */
871  }
872 
873  /* child */
874  close(fd); /* socketpair no longer needed */
875 
876  pid_t p2 = fork();
877  if (p2 < 0)
878  {
879  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: fork(2) failed");
880  exit(1);
881  }
882 
883  if (p2 != 0) /* new parent: exit right away */
884  {
885  exit(0);
886  }
887 
888  /* grandchild */
889  plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: deferred auth for '%s', pid=%d",
890  up->username, (int) getpid() );
891 
892  /* the rest is very simple: do PAM, write status byte to file, done */
893  int ac_fd = open( ac_file_name, O_WRONLY );
894  if (ac_fd < 0)
895  {
896  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "cannot open '%s' for writing",
897  ac_file_name );
898  exit(1);
899  }
900  int pam_success = pam_auth(service, up);
901 
902  if (write( ac_fd, pam_success ? "1" : "0", 1 ) != 1)
903  {
904  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "cannot write to '%s'",
905  ac_file_name );
906  }
907  close(ac_fd);
908  plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: %s: deferred auth: PAM %s",
909  up->username, pam_success ? "succeeded" : "rejected" );
910  exit(0);
911 }
912 
913 /*
914  * Background process -- runs with privilege.
915  */
916 static void
917 pam_server(int fd, const char *service, int verb, const struct name_value_list *name_value_list)
918 {
919  struct user_pass up;
920  char ac_file_name[PATH_MAX];
921  int command;
922 #ifdef USE_PAM_DLOPEN
923  static const char pam_so[] = "libpam.so";
924 #endif
925 
926  /*
927  * Do initialization
928  */
929  if (DEBUG(verb))
930  {
931  plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: INIT service='%s'", service);
932  }
933 
934 #ifdef USE_PAM_DLOPEN
935  /*
936  * Load PAM shared object
937  */
938  if (!dlopen_pam(pam_so))
939  {
940  plugin_log(PLOG_ERR, MODULE, "BACKGROUND: could not load PAM lib %s: %s", pam_so, dlerror());
942  goto done;
943  }
944 #endif
945 
946  /*
947  * Tell foreground that we initialized successfully
948  */
949  if (send_control(fd, RESPONSE_INIT_SUCCEEDED) == -1)
950  {
951  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: write error on response socket [1]");
952  goto done;
953  }
954 
955  plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: initialization succeeded");
956 
957  /*
958  * Event loop
959  */
960  while (1)
961  {
962  memset(&up, 0, sizeof(up));
963  up.verb = verb;
965 
966  /* get a command from foreground process */
967  command = recv_control(fd);
968 
969  if (DEBUG(verb))
970  {
971  plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: received command code: %d", command);
972  }
973 
974  switch (command)
975  {
976  case COMMAND_VERIFY:
977  if (recv_string(fd, up.username, sizeof(up.username)) == -1
978  || recv_string(fd, up.password, sizeof(up.password)) == -1
979  || recv_string(fd, up.common_name, sizeof(up.common_name)) == -1
980  || recv_string(fd, ac_file_name, sizeof(ac_file_name)) == -1
981  || recv_string(fd, up.remote, sizeof(up.remote)) == -1)
982  {
983  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: read error on command channel: code=%d, exiting",
984  command);
985  goto done;
986  }
987 
988  if (DEBUG(verb))
989  {
990 #if 0
991  plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: USER/PASS: %s/%s",
992  up.username, up.password);
993 #else
994  plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: USER: %s", up.username);
995  plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: REMOTE: %s", up.remote);
996 #endif
997  }
998 
999  /* If password is of the form SCRV1:base64:base64 split it up */
1000  split_scrv1_password(&up);
1001 
1002  /* client wants deferred auth
1003  */
1004  if (strlen(ac_file_name) > 0)
1005  {
1006  do_deferred_pam_auth(fd, ac_file_name, service, &up);
1007  break;
1008  }
1009 
1010 
1011  /* non-deferred auth: wait for pam result and send
1012  * result back via control socketpair
1013  */
1014  if (pam_auth(service, &up)) /* Succeeded */
1015  {
1016  if (send_control(fd, RESPONSE_VERIFY_SUCCEEDED) == -1)
1017  {
1018  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: write error on response socket [2]");
1019  goto done;
1020  }
1021  }
1022  else /* Failed */
1023  {
1024  if (send_control(fd, RESPONSE_VERIFY_FAILED) == -1)
1025  {
1026  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: write error on response socket [3]");
1027  goto done;
1028  }
1029  }
1030  plugin_secure_memzero(up.password, sizeof(up.password));
1031  break;
1032 
1033  case COMMAND_EXIT:
1034  goto done;
1035 
1036  case -1:
1037  plugin_log(PLOG_ERR|PLOG_ERRNO, MODULE, "BACKGROUND: read error on command channel");
1038  goto done;
1039 
1040  default:
1041  plugin_log(PLOG_ERR, MODULE, "BACKGROUND: unknown command code: code=%d, exiting",
1042  command);
1043  goto done;
1044  }
1045  plugin_secure_memzero(up.response, sizeof(up.response));
1046  }
1047 done:
1048  plugin_secure_memzero(up.password, sizeof(up.password));
1049  plugin_secure_memzero(up.response, sizeof(up.response));
1050 #ifdef USE_PAM_DLOPEN
1051  dlclose_pam();
1052 #endif
1053  if (DEBUG(verb))
1054  {
1055  plugin_log(PLOG_NOTE, MODULE, "BACKGROUND: EXIT");
1056  }
1057 
1058  return;
1059 }
openvpn_plugin_callbacks::plugin_base64_decode
plugin_base64_decode_t plugin_base64_decode
Definition: openvpn-plugin.h:323
PLOG_NOTE
@ PLOG_NOTE
Definition: openvpn-plugin.h:235
auth_pam_context::foreground_fd
int foreground_fd
Definition: auth-pam.c:82
openvpn_plugin_close_v1
OPENVPN_EXPORT void openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
This cleans up the last part of the plug-in, allows it to shut down cleanly and release the plug-in g...
Definition: auth-pam.c:600
string_array_len
int string_array_len(const char **array)
Definition: buffer.c:721
openvpn_plugin_callbacks::plugin_secure_memzero
plugin_secure_memzero_t plugin_secure_memzero
Definition: openvpn-plugin.h:321
openvpn_plugin_abort_v1
OPENVPN_EXPORT void openvpn_plugin_abort_v1(openvpn_plugin_handle_t handle)
Definition: auth-pam.c:631
my_conv
static int my_conv(int n, const struct pam_message **msg_array, struct pam_response **response_array, void *appdata_ptr)
Definition: auth-pam.c:648
do_deferred_pam_auth
static void do_deferred_pam_auth(int fd, const char *ac_file_name, const char *service, const struct user_pass *up)
Definition: auth-pam.c:850
pamdl.h
argv
Definition: argv.h:35
COMMAND_VERIFY
#define COMMAND_VERIFY
Definition: auth-pam.c:58
name_value_list::len
int len
Definition: auth-pam.c:109
get_env
static const char * get_env(const char *name, const char *envp[])
Definition: sample-client-connect.c:85
openvpn_plugin_args_open_in
Arguments used to transport variables to the plug-in.
Definition: openvpn-plugin.h:359
context
Contains all state information for one tunnel.
Definition: openvpn.h:476
user_pass::username
char username[USER_PASS_LEN]
Definition: misc.h:71
daemonize
static void daemonize(const char *envp[])
Definition: down-root.c:165
OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY
#define OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY
Definition: openvpn-plugin.h:122
openvpn_plugin_args_open_return::type_mask
int type_mask
Definition: openvpn-plugin.h:396
name_value::name
const char * name
Definition: auth-pam.c:104
name_value_match
static int name_value_match(const char *query, const char *match)
Definition: auth-pam.c:284
COMMAND_EXIT
#define COMMAND_EXIT
Definition: auth-pam.c:59
plugin_log_t
void(* plugin_log_t)(openvpn_plugin_log_flags_t flags, const char *plugin_name, const char *format,...) _ovpn_chk_fmt(3
Definition: openvpn-plugin.h:254
OPENVPN_PLUGIN_MASK
#define OPENVPN_PLUGIN_MASK(x)
Definition: openvpn-plugin.h:137
DEBUG
#define DEBUG(verb)
Definition: auth-pam.c:55
MODULE
static char * MODULE
Definition: auth-pam.c:74
RESPONSE_DEFER
#define RESPONSE_DEFER
Definition: auth-pam.c:66
plugin_base64_decode
static plugin_base64_decode_t plugin_base64_decode
Definition: auth-pam.c:71
auth_pam_context
Definition: auth-pam.c:79
read
@ read
Definition: interactive.c:218
RESPONSE_INIT_FAILED
#define RESPONSE_INIT_FAILED
Definition: auth-pam.c:63
OPENVPN_PLUGIN_FUNC_ERROR
#define OPENVPN_PLUGIN_FUNC_ERROR
Definition: openvpn-plugin.h:149
name_value_list
Definition: auth-pam.c:108
PLOG_ERRNO
@ PLOG_ERRNO
Definition: openvpn-plugin.h:238
name_value::value
const char * value
Definition: auth-pam.c:105
write
@ write
Definition: interactive.c:219
plugin_log
static plugin_log_t plugin_log
Definition: auth-pam.c:69
plugin_base64_decode_t
int(* plugin_base64_decode_t)(const char *str, void *data, int size)
Export of openvpn_base64_decode() to be used inside plug-ins.
Definition: openvpn-plugin.h:298
utils.h
OPENVPN_EXPORT
#define OPENVPN_EXPORT
Definition: openvpn-plugin.h:156
pam_auth
static int pam_auth(const char *service, const struct user_pass *up)
Definition: auth-pam.c:791
name_value
Definition: auth-pam.c:103
OPENVPN_PLUGIN_FUNC_DEFERRED
#define OPENVPN_PLUGIN_FUNC_DEFERRED
Definition: openvpn-plugin.h:150
name_value_list::data
struct name_value data[N_NAME_VALUE]
Definition: auth-pam.c:110
plugin_secure_memzero
static plugin_secure_memzero_t plugin_secure_memzero
Definition: auth-pam.c:70
user_pass::verb
int verb
Definition: auth-pam.c:118
buffer
Wrapper structure for dynamically allocated memory.
Definition: buffer.h:60
PLOG_ERR
@ PLOG_ERR
Definition: openvpn-plugin.h:233
recv_control
static int recv_control(int fd)
Definition: auth-pam.c:138
auth_pam_context::verb
int verb
Definition: auth-pam.c:88
recv_string
static int recv_string(int fd, char *buffer, int len)
Definition: auth-pam.c:169
user_pass::common_name
char common_name[128]
Definition: auth-pam.c:122
searchandreplace
char * searchandreplace(const char *tosearch, const char *searchfor, const char *replacewith)
Read 'tosearch', replace all occurrences of 'searchfor' with 'replacewith' and return a pointer to th...
Definition: utils.c:43
send_control
static int send_control(int fd, int code)
Definition: auth-pam.c:154
RESPONSE_VERIFY_FAILED
#define RESPONSE_VERIFY_FAILED
Definition: auth-pam.c:65
daemon
int daemon(int nochdir, int noclose)
Definition: compat-daemon.c:51
openvpn_plugin_open_v3
OPENVPN_EXPORT int openvpn_plugin_open_v3(const int v3structver, struct openvpn_plugin_args_open_in const *args, struct openvpn_plugin_args_open_return *ret)
This function is called when OpenVPN loads the plug-in.
Definition: auth-pam.c:358
openvpn_plugin_args_open_in::envp
const char **const envp
Definition: openvpn-plugin.h:363
plugin_secure_memzero_t
void(*) typedef void(*) typedef void(* plugin_secure_memzero_t)(void *data, size_t len)
Export of secure_memzero() to be used inside plug-ins.
Definition: openvpn-plugin.h:271
RESPONSE_VERIFY_SUCCEEDED
#define RESPONSE_VERIFY_SUCCEEDED
Definition: auth-pam.c:64
openvpn_plugin_args_open_in::argv
const char **const argv
Definition: openvpn-plugin.h:362
user_pass::name_value_list
const struct name_value_list * name_value_list
Definition: auth-pam.c:126
pam_server
static void pam_server(int fd, const char *service, int verb, const struct name_value_list *name_value_list)
Definition: auth-pam.c:917
send_string
static int send_string(int fd, const char *string)
Definition: auth-pam.c:186
service
static SERVICE_STATUS_HANDLE service
Definition: interactive.c:52
openvpn_plugin_args_open_return
Arguments used to transport variables from the plug-in back to the OpenVPN process.
Definition: openvpn-plugin.h:394
openvpn_plugin_args_open_return::handle
openvpn_plugin_handle_t handle
Definition: openvpn-plugin.h:397
status
static SERVICE_STATUS status
Definition: interactive.c:53
close_fds_except
static void close_fds_except(int keep)
Definition: auth-pam.c:251
OPENVPN_PLUGIN_FUNC_SUCCESS
#define OPENVPN_PLUGIN_FUNC_SUCCESS
Definition: openvpn-plugin.h:148
config.h
user_pass::password
char password[USER_PASS_LEN]
Definition: misc.h:72
user_pass::response
char response[128]
Definition: auth-pam.c:123
user_pass
Definition: misc.h:56
auth_pam_context::background_pid
pid_t background_pid
Definition: auth-pam.c:85
openvpn-plugin.h
openvpn_plugin_handle_t
void * openvpn_plugin_handle_t
Definition: openvpn-plugin.h:143
msg
#define msg(flags,...)
Definition: error.h:144
N_NAME_VALUE
#define N_NAME_VALUE
Definition: auth-pam.c:101
user_pass::remote
char remote[INET6_ADDRSTRLEN]
Definition: auth-pam.c:124
openvpn_plugin_args_open_in::callbacks
struct openvpn_plugin_callbacks * callbacks
Definition: openvpn-plugin.h:364
set_signals
static void set_signals(void)
Definition: auth-pam.c:269
openvpn_plugin_callbacks::plugin_log
plugin_log_t plugin_log
Definition: openvpn-plugin.h:319
split_scrv1_password
static void split_scrv1_password(struct user_pass *up)
Definition: auth-pam.c:303
openvpn_plugin_func_v1
OPENVPN_EXPORT int openvpn_plugin_func_v1(openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
This function is called by OpenVPN each time the OpenVPN reaches a point where plug-in calls should h...
Definition: auth-pam.c:523
RESPONSE_INIT_SUCCEEDED
#define RESPONSE_INIT_SUCCEEDED
Definition: auth-pam.c:62