From 0cda12f91660a2465fdf4748020dfebf5ac24177 Mon Sep 17 00:00:00 2001
From: "th.l" <thl-cmk@outlook.com>
Date: Sun, 28 Apr 2024 17:48:15 +0200
Subject: [PATCH] update project

---
 README.md                                     |   2 +-
 mkp/check_radius-0.1.0-20240428.mkp           | Bin 0 -> 11238 bytes
 source/checks/check_radius                    |  39 ++-
 source/gui/metrics/check_radius.py            |  12 +-
 .../gui/wato/check_parameters/check_radius.py | 246 +++++++++++++++-
 source/lib/nagios/plugins/check_radius        | 278 ++++++++++++++----
 source/lib/nagios/plugins/dictionary          |   2 +-
 .../lib/nagios/plugins/dictionary.freeradius  |   0
 source/packages/check_radius                  |  11 +-
 9 files changed, 490 insertions(+), 100 deletions(-)
 create mode 100644 mkp/check_radius-0.1.0-20240428.mkp
 mode change 100644 => 100755 source/lib/nagios/plugins/dictionary
 mode change 100644 => 100755 source/lib/nagios/plugins/dictionary.freeradius

diff --git a/README.md b/README.md
index 6f8835b..e5f9ebc 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[PACKAGE]: ../../raw/master/mkp/check_radius-0.0.1-20240421.mkp "check_radius-0.0.1-20240421.mkp"
+[PACKAGE]: ../../raw/master/mkp/check_radius-0.1.0-20240428.mkp "check_radius-0.1.0-20240428.mkp"
 # Check RADIUS
 
 This is a (very) basic active RADIUS check. Tests if a RADIUS server is responsive (accept/reject/timeout). There is (limited) support to add AV-pairs to the RADIUS request.
diff --git a/mkp/check_radius-0.1.0-20240428.mkp b/mkp/check_radius-0.1.0-20240428.mkp
new file mode 100644
index 0000000000000000000000000000000000000000..c895811b07fc9b346597ffd6feffbc1f8170408e
GIT binary patch
literal 11238
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

literal 0
HcmV?d00001

diff --git a/source/checks/check_radius b/source/checks/check_radius
index eaa316c..3521903 100644
--- a/source/checks/check_radius
+++ b/source/checks/check_radius
@@ -14,32 +14,53 @@
 def check_radius_arguments(params):
     args = []
 
-    if server := params.get('server'):
+    if (server := params.get('server')) is not None:
         args.extend(['-H', server])
     else:
         args.append('-H $HOSTADDRESS$')
 
-    if auth_port := params.get("auth_port"):
-        args.extend(['--authport', auth_port])
+    if (auth_port := params.get("auth_port")) is not None:
+        args.extend(['--auth-port', auth_port])
 
-    if secret := params.get("secret"):
+    if (secret := params.get("secret")) is not None:
         args.extend(["--secret", passwordstore_get_cmdline("%s", secret)])
 
-    if user_name := params.get("user_name"):
+    if (user_name := params.get("user_name")) is not None:
         args.extend([f'--username', user_name])
 
-    if user_password := params.get("user_password"):
+    if (user_password := params.get("user_password")) is not None:
         args.extend(["--password", passwordstore_get_cmdline("%s", user_password)])
 
-    if timeout := params.get('timeout'):
-        args.extend(['-timeout', timeout])
+    if (timeout := params.get('timeout')) is not None:
+        args.extend(['--timeout', timeout])
+
+    if (request_attributes := params.get('request_params').get('request_attributes')) is not None:
+        for av_name, av_value in request_attributes:
+            args.extend(['--request-attribute', f'{av_name}:{av_value}'])
+
+    if (expected_response := params.get('response_params').get('expected_response')) is not None:
+        args.extend(['--expected-response', expected_response])
+
+    if (state_not_expected_response := params.get('response_params').get('state_not_expected_response')) is not None:
+        args.extend(['--state-not-expected-response', state_not_expected_response])
+
+    if (num_resp_attributes := params.get('response_params').get('num_resp_attributes')) is not None:
+        args.extend(['--num-resp-attributes', num_resp_attributes])
+
+    if (state_wrong_number_of_response_attributes := params.get('response_params').get(
+            'state_wrong_number_of_response_attributes')) is not None:
+        args.extend(['--state-wrong-num-resp-attributes', state_wrong_number_of_response_attributes])
+
+    if (level_upper_response_time := params.get('response_params').get('level_upper_response_time')) is not None:
+        warn, crit = level_upper_response_time
+        args.extend(['--max-response-time', f'{warn}, {crit}'])
 
     return args
 
 
 def _check_description(params):
     if 'description' in params:
