• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /asuswrt-rt-n18u-9.0.0.4.380.2695/release/src-rt/router/openvpn/src/plugins/auth-pam/
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-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
9 *
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.
13 *
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.
18 *
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
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
51#include <openvpn-plugin.h>
52
53#define DEBUG(verb) ((verb) >= 4)
54
55/* Command codes for foreground -> background communication */
56#define COMMAND_VERIFY 0
57#define COMMAND_EXIT   1
58
59/* Response codes for background -> foreground communication */
60#define RESPONSE_INIT_SUCCEEDED   10
61#define RESPONSE_INIT_FAILED      11
62#define RESPONSE_VERIFY_SUCCEEDED 12
63#define RESPONSE_VERIFY_FAILED    13
64
65/*
66 * Plugin state, used by foreground
67 */
68struct auth_pam_context
69{
70  /* Foreground's socket to background process */
71  int foreground_fd;
72
73  /* Process ID of background process */
74  pid_t background_pid;
75
76  /* Verbosity level of OpenVPN */
77  int verb;
78};
79
80/*
81 * Name/Value pairs for conversation function.
82 * Special Values:
83 *
84 *  "USERNAME" -- substitute client-supplied username
85 *  "PASSWORD" -- substitute client-specified password
86 *  "COMMONNAME" -- substitute client certificate common name
87 */
88
89#define N_NAME_VALUE 16
90
91struct name_value {
92  const char *name;
93  const char *value;
94};
95
96struct name_value_list {
97  int len;
98  struct name_value data[N_NAME_VALUE];
99};
100
101/*
102 * Used to pass the username/password
103 * to the PAM conversation function.
104 */
105struct user_pass {
106  int verb;
107
108  char username[128];
109  char password[128];
110  char common_name[128];
111
112  const struct name_value_list *name_value_list;
113};
114
115/* Background process function */
116static void pam_server (int fd, const char *service, int verb, const struct name_value_list *name_value_list);
117
118/*  Read 'tosearch', replace all occurences of 'searchfor' with 'replacewith' and return
119 *  a pointer to the NEW string.  Does not modify the input strings.  Will not enter an
120 *  infinite loop with clever 'searchfor' and 'replacewith' strings.
121 *  Daniel Johnson - Progman2000@usa.net / djohnson@progman.us
122 */
123static char *
124searchandreplace(const char *tosearch, const char *searchfor, const char *replacewith)
125{
126  const char *searching=tosearch;
127  char *scratch;
128  char temp[strlen(tosearch)*10];
129  temp[0]=0;
130
131  if (!tosearch || !searchfor || !replacewith) return 0;
132  if (!strlen(tosearch) || !strlen(searchfor) || !strlen(replacewith)) return 0;
133
134  scratch = strstr(searching,searchfor);
135  if (!scratch) return strdup(tosearch);
136
137  while (scratch) {
138    strncat(temp,searching,scratch-searching);
139    strcat(temp,replacewith);
140
141    searching=scratch+strlen(searchfor);
142    scratch = strstr(searching,searchfor);
143  }
144  return strdup(temp);
145}
146
147/*
148 * Given an environmental variable name, search
149 * the envp array for its value, returning it
150 * if found or NULL otherwise.
151 */
152static const char *
153get_env (const char *name, const char *envp[])
154{
155  if (envp)
156    {
157      int i;
158      const int namelen = strlen (name);
159      for (i = 0; envp[i]; ++i)
160	{
161	  if (!strncmp (envp[i], name, namelen))
162	    {
163	      const char *cp = envp[i] + namelen;
164	      if (*cp == '=')
165		return cp + 1;
166	    }
167	}
168    }
169  return NULL;
170}
171
172/*
173 * Return the length of a string array
174 */
175static int
176string_array_len (const char *array[])
177{
178  int i = 0;
179  if (array)
180    {
181      while (array[i])
182	++i;
183    }
184  return i;
185}
186
187/*
188 * Socket read/write functions.
189 */
190
191static int
192recv_control (int fd)
193{
194  unsigned char c;
195  const ssize_t size = read (fd, &c, sizeof (c));
196  if (size == sizeof (c))
197    return c;
198  else
199    {
200      /*fprintf (stderr, "AUTH-PAM: DEBUG recv_control.read=%d\n", (int)size);*/
201      return -1;
202    }
203}
204
205static int
206send_control (int fd, int code)
207{
208  unsigned char c = (unsigned char) code;
209  const ssize_t size = write (fd, &c, sizeof (c));
210  if (size == sizeof (c))
211    return (int) size;
212  else
213    return -1;
214}
215
216static int
217recv_string (int fd, char *buffer, int len)
218{
219  if (len > 0)
220    {
221      ssize_t size;
222      memset (buffer, 0, len);
223      size = read (fd, buffer, len);
224      buffer[len-1] = 0;
225      if (size >= 1)
226	return (int)size;
227    }
228  return -1;
229}
230
231static int
232send_string (int fd, const char *string)
233{
234  const int len = strlen (string) + 1;
235  const ssize_t size = write (fd, string, len);
236  if (size == len)
237    return (int) size;
238  else
239    return -1;
240}
241
242#ifdef DO_DAEMONIZE
243
244/*
245 * Daemonize if "daemon" env var is true.
246 * Preserve stderr across daemonization if
247 * "daemon_log_redirect" env var is true.
248 */
249static void
250daemonize (const char *envp[])
251{
252  const char *daemon_string = get_env ("daemon", envp);
253  if (daemon_string && daemon_string[0] == '1')
254    {
255      const char *log_redirect = get_env ("daemon_log_redirect", envp);
256      int fd = -1;
257      if (log_redirect && log_redirect[0] == '1')
258	fd = dup (2);
259      if (daemon (0, 0) < 0)
260	{
261	  fprintf (stderr, "AUTH-PAM: daemonization failed\n");
262	}
263      else if (fd >= 3)
264	{
265	  dup2 (fd, 2);
266	  close (fd);
267	}
268    }
269}
270
271#endif
272
273/*
274 * Close most of parent's fds.
275 * Keep stdin/stdout/stderr, plus one
276 * other fd which is presumed to be
277 * our pipe back to parent.
278 * Admittedly, a bit of a kludge,
279 * but posix doesn't give us a kind
280 * of FD_CLOEXEC which will stop
281 * fds from crossing a fork().
282 */
283static void
284close_fds_except (int keep)
285{
286  int i;
287  closelog ();
288  for (i = 3; i <= 100; ++i)
289    {
290      if (i != keep)
291	close (i);
292    }
293}
294
295/*
296 * Usually we ignore signals, because our parent will
297 * deal with them.
298 */
299static void
300set_signals (void)
301{
302  signal (SIGTERM, SIG_DFL);
303
304  signal (SIGINT, SIG_IGN);
305  signal (SIGHUP, SIG_IGN);
306  signal (SIGUSR1, SIG_IGN);
307  signal (SIGUSR2, SIG_IGN);
308  signal (SIGPIPE, SIG_IGN);
309}
310
311/*
312 * Return 1 if query matches match.
313 */
314static int
315name_value_match (const char *query, const char *match)
316{
317  while (!isalnum (*query))
318    {
319      if (*query == '\0')
320	return 0;
321      ++query;
322    }
323  return strncasecmp (match, query, strlen (match)) == 0;
324}
325
326OPENVPN_EXPORT openvpn_plugin_handle_t
327openvpn_plugin_open_v1 (unsigned int *type_mask, const char *argv[], const char *envp[])
328{
329  pid_t pid;
330  int fd[2];
331
332  struct auth_pam_context *context;
333  struct name_value_list name_value_list;
334
335  const int base_parms = 2;
336
337  /*
338   * Allocate our context
339   */
340  context = (struct auth_pam_context *) calloc (1, sizeof (struct auth_pam_context));
341  if (!context)
342    goto error;
343  context->foreground_fd = -1;
344
345  /*
346   * Intercept the --auth-user-pass-verify callback.
347   */
348  *type_mask = OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY);
349
350  /*
351   * Make sure we have two string arguments: the first is the .so name,
352   * the second is the PAM service type.
353   */
354  if (string_array_len (argv) < base_parms)
355    {
356      fprintf (stderr, "AUTH-PAM: need PAM service parameter\n");
357      goto error;
358    }
359
360  /*
361   * See if we have optional name/value pairs to match against
362   * PAM module queried fields in the conversation function.
363   */
364  name_value_list.len = 0;
365  if (string_array_len (argv) > base_parms)
366    {
367      const int nv_len = string_array_len (argv) - base_parms;
368      int i;
369
370      if ((nv_len & 1) == 1 || (nv_len / 2) > N_NAME_VALUE)
371	{
372	  fprintf (stderr, "AUTH-PAM: bad name/value list length\n");
373	  goto error;
374	}
375
376      name_value_list.len = nv_len / 2;
377      for (i = 0; i < name_value_list.len; ++i)
378	{
379	  const int base = base_parms + i * 2;
380	  name_value_list.data[i].name = argv[base];
381	  name_value_list.data[i].value = argv[base+1];
382	}
383    }
384
385  /*
386   * Get verbosity level from environment
387   */
388  {
389    const char *verb_string = get_env ("verb", envp);
390    if (verb_string)
391      context->verb = atoi (verb_string);
392  }
393
394  /*
395   * Make a socket for foreground and background processes
396   * to communicate.
397   */
398  if (socketpair (PF_UNIX, SOCK_DGRAM, 0, fd) == -1)
399    {
400      fprintf (stderr, "AUTH-PAM: socketpair call failed\n");
401      goto error;
402    }
403
404  /*
405   * Fork off the privileged process.  It will remain privileged
406   * even after the foreground process drops its privileges.
407   */
408  pid = fork ();
409
410  if (pid)
411    {
412      int status;
413
414      /*
415       * Foreground Process
416       */
417
418      context->background_pid = pid;
419
420      /* close our copy of child's socket */
421      close (fd[1]);
422
423      /* don't let future subprocesses inherit child socket */
424      if (fcntl (fd[0], F_SETFD, FD_CLOEXEC) < 0)
425	fprintf (stderr, "AUTH-PAM: Set FD_CLOEXEC flag on socket file descriptor failed\n");
426
427      /* wait for background child process to initialize */
428      status = recv_control (fd[0]);
429      if (status == RESPONSE_INIT_SUCCEEDED)
430	{
431	  context->foreground_fd = fd[0];
432	  return (openvpn_plugin_handle_t) context;
433	}
434    }
435  else
436    {
437      /*
438       * Background Process
439       */
440
441      /* close all parent fds except our socket back to parent */
442      close_fds_except (fd[1]);
443
444      /* Ignore most signals (the parent will receive them) */
445      set_signals ();
446
447#ifdef DO_DAEMONIZE
448      /* Daemonize if --daemon option is set. */
449      daemonize (envp);
450#endif
451
452      /* execute the event loop */
453      pam_server (fd[1], argv[1], context->verb, &name_value_list);
454
455      close (fd[1]);
456
457      exit (0);
458      return 0; /* NOTREACHED */
459    }
460
461 error:
462  if (context)
463    free (context);
464  return NULL;
465}
466
467OPENVPN_EXPORT int
468openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
469{
470  struct auth_pam_context *context = (struct auth_pam_context *) handle;
471
472  if (type == OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY && context->foreground_fd >= 0)
473    {
474      /* get username/password from envp string array */
475      const char *username = get_env ("username", envp);
476      const char *password = get_env ("password", envp);
477      const char *common_name = get_env ("common_name", envp) ? get_env ("common_name", envp) : "";
478
479      if (username && strlen (username) > 0 && password)
480	{
481	  if (send_control (context->foreground_fd, COMMAND_VERIFY) == -1
482	      || send_string (context->foreground_fd, username) == -1
483	      || send_string (context->foreground_fd, password) == -1
484             || send_string (context->foreground_fd, common_name) == -1)
485	    {
486	      fprintf (stderr, "AUTH-PAM: Error sending auth info to background process\n");
487	    }
488	  else
489	    {
490	      const int status = recv_control (context->foreground_fd);
491	      if (status == RESPONSE_VERIFY_SUCCEEDED)
492		return OPENVPN_PLUGIN_FUNC_SUCCESS;
493	      if (status == -1)
494		fprintf (stderr, "AUTH-PAM: Error receiving auth confirmation from background process\n");
495	    }
496	}
497    }
498  return OPENVPN_PLUGIN_FUNC_ERROR;
499}
500
501OPENVPN_EXPORT void
502openvpn_plugin_close_v1 (openvpn_plugin_handle_t handle)
503{
504  struct auth_pam_context *context = (struct auth_pam_context *) handle;
505
506  if (DEBUG (context->verb))
507    fprintf (stderr, "AUTH-PAM: close\n");
508
509  if (context->foreground_fd >= 0)
510    {
511      /* tell background process to exit */
512      if (send_control (context->foreground_fd, COMMAND_EXIT) == -1)
513	fprintf (stderr, "AUTH-PAM: Error signaling background process to exit\n");
514
515      /* wait for background process to exit */
516      if (context->background_pid > 0)
517	waitpid (context->background_pid, NULL, 0);
518
519      close (context->foreground_fd);
520      context->foreground_fd = -1;
521    }
522
523  free (context);
524}
525
526OPENVPN_EXPORT void
527openvpn_plugin_abort_v1 (openvpn_plugin_handle_t handle)
528{
529  struct auth_pam_context *context = (struct auth_pam_context *) handle;
530
531  /* tell background process to exit */
532  if (context && context->foreground_fd >= 0)
533    {
534      send_control (context->foreground_fd, COMMAND_EXIT);
535      close (context->foreground_fd);
536      context->foreground_fd = -1;
537    }
538}
539
540/*
541 * PAM conversation function
542 */
543static int
544my_conv (int n, const struct pam_message **msg_array,
545	 struct pam_response **response_array, void *appdata_ptr)
546{
547  const struct user_pass *up = ( const struct user_pass *) appdata_ptr;
548  struct pam_response *aresp;
549  int i;
550  int ret = PAM_SUCCESS;
551
552  *response_array = NULL;
553
554  if (n <= 0 || n > PAM_MAX_NUM_MSG)
555    return (PAM_CONV_ERR);
556  if ((aresp = calloc (n, sizeof *aresp)) == NULL)
557    return (PAM_BUF_ERR);
558
559  /* loop through each PAM-module query */
560  for (i = 0; i < n; ++i)
561    {
562      const struct pam_message *msg = msg_array[i];
563      aresp[i].resp_retcode = 0;
564      aresp[i].resp = NULL;
565
566      if (DEBUG (up->verb))
567	{
568	  fprintf (stderr, "AUTH-PAM: BACKGROUND: my_conv[%d] query='%s' style=%d\n",
569		   i,
570		   msg->msg ? msg->msg : "NULL",
571		   msg->msg_style);
572	}
573
574      if (up->name_value_list && up->name_value_list->len > 0)
575	{
576	  /* use name/value list match method */
577	  const struct name_value_list *list = up->name_value_list;
578	  int j;
579
580	  /* loop through name/value pairs */
581	  for (j = 0; j < list->len; ++j)
582	    {
583	      const char *match_name = list->data[j].name;
584	      const char *match_value = list->data[j].value;
585
586	      if (name_value_match (msg->msg, match_name))
587		{
588		  /* found name/value match */
589		  aresp[i].resp = NULL;
590
591		  if (DEBUG (up->verb))
592		    fprintf (stderr, "AUTH-PAM: BACKGROUND: name match found, query/match-string ['%s', '%s'] = '%s'\n",
593			     msg->msg,
594			     match_name,
595			     match_value);
596
597		  if (strstr(match_value, "USERNAME"))
598		    aresp[i].resp = searchandreplace(match_value, "USERNAME", up->username);
599		  else if (strstr(match_value, "PASSWORD"))
600		    aresp[i].resp = searchandreplace(match_value, "PASSWORD", up->password);
601		  else if (strstr(match_value, "COMMONNAME"))
602		    aresp[i].resp = searchandreplace(match_value, "COMMONNAME", up->common_name);
603		  else
604		    aresp[i].resp = strdup (match_value);
605
606		  if (aresp[i].resp == NULL)
607		    ret = PAM_CONV_ERR;
608		  break;
609		}
610	    }
611
612	  if (j == list->len)
613	    ret = PAM_CONV_ERR;
614	}
615      else
616	{
617	  /* use PAM_PROMPT_ECHO_x hints */
618	  switch (msg->msg_style)
619	    {
620	    case PAM_PROMPT_ECHO_OFF:
621	      aresp[i].resp = strdup (up->password);
622	      if (aresp[i].resp == NULL)
623		ret = PAM_CONV_ERR;
624	      break;
625
626	    case PAM_PROMPT_ECHO_ON:
627	      aresp[i].resp = strdup (up->username);
628	      if (aresp[i].resp == NULL)
629		ret = PAM_CONV_ERR;
630	      break;
631
632	    case PAM_ERROR_MSG:
633	    case PAM_TEXT_INFO:
634	      break;
635
636	    default:
637	      ret = PAM_CONV_ERR;
638	      break;
639	    }
640	}
641    }
642
643  if (ret == PAM_SUCCESS)
644    *response_array = aresp;
645  return ret;
646}
647
648/*
649 * Return 1 if authenticated and 0 if failed.
650 * Called once for every username/password
651 * to be authenticated.
652 */
653static int
654pam_auth (const char *service, const struct user_pass *up)
655{
656  struct pam_conv conv;
657  pam_handle_t *pamh = NULL;
658  int status = PAM_SUCCESS;
659  int ret = 0;
660  const int name_value_list_provided = (up->name_value_list && up->name_value_list->len > 0);
661
662  /* Initialize PAM */
663  conv.conv = my_conv;
664  conv.appdata_ptr = (void *)up;
665  status = pam_start (service, name_value_list_provided ? NULL : up->username, &conv, &pamh);
666  if (status == PAM_SUCCESS)
667    {
668      /* Call PAM to verify username/password */
669      status = pam_authenticate(pamh, 0);
670      if (status == PAM_SUCCESS)
671	status = pam_acct_mgmt (pamh, 0);
672      if (status == PAM_SUCCESS)
673	ret = 1;
674
675      /* Output error message if failed */
676      if (!ret)
677	{
678	  fprintf (stderr, "AUTH-PAM: BACKGROUND: user '%s' failed to authenticate: %s\n",
679		   up->username,
680		   pam_strerror (pamh, status));
681	}
682
683      /* Close PAM */
684      pam_end (pamh, status);
685    }
686
687  return ret;
688}
689
690/*
691 * Background process -- runs with privilege.
692 */
693static void
694pam_server (int fd, const char *service, int verb, const struct name_value_list *name_value_list)
695{
696  struct user_pass up;
697  int command;
698#ifdef USE_PAM_DLOPEN
699  static const char pam_so[] = "libpam.so";
700#endif
701
702  /*
703   * Do initialization
704   */
705  if (DEBUG (verb))
706    fprintf (stderr, "AUTH-PAM: BACKGROUND: INIT service='%s'\n", service);
707
708#ifdef USE_PAM_DLOPEN
709  /*
710   * Load PAM shared object
711   */
712  if (!dlopen_pam (pam_so))
713    {
714      fprintf (stderr, "AUTH-PAM: BACKGROUND: could not load PAM lib %s: %s\n", pam_so, dlerror());
715      send_control (fd, RESPONSE_INIT_FAILED);
716      goto done;
717    }
718#endif
719
720  /*
721   * Tell foreground that we initialized successfully
722   */
723  if (send_control (fd, RESPONSE_INIT_SUCCEEDED) == -1)
724    {
725      fprintf (stderr, "AUTH-PAM: BACKGROUND: write error on response socket [1]\n");
726      goto done;
727    }
728
729  /*
730   * Event loop
731   */
732  while (1)
733    {
734      memset (&up, 0, sizeof (up));
735      up.verb = verb;
736      up.name_value_list = name_value_list;
737
738      /* get a command from foreground process */
739      command = recv_control (fd);
740
741      if (DEBUG (verb))
742	fprintf (stderr, "AUTH-PAM: BACKGROUND: received command code: %d\n", command);
743
744      switch (command)
745	{
746	case COMMAND_VERIFY:
747	  if (recv_string (fd, up.username, sizeof (up.username)) == -1
748	      || recv_string (fd, up.password, sizeof (up.password)) == -1
749	      || recv_string (fd, up.common_name, sizeof (up.common_name)) == -1)
750	    {
751	      fprintf (stderr, "AUTH-PAM: BACKGROUND: read error on command channel: code=%d, exiting\n",
752		       command);
753	      goto done;
754	    }
755
756	  if (DEBUG (verb))
757	    {
758#if 0
759	      fprintf (stderr, "AUTH-PAM: BACKGROUND: USER/PASS: %s/%s\n",
760		       up.username, up.password);
761#else
762	      fprintf (stderr, "AUTH-PAM: BACKGROUND: USER: %s\n", up.username);
763#endif
764	    }
765
766	  if (pam_auth (service, &up)) /* Succeeded */
767	    {
768	      if (send_control (fd, RESPONSE_VERIFY_SUCCEEDED) == -1)
769		{
770		  fprintf (stderr, "AUTH-PAM: BACKGROUND: write error on response socket [2]\n");
771		  goto done;
772		}
773	    }
774	  else /* Failed */
775	    {
776	      if (send_control (fd, RESPONSE_VERIFY_FAILED) == -1)
777		{
778		  fprintf (stderr, "AUTH-PAM: BACKGROUND: write error on response socket [3]\n");
779		  goto done;
780		}
781	    }
782	  break;
783
784	case COMMAND_EXIT:
785	  goto done;
786
787	case -1:
788	  fprintf (stderr, "AUTH-PAM: BACKGROUND: read error on command channel\n");
789	  goto done;
790
791	default:
792	  fprintf (stderr, "AUTH-PAM: BACKGROUND: unknown command code: code=%d, exiting\n",
793		   command);
794	  goto done;
795	}
796    }
797 done:
798
799#ifdef USE_PAM_DLOPEN
800  dlclose_pam ();
801#endif
802  if (DEBUG (verb))
803    fprintf (stderr, "AUTH-PAM: BACKGROUND: EXIT\n");
804
805  return;
806}
807