Source code for linotp.lib.tokens.radiustoken

# -*- coding: utf-8 -*-
#
#    LinOTP - the open source solution for two factor authentication
#    Copyright (C) 2010 - 2014 LSE Leading Security Experts GmbH
#
#    This file is part of LinOTP server.
#
#    This program is free software: you can redistribute it and/or
#    modify it under the terms of the GNU Affero General Public
#    License, version 3, as published by the Free Software Foundation.
#
#    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 Affero General Public License for more details.
#
#    You should have received a copy of the
#               GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#
#    E-mail: linotp@lsexperts.de
#    Contact: www.linotp.org
#    Support: www.lsexperts.de
#
"""This file containes the RADIUS token class"""


import logging

import traceback
import binascii

from linotp.lib.util    import getParam


optional = True
required = False

## for update, we require the TokenClass
from linotp.lib.tokenclass import TokenClass
from linotp.lib.tokens.remotetoken import RemoteTokenClass


log = logging.getLogger(__name__)

# we need this for the radius token
import pyrad.packet
from pyrad.client import Client
from pyrad.dictionary import Dictionary
from pylons.configuration import config as env


VOID_RADIUS_SECRET = "voidRadiusSecret"

###############################################
[docs]class RadiusTokenClass(RemoteTokenClass): def __init__(self, aToken): RemoteTokenClass.__init__(self, aToken) self.setType(u"radius") self.radiusServer = "" self.radiusUser = "" self.radiusLocal_checkpin = "0" self.radiusSecret = VOID_RADIUS_SECRET @classmethod
[docs] def getClassType(cls): return "radius"
@classmethod
[docs] def getClassPrefix(cls): return "LSRA"
@classmethod
[docs] def getClassInfo(cls, key=None, ret='all'): ''' getClassInfo - returns a subtree of the token definition :param key: subsection identifier :type key: string :param ret: default return value, if nothing is found :type ret: user defined :return: subsection if key exists or user defined :rtype: s.o. ''' log.debug("[getClassInfo] begin. Get class render info for section: key %r, ret %r " % (key, ret)) res = { 'type' : 'radius', 'title' : 'RADIUS Token', 'description' : ('RADIUS token to forward the authentication request to another RADIUS server'), 'init' : {'page' : {'html' : 'radiustoken.mako', 'scope' : 'enroll', }, 'title' : {'html' : 'radiustoken.mako', 'scope' : 'enroll.title', }, }, 'config' : { 'page' : {'html' : 'radiustoken.mako', 'scope' : 'config', }, 'title' : {'html' : 'radiustoken.mako', 'scope' : 'config.title', }, }, 'selfservice' : {}, 'policy' : {}, } if key is not None and res.has_key(key): ret = res.get(key) else: if ret == 'all': ret = res log.debug("[getClassInfo] end. Returned the configuration section: ret %r " % (ret)) return ret
[docs] def update(self, param): self.radiusServer = getParam(param, "radius.server", required) # if another OTP length would be specified in /admin/init this would # be overwritten by the parent class, which is ok. self.setOtpLen(6) val = getParam(param, "radius.local_checkpin", optional) if val is not None: self.radiusLocal_checkpin = val val = getParam(param, "radius.user", required) if val is not None: self.radiusUser = val val = getParam(param, "radius.secret", required) if val is not None: self.radiusSecret = val if self.radiusSecret == VOID_RADIUS_SECRET: log.warning("Usage of default radius secret is not recomended!!") TokenClass.update(self, param) # We need to write the secret! self.token.setHKey(binascii.hexlify(self.radiusSecret)) self.addToTokenInfo("radius.server", self.radiusServer) self.addToTokenInfo("radius.local_checkpin", self.radiusLocal_checkpin) self.addToTokenInfo("radius.user", self.radiusUser)
[docs] def check_pin_local(self): """ lookup if pin should be checked locally or on radius host :return: bool """ local_check = False if 1 == int(self.getFromTokenInfo("radius.local_checkpin")): local_check = True log.debug(" local checking pin? %r" % local_check) return local_check
[docs] def checkPin(self, pin, options=None): ''' check the pin - either remote or localy - in case of remote, we return true, as the the splitPinPass will put the passw then in the otpVal :param pin: the pin which should be checked :param options: the additional request parameters ''' res = True log.debug("[checkPin]") if self.check_pin_local(): log.debug("[checkPin] [radiustoken] checking PIN locally") res = RemoteTokenClass.checkPin(self, pin) return res
[docs] def splitPinPass(self, passw): ''' Split the PIN and the OTP value. Only if it is locally checked and not remotely. ''' res = 0 pin = "" otpval = "" local_check = self.check_pin_local() log.debug("[splitPinPass] [radiustoken] local checking pin? %r" % local_check) if self.check_pin_local(): log.debug("[splitPinPass] [radiustoken] locally checked") (res, pin, otpval) = TokenClass.splitPinPass(self, passw) else: log.debug("[splitPinPass] [radiustoken] remotely checked") pin = "" otpval = passw log.debug("[splitPinPass] [radiustoken] returning (len:%s) (len:%s)" % (len(pin), len(otpval))) return (res, pin, otpval)
[docs] def do_request(self, anOtpVal, transactionid=None, user=None): ''' Here we contact the Radius Server to verify the pass ''' log.debug("do_request") reply = {} res = False otp_count = -1 radiusServer = self.getFromTokenInfo("radius.server") radiusUser = self.getFromTokenInfo("radius.user") ## Read the secret!!! secret = self.token.getHOtpKey() radiusSecret = binascii.unhexlify(secret.getKey()) if radiusSecret == VOID_RADIUS_SECRET: log.warning("Usage of default radius secret is not recomended!!") ## here we also need to check for radius.user log.debug("[do_request] checking OTP len:%s on radius server: %s," " user: %s" % (len(anOtpVal), radiusServer, radiusUser)) try: # pyrad does not allow to set timeout and retries. # it defaults to retries=3, timeout=5 # TODO: At the moment we support only one radius server. # No round robin. server = radiusServer.split(':') r_server = server[0] r_authport = 1812 nas_identifier = env.get("radius.nas_identifier", "LinOTP") r_dict = env.get("radius.dictfile", "/etc/linotp2/dictionary") if len(server) >= 2: r_authport = int(server[1]) log.debug("[do_request] [RadiusToken] NAS Identifier: %r, " "Dictionary: %r" % (nas_identifier, r_dict)) log.debug("[do_request] [RadiusToken] constructing client object " "with server: %r, port: %r, secret: %r" % (r_server, r_authport, radiusSecret)) srv = Client(server=r_server, authport=r_authport, secret=radiusSecret, dict=Dictionary(r_dict)) #log.debug("[checkOTP [RadiusToken] building Request packet") req = srv.CreateAuthPacket(code=pyrad.packet.AccessRequest, User_Name=radiusUser.encode('ascii'), NAS_Identifier=nas_identifier.encode('ascii')) #log.debug("[checkOTP [RadiusToken] adding password to request") req["User-Password"] = req.PwCrypt(anOtpVal) if transactionid is not None: req["State"] = str(transactionid) #log.debug("[checkOTP [RadiusToken] sending request") #log.debug(req) response = srv.SendPacket(req) if response.code == pyrad.packet.AccessChallenge: opt = {} for attr in response.keys(): opt[attr] = response[attr] res = False log.debug("challenge returned %r " % opt) ## now we map this to a linotp challenge if "State" in opt: reply["transactionid"] = opt["State"][0] if "Reply-Message" in opt: reply["message"] = opt["Reply-Message"][0] elif response.code == pyrad.packet.AccessAccept: log.info("[do_request] [RadiusToken] Radiusserver %s granted " "access to user %s." % (r_server, radiusUser)) otp_count = 0 res = True else: log.warning("[do_request] [RadiusToken] Radiusserver %s" "rejected access to user %s." % (r_server, radiusUser)) res = False except Exception as ex: log.error("[do_request] [RadiusToken] Error contacting radius Server: %r" % (ex)) log.error("[do_request] [RadiusToken] %r" % traceback.format_exc()) return (res, otp_count, reply)