-        return f'RADIUS server {params["description"]}'
+        return f'RADIUS {params["description"]}'
 
     return 'RADIUS server'
 
diff --git a/source/gui/metrics/check_radius.py b/source/gui/metrics/check_radius.py
index 5c8cff2..67790d9 100644
--- a/source/gui/metrics/check_radius.py
+++ b/source/gui/metrics/check_radius.py
@@ -18,8 +18,8 @@ from cmk.gui.plugins.metrics.utils import (
     perfometer_info
 )
 
-metric_info['radius_request_time'] = {
-    'title': _('Request time'),
+metric_info['radius_response_time'] = {
+    'title': _('Response time'),
     'unit': 's',
     'color': '#9a52bf',
 }
@@ -27,17 +27,17 @@ metric_info['radius_request_time'] = {
 graph_info['check_radius_time'] = {
     'title': _('RADIUS request time'),
     'metrics': [
-        ('radius_request_time', 'area'),
+        ('radius_response_time', 'area'),
     ],
     'scalars': [
-        ('radius_request_time:crit', _('Crit')),
-        ('radius_request_time:warn', _('Warn')),
+        ('radius_response_time:crit', _('Crit')),
+        ('radius_response_time:warn', _('Warn')),
     ],
 }
 
 perfometer_info.append({
     'type': 'logarithmic',
-    'metric': 'radius_request_time',
+    'metric': 'radius_response_time',
     'half_value': 1.0,
     'exponent': 10.0,
 })
diff --git a/source/gui/wato/check_parameters/check_radius.py b/source/gui/wato/check_parameters/check_radius.py
index d38398d..71dd1d8 100644
--- a/source/gui/wato/check_parameters/check_radius.py
+++ b/source/gui/wato/check_parameters/check_radius.py
@@ -12,15 +12,153 @@
 # 2024-01-01: modified for cmk 2.2.x
 
 from cmk.gui.i18n import _
+from cmk.gui.plugins.wato.active_checks.common import RulespecGroupActiveChecks
+from cmk.gui.plugins.wato.utils import HostRulespec, IndividualOrStoredPassword, rulespec_registry
 from cmk.gui.valuespec import (
+    Alternative,
     Dictionary,
+    DropdownChoice,
+    FixedValue,
+    Foldable,
+    IPv4Address,
     Integer,
-    TextAscii,
+    ListOf,
+    MonitoringState,
+    TextInput,
     Transform,
+    Tuple,
 )
 
-from cmk.gui.plugins.wato.active_checks.common import RulespecGroupActiveChecks
-from cmk.gui.plugins.wato.utils import HostRulespec, rulespec_registry, IndividualOrStoredPassword
+_called_station_id = Tuple(
+    title='Called-Station-Id',
+    orientation='horizontal',
+    elements=[
+        FixedValue('Called-Station-Id'),  # , totext='' # add empty totext to remove "duplicate" attribute name
+        TextInput(
+            size=20,
+            placeholder='AA-BB-CC-DD-EE-FF',
+            allow_empty=False,
+        ),
+    ],
+)
+_calling_station_id = Tuple(
+    title='Calling-Station-Id',
+    orientation='horizontal',
+    elements=[
+        FixedValue('Calling-Station-Id'),
+        TextInput(
+            size=20,
+            placeholder='AA-BB-CC-DD-EE-FF',
+            allow_empty=False,
+        ),
+    ],
+)
+_framed_mtu = Tuple(
+    title='Framed-MTU',
+    orientation='horizontal',
+    elements=[
+        FixedValue('Framed-MTU'),
+        Integer(
+            size=5,
+            default_value=1500
+        ),
+    ],
+)
+_nas_identifier = Tuple(
+    title='NAS-Identifier',
+    orientation='horizontal',
+    elements=[
+        FixedValue('NAS-Identifier'),
+        TextInput(
+            size=20,
+            placeholder='NAS001',
+            allow_empty=False,
+        ),
+    ],
+)
+_nas_ip_address = Tuple(
+    title='NAS-IP-Address',
+    orientation='horizontal',
+    elements=[
+        FixedValue('NAS-IP-Address'),
+        IPv4Address(),
+    ],
+)
+_nas_port_id = Tuple(
+    title='NAS-Port-Id',
+    orientation='horizontal',
+    elements=[
+        FixedValue('NAS-Port-Id'),
+        TextInput(
+            size=20,
+            placeholder='GigabitEthernet0/8',
+            allow_empty=False,
+        ),
+    ],
+)
+_nas_port_type = Tuple(
+    title='NAS-Port-Type',
+    orientation='horizontal',
+    elements=[
+        FixedValue('NAS-Port-Type'),
+        DropdownChoice(
+            choices=[
+                ('0', 'Async'),
+                ('1', 'Sync'),
+                ('2', 'ISDN'),
+                ('3', 'ISDN-V120'),
+                ('4', 'ISDN-V110'),
+                ('5', 'Virtual'),
+                ('6', 'PIAFS'),
+                ('7', 'HDLC-Clear-Channel'),
+                ('8', 'X.25'),
+                ('9', 'X.75'),
+                ('10', 'G.3-Fax'),
+                ('11', 'SDSL'),
+                ('12', 'ADSL-CAP'),
+                ('13', 'ADSL-DMT'),
+                ('14', 'IDSL'),
+                ('15', 'Ethernet'),
+                ('16', 'xDSL'),
+                ('17', 'Cable'),
+                ('18', 'Wireless-Other'),
+                ('19', 'Wireless-802.11'),
+            ]
+        ),
+    ],
+)
+_nas_port = Tuple(
+    title='NAS-Port',
+    orientation='horizontal',
+    elements=[
+        FixedValue('NAS-Port'),
+        Integer(
+            size=7,
+        ),
+    ],
+)
+_service_type = Tuple(
+    title='Service-Type',
+    orientation='horizontal',
+    elements=[
+        FixedValue('Service-Type'),
+        DropdownChoice(
+            choices=[
+                ('1', 'Login-User'),
+                ('2', 'Framed-User'),
+                ('3', 'Callback-Login-User'),
+                ('4', 'Callback-Framed-User'),
+                ('5', 'Outbound-User'),
+                ('6', 'Administrative-User'),
+                ('7', 'NAS-Prompt-User'),
+                ('8', 'Authenticate-Only'),
+                ('9', 'Callback-NAS-Prompt'),
+                ('10', 'Call-Check'),
+                ('11', 'Callback-Administrative'),
+            ]
+        ),
+    ],
+)
 
 
 def _valuespec_active_checks_radius():
@@ -30,49 +168,51 @@ def _valuespec_active_checks_radius():
             help=_(''),
             elements=[
                 ('description',
-                 TextAscii(
+                 TextInput(
                      title=_('Service description'),
                      help=_(
-                         'Must be unique for every host. The service description starts always with \"RADIUS server\".'),
+                         'Must be unique for every host. The service description starts always with \"RADIUS server\".'
+                     ),
                      size=50,
                      placeholder='Item name for the service',
                      allow_empty=False,
                  )),
                 ('server',
-                 TextAscii(
+                 TextInput(
                      title=_('Server IP-address or name'),
                      help=_(
                          'Hostname or IP-address to monitor. Default is the host name/IP-Address of the monitored host.'
                      ),
                      size=50,
                      allow_empty=False,
+                     placeholder='i.e 192.168.10.10 or srvrad01.company.intern'
                  )),
                 ('auth_port',
                  Integer(
-                     title=_('RADIUS authentication port'),
+                     title=_('Authentication port'),
                      help=_('The RADIUS port to use for authentication. Default is 1812.'),
-                     # size=5,
+                     size=5,
                      default_value=1812,
                      minvalue=1,
                      maxvalue=65535,
                  )),
                 ('secret',
                  IndividualOrStoredPassword(
-                     title=_('Server secret'),
-                     help=_('The RADIUS secret.'),
-                     # size=50,
+                     title=_('Shared secret'),
+                     help=_('The shared secret.'),
                      allow_empty=False,
                  )),
                 ('timeout',
                  Integer(
-                     title=_('Server timeout'),
-                     help=_('The user password.'),
+                     title=_('Request timeout'),
+                     help=_('The timeout for the RADIUS request.'),
                      default_value=2,
                      minvalue=1,
                      maxvalue=30,
+                     unit='s',
                  )),
                 ('user_name',
-                 TextAscii(
+                 TextInput(
                      title=_('User name'),
                      help=_('The user name to use in the request.'),
                      size=50,
@@ -83,11 +223,85 @@ def _valuespec_active_checks_radius():
                  IndividualOrStoredPassword(
                      title=_('User password'),
                      help=_('The user password.'),
-                     # size=50,
                      allow_empty=False
                  )),
+                ('request_params',
+                 Foldable(
+                     Dictionary(
+                         title="Hide/Show Request Parameters",
+                         elements=[
+                             ('request_attributes',
+                              ListOf(
+                                  Alternative(
+                                      orientation='horizontal',
+                                      elements=[
+                                          _called_station_id,
+                                          _calling_station_id,
+                                          _framed_mtu,
+                                          _nas_identifier,
+                                          _nas_ip_address,
+                                          _nas_port,
+                                          _nas_port_id,
+                                          _nas_port_type,
+                                          _service_type,
+                                      ],
+                                  ),
+                                  title=_('Request Attributes'),
+                                  add_label=_('add attribute'),
+                              )),
+                         ],
+                     ),
+                     title='Request paranmeters',
+                 )),
+                ('response_params',
+                 Foldable(
+                     Dictionary(
+                         title="Hide/Show Response Parameters",
+                         elements=[
+                             ('expected_response',
+                              DropdownChoice(
+                                  choices=(
+                                      (2, 'Accepted'),
+                                      (3, 'Rejected'),
+                                  ),
+                                  title=_('Expected response'),
+                                  help=_('Expected response from the RADIUS server.'),
+                              )),
+                             ('state_not_expected_response',
+                              MonitoringState(
+                                  title=_('Monitoring state not expected response'),
+                                  default_value=2,
+                              )),
+                             ('level_upper_response_time',
+                              Tuple(
+                                  elements=[
+                                      Integer(title=_('Warning at'), unit='ms', minvalue=0, maxvalue=10000),
+                                      Integer(title=_('Critical at'), unit='ms', minvalue=0, maxvalue=10000),
+                                  ],
+                                  title=_('Max. response time'),
+                              )),
+                             ('num_resp_attributes',
+                              Integer(
+                                  title=_('# of expected attribues in response'),
+                                  help=_('The expected number of RADIUS attibutes in the response.'),
+                                  minvalue=0,
+                                  maxvalue=65535,
+                              )),
+                             ('state_wrong_number_of_response_attributes',
+                              MonitoringState(
+                                  title=_('Monitoring state on wrong # of response attributes'),
+                                  default_value=1,
+                              )),
+                         ],
+                     ),
+                     title='Response paranmeters',
+                 ))
             ],
-            required_keys=['secret']
+            required_keys=[
+                'secret',
+                'request_params',
+                'response_params',
+            ]
         ),
     )
 
diff --git a/source/lib/nagios/plugins/check_radius b/source/lib/nagios/plugins/check_radius
index 4680980..2147c1f 100755
--- a/source/lib/nagios/plugins/check_radius
+++ b/source/lib/nagios/plugins/check_radius
@@ -13,48 +13,146 @@
 #
 # https://github.com/pyradius/pyrad
 #
-import socket
 
-from typing import Sequence
-import sys
-from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, Namespace, ArgumentTypeError
-from time import time_ns
+from argparse import (
+    ArgumentDefaultsHelpFormatter,
+    ArgumentParser,
+    ArgumentTypeError,
+    Namespace,
+)
 from os import environ
+from socket import error as socket_error
+from sys import (
+    argv as sys_argv,
+    exit as sys_exit,
+    stdout as sys_stdout,
+)
+from time import time_ns
+from typing import Sequence, Tuple
 
 import cmk.utils.password_store
 
 no_radiuslib = False
 try:
-    from pyrad.client import Client as rad_client
-    from pyrad.dictionary import Dictionary as rad_dictionary
-    import pyrad.packet
+    from pyrad.client import Client as radClient
+    from pyrad.dictionary import Dictionary as radDictionary
+    from pyrad.packet import AccessAccept, AccessReject, AccessRequest
+    from pyrad.client import Timeout as pyTimeout
 except ModuleNotFoundError:
     no_radiuslib = True
 
 
-def parse_arguments(argv: Sequence[str]) -> Namespace:
+class Args(Namespace):
+    host: str
+    auth_port: int
+    secret: str
+    timeout: int
+    username: str
+    password: str
+    num_resp_attributes: int
+    state_wrong_num_resp_attributes: int
+    request_attribute: Tuple[str, str]
+    max_response_time: Tuple[int, int]
+    expected_response: int
+    state_not_expected_response: int
+
+
+VERSION = '0.1.0-20240428'
+
+cmk_state = {
+    0: '',
+    1: '(!)',
+    2: '(!!)',
+    3: '(?)',
+}
+response_str = {
+    2: 'accept',
+    3: 'reject',
+}
+
+
+def parse_arguments(argv: Sequence[str]) -> Args:
+    def _av_pair(s):
+        try:
+            name, value = s.split(':')
+            return name, value
+        except ValueError:
+            raise ArgumentTypeError("AV-Pairs must be in the form of name:vale")
+
+    def _levels(s) -> Tuple[int, int]:
+        try:
+            warn, crit = s.split(',')
+            warn = int(warn)
+            crit = int(crit)
+            return warn, crit
+        except ValueError:
+            raise ArgumentTypeError("Levels must be in the form 'warn,crit' value")
+
     parser = ArgumentParser(
+        description='This is a (very) basic active RADIUS check for Check_mk. Tests if a RADIUS server is responsive '
+                    '(accept/reject/timeout). There is (limited) support to add AV-pairs to the RADIUS request.',
         formatter_class=ArgumentDefaultsHelpFormatter,
-        epilog=''
+        epilog=f'(c) thl-cmk[at]outlook[dot], Version: {VERSION}, For more information see: https://thl-cmk.hopto.org'
     )
+    #
+    # required request parameters
+    #
     parser.add_argument(
         '-H', '--host', required=True,
-        help='Host/IP-Address of RADIUS server to query (required)')
+        help='Host/IP-Address of RADIUS server to query (required)',
+    )
     parser.add_argument(
         '--secret', required=True,
-        help='secret RADIUS key')
+        help='secret RADIUS key',
+    )
     parser.add_argument(
-        '--username', default='dummyuser',
-        help='user name to test with')
+        '--username', default='dummyuser', required=True,
+        help='user name to test with',
+    )
     parser.add_argument(
-        '--password', default='dummyuser',
-        help='user password to test with')
+        '--password', default='dummypassword', required=True,
+        help='user password to test with',
+    )
+    #
+    # optional request parameters
+    #
     parser.add_argument(
-        '--authport', type=int, default=1812,
-        help='RADIUS authentication port to use.')
+        '--auth_port', type=int, default=1812,
+        help='RADIUS authentication port to use.',
+    )
     parser.add_argument(
         '--timeout', type=int, default=1,
-        help='RADIUS server timeout.')
+        help='RADIUS server timeout',
+    )
+    parser.add_argument(
+        '--request-attribute', nargs='*', type=_av_pair, action='append', default=[],
+        help='add request attribute in the form of "attribute-name:attribute-value" '
+             'ie: "Called-Station-Id:AA-BB-CC-DD-EE-FF". Repeat to add more attributes. '
+             'For valid attributes the dictionary file.',
+    )
+    #
+    # response parameters
+    #
+    parser.add_argument(
+        '--expected-response', type=int, choices=[2, 3],
+        help=' 2 -> Accepted, 3 -> Rejected',
+    )
+    parser.add_argument(
+        '--state-not-expected-response', type=int, choices=[0, 1, 2, 3], default=1,
+        help='Monitoring state: 0 -> OK, 1 -> WARN, 2 -> CRIT, 3 -> UNKNOWN',
+    )
+    parser.add_argument(
+        '--num-resp-attributes', type=int,
+        help='Expected number of response attributes',
+    )
+    parser.add_argument(
+        '--state-wrong-num-resp-attributes', type=int, choices=[0, 1, 2, 3], default=1,
+        help='Monitoring state: 0 -> OK, 1 -> WARN, 2 -> CRIT, 3 -> UNKNOWN',
+    )
+    parser.add_argument(
+        '--max-response-time', type=_levels,
+        help='Upper levels for response time in ms in the format WARN,CRIT time. ie: 10,50'
+    )
 
     args = parser.parse_args(argv)
     args.host = args.host.strip(' ')
