diff --git a/rhodecode/lib/auth_modules/auth_pam.py b/rhodecode/lib/auth_modules/auth_pam.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/lib/auth_modules/auth_pam.py
@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+"""
+rhodecode.lib.auth_pam
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+RhodeCode authentication library for PAM
+
+:created_on: Created on Apr 09, 2013
+:author: Alexey Larikov
+"""
+
+import logging
+import time
+import pam
+import pwd
+import grp
+import re
+import socket
+import threading
+
+from rhodecode.lib import auth_modules
+from rhodecode.lib.compat import formatted_json, hybrid_property
+
+log = logging.getLogger(__name__)
+
+# Cache to store PAM authenticated users
+_auth_cache = dict()
+_pam_lock = threading.Lock()
+
+
+class RhodeCodeAuthPlugin(auth_modules.RhodeCodeExternalAuthPlugin):
+ # PAM authnetication can be slow. Repository operations involve a lot of
+ # auth calls. Little caching helps speedup push/pull operations significantly
+ AUTH_CACHE_TTL = 4
+
+ def __init__(self):
+ global _auth_cache
+ ts = time.time()
+ cleared_cache = dict(
+ [(k, v) for (k, v) in _auth_cache.items() if
+ (v + RhodeCodeAuthPlugin.AUTH_CACHE_TTL > ts)])
+ _auth_cache = cleared_cache
+
+ @hybrid_property
+ def name(self):
+ return "pam"
+
+ def settings(self):
+ settings = [
+ {
+ "name": "service",
+ "validator": self.validators.UnicodeString(strip=True),
+ "type": "string",
+ "description": "PAM service name to use for authentication",
+ "default": "login",
+ "formname": "PAM service name"
+ },
+ {
+ "name": "gecos",
+ "validator": self.validators.UnicodeString(strip=True),
+ "type": "string",
+ "description": "Regex for extracting user name/email etc "
+ "from Unix userinfo",
+ "default": "(?P.+),\s*(?P\w+)",
+ "formname": "Gecos Regex"
+ }
+ ]
+ return settings
+
+ def use_fake_password(self):
+ return True
+
+ def auth(self, userobj, username, password, settings, **kwargs):
+ if username not in _auth_cache:
+ # Need lock here, as PAM authentication is not thread safe
+ _pam_lock.acquire()
+ try:
+ auth_result = pam.authenticate(username, password,
+ settings["service"])
+ # cache result only if we properly authenticated
+ if auth_result:
+ _auth_cache[username] = time.time()
+ finally:
+ _pam_lock.release()
+
+ if not auth_result:
+ log.error("PAM was unable to authenticate user: %s" % (username,))
+ return None
+ else:
+ log.debug("Using cached auth for user: %s" % (username,))
+
+ # old attrs fetched from RhodeCode database
+ admin = getattr(userobj, 'admin', False)
+ active = getattr(userobj, 'active', True)
+ email = getattr(userobj, 'email', '') or "%s@%s" % (username, socket.gethostname())
+ firstname = getattr(userobj, 'firstname', '')
+ lastname = getattr(userobj, 'lastname', '')
+ extern_type = getattr(userobj, 'extern_type', '')
+
+ user_attrs = {
+ 'username': username,
+ 'firstname': firstname,
+ 'lastname': lastname,
+ 'groups': [g.gr_name for g in grp.getgrall() if username in g.gr_mem],
+ 'email': email,
+ 'admin': admin,
+ 'active': active,
+ "active_from_extern": None,
+ 'extern_name': username,
+ 'extern_type': extern_type,
+ }
+
+ try:
+ user_data = pwd.getpwnam(username)
+ regex = settings["gecos"]
+ match = re.search(regex, user_data.pw_gecos)
+ if match:
+ user_attrs["firstname"] = match.group('first_name')
+ user_attrs["lastname"] = match.group('last_name')
+ except Exception:
+ log.warn("Cannot extract additional info for PAM user")
+ pass
+
+ log.debug("pamuser: \n%s" % formatted_json(user_attrs))
+ log.info('user %s authenticated correctly' % user_attrs['username'])
+ return user_attrs