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