@@ -63,80 +161,130 @@ def parse_arguments(argv: Sequence[str]) -> Namespace:
 
 def main(args=None):
     if args is None:
-        args = sys.argv[1:]  # without the path/plugin itself
+        args = sys_argv[1:]  # without the path/plugin itself
 
     args = parse_arguments(args)
 
     if no_radiuslib:
-        sys.stdout.write(
-            'To use this check plugin you need to install the python pyrad lib in your CMK python environment.\n'
+        sys_stdout.write(
+            'To use this check plugin you need to install the python pyrad lib in your CMK python environment.(!!!)\n'
         )
-        sys.exit(3)
+        sys_exit(3)
 
     omd_root = environ["OMD_ROOT"]
-    info_text = ''
-    long_output = ''
-    perf_data = ''
+    info_text = []
+    long_output = []
+    perf_data = []
     status = 0
 
-    rad_server = rad_client(
+    rad_server = radClient(
         server=args.host,
-        authport=args.authport,
+        authport=args.auth_port,
         secret=args.secret.encode('utf-8'),
-        dict=rad_dictionary(f'{omd_root}/local/lib/nagios/plugins/dictionary'),
+        # freeradius dictionaries are under /usr/share/freeradius/
+        dict=radDictionary(f'{omd_root}/local/lib/nagios/plugins/dictionary'),
         timeout=args.timeout,
     )
 
     rad_req = rad_server.CreateAuthPacket(
-        code=pyrad.packet.AccessRequest,
+        code=AccessRequest,
         User_Name=args.username,
         NAS_Identifier=args.host,
     )
     rad_req["User-Password"] = rad_req.PwCrypt(args.password)
+
+    # add optional request attributes
+    for av_pair in args.request_attribute:
+        name, value = av_pair[0]
+        try:
+            rad_req[name] = value
+        except TypeError:
+            sys_stdout.write(
+                f'WARNING: attribute value must be the real value not the name of the '
+                f'value: {value}{cmk_state[1]}{cmk_state[1]}'
+            )
+            status = max(status, 1)
+            continue
+
     before_request_time = time_ns()
     try:
         response = rad_server.SendPacket(rad_req)
-    except pyrad.client.Timeout as e:
-        status = 2
-        info_text = 'Radius request timeout'
-        long_output += f'\nRadius request timeout.\n{e}'
-    except socket.error as e:
-        status = 2
-        info_text = 'Network error'
-        long_output += f'\nNetwork error\n{e}'
+    except pyTimeout as e:
+        status = max(status, 2)
+        message = f'Radius request timeout{cmk_state[2]}'
+        info_text.append(message)
+        long_output.append(f'{message}\n{e}')
+    except socket_error as e:
+        status = max(status, 2)
+        message = f'Network error{cmk_state[2]}'
+        info_text.append(message)
+        long_output.append(f'{message}\n{e}')
     else:
-        request_time = (time_ns() - before_request_time) / 1000 / 1000 / 1000  # -> ns to seconds
-        match response.code:
-            case pyrad.packet.AccessAccept:
-                info_text += 'Response: access accept'
-                long_output += '\nResponse: access accept'
-                long_output += f'\nResponse code: {response.code}'
-                if response.has_key:
-                    long_output += f'\nNumber of attributes in response: {len(response.keys())}'
-                    long_output += f'\n\nResponse attributes:'
-                    for key in response.keys():
-                        long_output += f'\n{key}: {response.get(key)}'
+        # first: calculate response time
+        response_time = (time_ns() - before_request_time) / 1000 / 1000 / 1000  # -> ns to seconds
+
+        #
+        # second: check response code
+        message = f'Response: access {response_str.get(response.code, f"unknown ({response.code})")}'
+        if args.expected_response and response.code != args.expected_response:
+            message += f' (expected: {response_str[args.expected_response]}{cmk_state[args.state_not_expected_response]})'
+            status = max(status, args.state_not_expected_response)
+        info_text.append(message)
+        long_output.append(message)
+
+        # third: check response time
+        message = f'Response time {response_time * 1000:.0f}ms'
+        if args.max_response_time:
+            warn, crit = args.max_response_time
+            warn = warn
+            crit = crit
+            if response_time >= warn / 1000:
+                message += f' (WARN/CRIT at {warn}/{crit}'
+                if response_time >= crit / 1000:
+                    message += cmk_state[2]
+                    status = max(status, 2)
                 else:
-                    long_output += f'\nNo attributes in response: {len(response.keys())}'
-            case pyrad.packet.AccessReject:
-                info_text += 'Response: access reject'
-                long_output += '\nResponse: access reject'
-                long_output += f'\nResponse code: {response.code}'
-            case _:
-                info_text += f'Response: code unknown'
-                long_output += f'\nResponse: code unknown'
-                long_output += f'\nResponse code: {response.code}'
-                status = 3
-
-        perf_data += f'radius_request_time={request_time}'
-
-    info_text = info_text.strip(',').strip(' ')
-    sys.stdout.write(f'{info_text}\n{long_output} | {perf_data}\n')
+                    message += cmk_state[1]
+                    status = max(status, 1)
+            perf_data.append(f'radius_response_time={response_time};{warn};{crit};;')
+        else:
+            perf_data.append(f'radius_response_time={response_time}')
+        info_text.append(message)
+        long_output.append(message)
+
+        if response.code == AccessAccept:
+            #
+            # forth: check return attributes
+            if response.has_key:
+                message = f'Number of attributes in response: {len(response.keys())}'
+            else:
+                message = long_output.append('No return attributes in response')
 
+            if args.num_resp_attributes and len(response.keys()) != args.num_resp_attributes:
+                message += f' (expected {args.num_resp_attributes}{cmk_state[args.state_wrong_num_resp_attributes]})'
+                status = max(status, args.state_wrong_num_resp_attributes)
+                info_text.append(message)
+
+            long_output.append(message)
+
+            if response.has_key:
+                long_output.append('\nResponse attributes:')
+                for key in response.keys():
+                    long_output.append(f'{key}: {response.get(key)}')
+
+    #
+    # format output data
+    info_text = ', '.join(info_text)
+    long_output = '\n'.join(long_output)
+    perf_data = '|'.join(perf_data)
+    if perf_data:
+        sys_stdout.write(f'{info_text}\n{long_output}|{perf_data}\n')
+    else:
+        sys_stdout.write(f'{info_text}\n{long_output}')
     return status
 
 
 if __name__ == '__main__':
     cmk.utils.password_store.replace_passwords()
     exitcode = main()
-    sys.exit(exitcode)
+    sys_exit(exitcode)
diff --git a/source/lib/nagios/plugins/dictionary b/source/lib/nagios/plugins/dictionary
old mode 100644
new mode 100755
index 30fb528..125de2f
--- a/source/lib/nagios/plugins/dictionary
+++ b/source/lib/nagios/plugins/dictionary
@@ -230,7 +230,7 @@ ATTRIBUTE	Autz-Type		1011	integer
 #	User Types
 
 VALUE		Service-Type		Login-User		1
-VALUE		Service-Type		Framed-User		2
+VALUE		Service-Type		pdate		2
 VALUE		Service-Type		Callback-Login-User	3
 VALUE		Service-Type		Callback-Framed-User	4
 VALUE		Service-Type		Outbound-User		5
diff --git a/source/lib/nagios/plugins/dictionary.freeradius b/source/lib/nagios/plugins/dictionary.freeradius
old mode 100644
new mode 100755
diff --git a/source/packages/check_radius b/source/packages/check_radius
index b14b394..bff1f3c 100644
--- a/source/packages/check_radius
+++ b/source/packages/check_radius
@@ -1,5 +1,12 @@
 {'author': 'Th.L. (thl-cmk[at]outlook[dot]com)',
- 'description': 'active RADIUS check\n',
+ 'description': 'This is a (very) basic active RADIUS check:\n'
+                '\n'
+                ' - tests if a RADIUS server is responsive '
+                '(accept/reject/timeout).\n'
+                ' - (limited) support to add AV-pairs to the RADIUS request\n'
+                ' - checks response code, response time and number of response '
+                'attributes\n'
+                '\n',
  'download_url': 'https://thl-cmk.hopto.org',
  'files': {'checks': ['check_radius'],
            'gui': ['metrics/check_radius.py',
@@ -9,7 +16,7 @@
                    'nagios/plugins/dictionary.freeradius']},
  'name': 'check_radius',
  'title': 'Check RADIUS',
- 'version': '0.0.1-20240421',
+ 'version': '0.1.0-20240428',
  'version.min_required': '2.2.0b1',
  'version.packaged': '2.2.0p24',
  'version.usable_until': None}
-- 
GitLab