From 80c4ed7af26b5b6a581ccc8e0a9e65e53e43cc4c Mon Sep 17 00:00:00 2001 From: John Dennis Date: Tue, 6 Nov 2007 16:26:10 -0500 Subject: remove offensive use of rpm add the radiusprofile to the list of objectclasses used when creating a user --- ipa-python/ipautil.py | 36 ------------------------------------ 1 file changed, 36 deletions(-) (limited to 'ipa-python') diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py index 96a74d75..e7f59419 100644 --- a/ipa-python/ipautil.py +++ b/ipa-python/ipautil.py @@ -30,7 +30,6 @@ from string import lower import re import xmlrpclib import datetime -import rpm def realm_to_suffix(realm_name): s = realm_name.split(".") @@ -332,38 +331,3 @@ def parse_generalized_time(timestr): except ValueError: return None -#------------------------------------------------------------------------------- - -def get_rpm_nvr_from_header(hdr): - 'Given an RPM header return the package NVR as a string' - name = hdr['name'] - version = hdr['version'] - release = hdr['release'] - - return '%s-%s-%s' % (name, version, release) - -def get_rpm_nvr_by_name(name): - if name is None: - return None - - nvr = None - try: - ts = rpm.ts() - mi = ts.dbMatch(rpm.RPMTAG_NAME, name) - for h in mi: - nvr = get_rpm_nvr_from_header(h) - break - except: - logging.exception('failed to retrieve rpm info for %s', name) - return nvr - -def split_rpm_nvr(nvr): - components = nvr.split('-') - release = components[-1] - version = components[-2] - name = '-'.join(components[:-2]) - return (name, version, release) - - - - -- cgit From eab5a89d4a3e8159348b5a709cd4401c9784f058 Mon Sep 17 00:00:00 2001 From: John Dennis Date: Tue, 13 Nov 2007 20:05:02 -0500 Subject: ipa-addradiusclient now working --- ipa-python/ipaclient.py | 11 +++++++++++ ipa-python/rpcclient.py | 13 +++++++++++++ 2 files changed, 24 insertions(+) (limited to 'ipa-python') diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py index 659ff995..3c54d6ab 100644 --- a/ipa-python/ipaclient.py +++ b/ipa-python/ipaclient.py @@ -331,3 +331,14 @@ class IPAClient: entries.append(user.User(e)) return entries + + def add_radius_client(self,client): + client_dict = client.toDict() + + # dn is set on the server-side + del client_dict['dn'] + + # convert to a regular dict before sending + result = self.transport.add_radius_client(client_dict) + return result + diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py index 871c3725..f0ffec02 100644 --- a/ipa-python/rpcclient.py +++ b/ipa-python/rpcclient.py @@ -591,3 +591,16 @@ class RPCClient: raise xmlrpclib.Fault(value, msg) return ipautil.unwrap_binary_data(result) + + def add_radius_client(self,client): + server = self.setup_server() + + try: + result = server.add_radius_client(ipautil.wrap_binary_data(client)) + except xmlrpclib.Fault, fault: + raise ipaerror.gen_exception(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return ipautil.unwrap_binary_data(result) + -- cgit From c4bf859bfb95a1e0ebc4501317ff4e8665ac590d Mon Sep 17 00:00:00 2001 From: John Dennis Date: Tue, 13 Nov 2007 20:22:18 -0500 Subject: move radius client utility code to common radius_client.py so it can be shared --- ipa-python/radius_client.py | 131 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) (limited to 'ipa-python') diff --git a/ipa-python/radius_client.py b/ipa-python/radius_client.py index 44deb746..d21273d8 100644 --- a/ipa-python/radius_client.py +++ b/ipa-python/radius_client.py @@ -1,7 +1,138 @@ +# Authors: John Dennis +# +# Copyright (C) 2007 Red Hat +# see file 'COPYING' for use and warranty information +# +# 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; version 2 only +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import getpass +import re + from ipa.entity import Entity +__all__ = ['RadiusClient', + 'get_secret', + 'validate_ip_addr', + 'validate_secret', + 'validate_name', + 'validate_nastype', + 'validate_desc', + ] + +#------------------------------------------------------------------------------ + +dotted_octet_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)\.(\d+)(/(\d+))?$") +dns_RE = re.compile(r"^[a-zA-Z][a-zA-Z.-]+$") +# secret, name, nastype all have 31 char max in freeRADIUS, max ip address len is 255 +valid_secret_len = (1,31) +valid_name_len = (1,31) +valid_nastype_len = (1,31) +valid_ip_addr_len = (1,255) + +valid_ip_addr_msg = "IP address must be either a DNS name or a dotted octet with optional mask" +valid_desc_msg = "Description must text string" + +#------------------------------------------------------------------------------ + class RadiusClient(Entity): def __init2__(self): pass + +#------------------------------------------------------------------------------ + +def get_secret(): + valid = False + while (not valid): + secret = getpass.getpass("Enter Secret: ") + confirm = getpass.getpass("Confirm Secret: ") + if (secret != confirm): + print "Secrets do not match" + continue + valid = True + return secret + +#------------------------------------------------------------------------------ + +def valid_ip_addr(text): + + # is it a dotted octet? If so there should be 4 integers seperated + # by a dot and each integer should be between 0 and 255 + # there may be an optional mask preceded by a slash (e.g. 1.2.3.4/24) + match = dotted_octet_RE.search(text) + if match: + # dotted octet notation + i = 1 + while i <= 4: + octet = int(match.group(i)) + if octet > 255: return False + i += 1 + if match.group(5): + mask = int(match.group(6)) + if mask <= 32: + return True + else: + return False + return True + else: + # DNS name, can contain letters, dot and hypen + if dns_RE.search(text): return True + return False + +def validate_length(value, limits): + length = len(value) + if length < limits[0] or length > limits[1]: + return False + return True + +def valid_length_msg(name, limits): + return "%s length must be at least %d and not more than %d" % (name, limits[0], limits[1]) + +#------------------------------------------------------------------------------ + +def validate_ip_addr(ip_addr): + if not validate_length(ip_addr, valid_ip_addr_len): + print valid_length_msg('ip address', valid_ip_addr_len) + return False + if not valid_ip_addr(ip_addr): + print valid_ip_addr_msg + return False + return True + +def validate_secret(secret): + if not validate_length(secret, valid_secret_len): + print valid_length_msg('secret', valid_secret_len) + return False + return True + +def validate_name(name): + if not validate_length(name, valid_name_len): + print valid_length_msg('name', valid_name_len) + return False + return True + +def validate_nastype(nastype): + if not validate_length(nastype, valid_nastype_len): + print valid_length_msg('NAS Type', valid_nastype_len) + return False + return True + +def validate_desc(desc): + if ipavalidate.plain(desc, notEmpty=True) != 0: + print valid_desc_msg + return False + return True + -- cgit From c24da12fe1c68cc2ea5211763e8904b646a17d95 Mon Sep 17 00:00:00 2001 From: John Dennis Date: Wed, 14 Nov 2007 00:04:19 -0500 Subject: radius client modify and delete work --- ipa-python/ipaclient.py | 13 +++++++++++++ ipa-python/radius_client.py | 12 ++++++------ ipa-python/rpcclient.py | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 6 deletions(-) (limited to 'ipa-python') diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py index 3c54d6ab..2d9b7a46 100644 --- a/ipa-python/ipaclient.py +++ b/ipa-python/ipaclient.py @@ -27,6 +27,7 @@ import user import group import ipa import config +import radius_client class IPAClient: @@ -332,6 +333,11 @@ class IPAClient: return entries +# radius support + def get_radius_client_by_ip_addr(self,ip_addr,sattrs=None): + result = self.transport.get_radius_client_by_ip_addr(ip_addr,sattrs) + return radius_client.RadiusClient(result) + def add_radius_client(self,client): client_dict = client.toDict() @@ -342,3 +348,10 @@ class IPAClient: result = self.transport.add_radius_client(client_dict) return result + def update_radius_client(self,client): + result = self.transport.update_radius_client(client.origDataDict(), client.toDict()) + return result + + def delete_radius_client(self,ip_addr): + return self.transport.delete_radius_client(ip_addr) + diff --git a/ipa-python/radius_client.py b/ipa-python/radius_client.py index d21273d8..2709c4d9 100644 --- a/ipa-python/radius_client.py +++ b/ipa-python/radius_client.py @@ -33,15 +33,15 @@ __all__ = ['RadiusClient', #------------------------------------------------------------------------------ -dotted_octet_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)\.(\d+)(/(\d+))?$") -dns_RE = re.compile(r"^[a-zA-Z][a-zA-Z.-]+$") +dotted_octet_re = re.compile(r"^(\d+)\.(\d+)\.(\d+)\.(\d+)(/(\d+))?$") +dns_re = re.compile(r"^[a-zA-Z][a-zA-Z0-9.-]+$") # secret, name, nastype all have 31 char max in freeRADIUS, max ip address len is 255 valid_secret_len = (1,31) valid_name_len = (1,31) valid_nastype_len = (1,31) valid_ip_addr_len = (1,255) -valid_ip_addr_msg = "IP address must be either a DNS name or a dotted octet with optional mask" +valid_ip_addr_msg = "IP address must be either a DNS name (letters,digits,dot,hyphen, beginning with a letter),or a dotted octet followed by an optional mask (e.g 192.168.1.0/24)" valid_desc_msg = "Description must text string" #------------------------------------------------------------------------------ @@ -72,7 +72,7 @@ def valid_ip_addr(text): # is it a dotted octet? If so there should be 4 integers seperated # by a dot and each integer should be between 0 and 255 # there may be an optional mask preceded by a slash (e.g. 1.2.3.4/24) - match = dotted_octet_RE.search(text) + match = dotted_octet_re.search(text) if match: # dotted octet notation i = 1 @@ -88,8 +88,8 @@ def valid_ip_addr(text): return False return True else: - # DNS name, can contain letters, dot and hypen - if dns_RE.search(text): return True + # DNS name, can contain letters, numbers, dot and hypen, must start with a letter + if dns_re.search(text): return True return False def validate_length(value, limits): diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py index f0ffec02..bf3f1bd9 100644 --- a/ipa-python/rpcclient.py +++ b/ipa-python/rpcclient.py @@ -592,6 +592,21 @@ class RPCClient: return ipautil.unwrap_binary_data(result) +# radius support + + def get_radius_client_by_ip_addr(self,ip_addr,sattrs=None): + server = self.setup_server() + if sattrs is None: + sattrs = "__NONE__" + try: + result = server.get_radius_client_by_ip_addr(ip_addr, sattrs) + except xmlrpclib.Fault, fault: + raise ipaerror.gen_exception(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return ipautil.unwrap_binary_data(result) + def add_radius_client(self,client): server = self.setup_server() @@ -603,4 +618,30 @@ class RPCClient: raise xmlrpclib.Fault(value, msg) return ipautil.unwrap_binary_data(result) + + def update_radius_client(self,oldclient,newclient): + server = self.setup_server() + + try: + result = server.update_radius_client(ipautil.wrap_binary_data(oldclient), + ipautil.wrap_binary_data(newclient)) + except xmlrpclib.Fault, fault: + raise ipaerror.gen_exception(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return ipautil.unwrap_binary_data(result) + + def delete_radius_client(self,ip_addr): + server = self.setup_server() + + try: + result = server.delete_radius_client(ip_addr) + except xmlrpclib.Fault, fault: + raise ipaerror.gen_exception(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return ipautil.unwrap_binary_data(result) + -- cgit From c4dbe6433da131b618ea2bfd9f0bfc5076c40ff2 Mon Sep 17 00:00:00 2001 From: John Dennis Date: Wed, 14 Nov 2007 15:32:08 -0500 Subject: add ipa-findradiusclient search --- ipa-python/ipaclient.py | 11 +++++++++++ ipa-python/rpcclient.py | 14 ++++++++++++++ 2 files changed, 25 insertions(+) (limited to 'ipa-python') diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py index 2d9b7a46..f487bec2 100644 --- a/ipa-python/ipaclient.py +++ b/ipa-python/ipaclient.py @@ -355,3 +355,14 @@ class IPAClient: def delete_radius_client(self,ip_addr): return self.transport.delete_radius_client(ip_addr) + def find_radius_clients(self, criteria, sattrs=None, searchlimit=0, timelimit=-1): + result = self.transport.find_radius_clients(criteria, sattrs, searchlimit, timelimit) + counter = result[0] + + users = [counter] + for attrs in result[1:]: + if attrs is not None: + users.append(user.User(attrs)) + + return users + diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py index bf3f1bd9..e756058a 100644 --- a/ipa-python/rpcclient.py +++ b/ipa-python/rpcclient.py @@ -645,3 +645,17 @@ class RPCClient: return ipautil.unwrap_binary_data(result) + def find_radius_clients(self, criteria, sattrs=None, searchlimit=0, timelimit=-1): + server = self.setup_server() + try: + # None values are not allowed in XML-RPC + if sattrs is None: + sattrs = "__NONE__" + result = server.find_radius_clients(criteria, sattrs, searchlimit, timelimit) + except xmlrpclib.Fault, fault: + raise ipaerror.gen_exception(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return ipautil.unwrap_binary_data(result) + -- cgit From d98686e96758870cb4a56d41fb0aaae54d4067c5 Mon Sep 17 00:00:00 2001 From: John Dennis Date: Wed, 21 Nov 2007 13:11:10 -0500 Subject: Add radius profile implementations: get_radius_profile_by_uid add_radius_profile update_radius_profile delete_radius_profile find_radius_profiles Rewrite command line arg handling, now support pair entry, interactive mode with auto completion, reading pairs from a file, better handling of mandatory values, better help, long arg names now match attribute name in pairs Establish mappings for all attributes and names used in clients and profiles Add notion of containers to radius clients and profiles in LDAP Move common code, variables, constants, and strings into the files radius_client.py, radius_util.py, ipautil.py to eliminate redundant elements which could get out of sync if modified and to provide access to other code which might benefit from using these items in the future. Add utility functions: format_list() parse_key_value_pairs() Add utility class: AttributeValueCompleter Unify attribute usage in radius ldap schema --- ipa-python/ipaclient.py | 16 +-- ipa-python/ipautil.py | 330 ++++++++++++++++++++++++++++++++++++++++++++ ipa-python/radius_client.py | 39 +++++- ipa-python/radius_util.py | 231 +++++++++++++++++++++++++++++++ ipa-python/rpcclient.py | 24 ++-- 5 files changed, 616 insertions(+), 24 deletions(-) create mode 100644 ipa-python/radius_util.py (limited to 'ipa-python') diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py index f487bec2..e9b0002f 100644 --- a/ipa-python/ipaclient.py +++ b/ipa-python/ipaclient.py @@ -334,29 +334,29 @@ class IPAClient: return entries # radius support - def get_radius_client_by_ip_addr(self,ip_addr,sattrs=None): - result = self.transport.get_radius_client_by_ip_addr(ip_addr,sattrs) + def get_radius_client_by_ip_addr(self, ip_addr, container=None, sattrs=None): + result = self.transport.get_radius_client_by_ip_addr(ip_addr, container, sattrs) return radius_client.RadiusClient(result) - def add_radius_client(self,client): + def add_radius_client(self,client, container=None): client_dict = client.toDict() # dn is set on the server-side del client_dict['dn'] # convert to a regular dict before sending - result = self.transport.add_radius_client(client_dict) + result = self.transport.add_radius_client(client_dict, container) return result def update_radius_client(self,client): result = self.transport.update_radius_client(client.origDataDict(), client.toDict()) return result - def delete_radius_client(self,ip_addr): - return self.transport.delete_radius_client(ip_addr) + def delete_radius_client(self, ip_addr, container=None): + return self.transport.delete_radius_client(ip_addr, container) - def find_radius_clients(self, criteria, sattrs=None, searchlimit=0, timelimit=-1): - result = self.transport.find_radius_clients(criteria, sattrs, searchlimit, timelimit) + def find_radius_clients(self, criteria, container=None, sattrs=None, searchlimit=0, timelimit=-1): + result = self.transport.find_radius_clients(criteria, container, sattrs, searchlimit, timelimit) counter = result[0] users = [counter] diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py index e7f59419..407406de 100644 --- a/ipa-python/ipautil.py +++ b/ipa-python/ipautil.py @@ -25,6 +25,10 @@ import logging import subprocess import os import stat +import copy +import readline +import traceback +from types import * from string import lower import re @@ -331,3 +335,329 @@ def parse_generalized_time(timestr): except ValueError: return None + +def format_list(items, quote=None, page_width=80): + '''Format a list of items formatting them so they wrap to fit the + available width. The items will be sorted. + + The items may optionally be quoted. The quote parameter may either be + a string, in which case it is added before and after the item. Or the + quote parameter may be a pair (either a tuple or list). In this case + quote[0] is left hand quote and quote[1] is the right hand quote. + ''' + left_quote = right_quote = '' + num_items = len(items) + if not num_items: return text + + if quote is not None: + if type(quote) in StringTypes: + left_quote = right_quote = quote + elif type(quote) is TupleType or type(quote) is ListType: + left_quote = quote[0] + right_quote = quote[1] + + max_len = max(map(len, items)) + max_len += len(left_quote) + len(right_quote) + num_columns = (page_width + max_len) / (max_len+1) + num_rows = (num_items + num_columns - 1) / num_columns + items.sort() + + rows = [''] * num_rows + i = row = col = 0 + + while i < num_items: + row = 0 + if col == 0: + separator = '' + else: + separator = ' ' + + while i < num_items and row < num_rows: + rows[row] += "%s%*s" % (separator, -max_len, "%s%s%s" % (left_quote, items[i], right_quote)) + i += 1 + row += 1 + col += 1 + return '\n'.join(rows) + +key_value_re = re.compile("([^\s=]+)\s*=\s*((\S+)|(?P['\\\"])((?P=quote)|(.*?[^\\\])(?P=quote)))") +def parse_key_value_pairs(input): + ''' Given a string composed of key=value pairs parse it and return + a dict of the key/value pairs. Keys must be a word, a key must be followed + by an equal sign (=) and a value. The value may be a single word or may be + quoted. Quotes may be either single or double quotes, but must be balanced. + Inside the quoted text the same quote used to start the quoted value may be + used if it is escaped by preceding it with a backslash (\). + White space between the key, the equal sign, and the value is ignored. + Values are always strings. Empty values must be specified with an empty + quoted string, it's value after parsing will be an empty string. + + Example: The string + + arg0 = '' arg1 = 1 arg2='two' arg3 = "three's a crowd" arg4 = "this is a \" quote" + + will produce + + arg0= arg1=1 + arg2=two + arg3=three's a crowd + arg4=this is a " quote + ''' + + kv_dict = {} + for match in key_value_re.finditer(input): + key = match.group(1) + quote = match.group('quote') + if match.group(5): + value = match.group(6) + if value is None: value = '' + value = re.sub('\\\%s' % quote, quote, value) + else: + value = match.group(2) + kv_dict[key] = value + return kv_dict + +class AttributeValueCompleter: + ''' + Gets input from the user in the form "lhs operator rhs" + TAB completes partial input. + lhs completes to a name in @lhs_names + The lhs is fully parsed if a lhs_delim delimiter is seen, then TAB will + complete to the operator and a default value. + Default values for a lhs value can specified as: + - a string, all lhs values will use this default + - a dict, the lhs value is looked up in the dict to return the default or None + - a function with a single arg, the lhs value, it returns the default or None + + After creating the completer you must open it to set the terminal + up, Then get a line of input from the user by calling read_input() + which returns two values, the lhs and rhs, which might be None if + lhs or rhs was not parsed. After you are done getting input you + should close the completer to restore the terminal. + + Example: (note this is essentially what the convenience function get_pairs() does) + + This will allow the user to autocomplete foo & foobar, both have + defaults defined in a dict. In addition the foobar attribute must + be specified before the prompting loop will exit. Also, this + example show how to require that each attrbute entered by the user + is valid. + + attrs = ['foo', 'foobar'] + defaults = {'foo' : 'foo_default', 'foobar' : 'foobar_default'} + mandatory_attrs = ['foobar'] + + c = AttributeValueCompleter(attrs, defaults) + c.open() + mandatory_attrs_remaining = copy.copy(mandatory_attrs) + + while True: + if mandatory_attrs_remaining: + attribute, value = c.read_input("Enter: ", mandatory_attrs_remaining[0]) + try: + mandatory_attrs_remaining.remove(attribute) + except ValueError: + pass + else: + attribute, value = c.read_input("Enter: ") + if attribute is None: + # Are we done? + if mandatory_attrs_remaining: + print "ERROR, you must specify: %s" % (','.join(mandatory_attrs_remaining)) + continue + else: + break + if attribute not in attrs: + print "ERROR: %s is not a valid attribute" % (attribute) + else: + print "got '%s' = '%s'" % (attribute, value) + + c.close() + print "exiting..." + ''' + + def __init__(self, lhs_names, default_value=None, lhs_regexp=r'^\s*(?P[^ =]+)', lhs_delims=' =', + operator='=', strip_rhs=True): + self.lhs_names = lhs_names + self.default_value = default_value + # lhs_regexp must have named group 'lhs' which returns the contents of the lhs + self.lhs_regexp = lhs_regexp + self.lhs_re = re.compile(self.lhs_regexp) + self.lhs_delims = lhs_delims + self.operator = operator + self.strip_rhs = strip_rhs + self._reset() + + def _reset(self): + self.lhs = None + self.lhs_complete = False + self.operator_complete = False + self.rhs = None + + def open(self): + # Save state + self.prev_completer = readline.get_completer() + self.prev_completer_delims = readline.get_completer_delims() + + # Set up for ourself + readline.parse_and_bind("tab: complete") + readline.set_completer(self.complete) + readline.set_completer_delims(self.lhs_delims) + + def close(self): + # Restore previous state + readline.set_completer_delims(self.prev_completer_delims) + readline.set_completer(self.prev_completer) + + def _debug(self): + print >> output_fd, "lhs='%s' lhs_complete=%s operator='%s' operator_complete=%s rhs='%s'" % \ + (self.lhs, self.lhs_complete, self.operator, self.operator_complete, self.rhs) + + + def parse_input(self): + '''We are looking for 3 tokens: + Extract as much of each token as possible. + Set flags indicating if token is fully parsed. + ''' + try: + self._reset() + buf_len = len(self.line_buffer) + pos = 0 + lhs_match = self.lhs_re.search(self.line_buffer, pos) + if not lhs_match: return # no lhs content + self.lhs = lhs_match.group('lhs') # get lhs contents + pos = lhs_match.end('lhs') # new scanning position + if pos == buf_len: return # nothing after lhs, lhs incomplete + self.lhs_complete = True # something trails the lhs, lhs is complete + operator_beg = self.line_buffer.find(self.operator, pos) # locate operator + if operator_beg == -1: return # did not find the operator + self.operator_complete = True # operator fully parsed + operator_end = operator_beg + len(self.operator) + pos = operator_end # step over the operator + self.rhs = self.line_buffer[pos:] + except Exception, e: + traceback.print_exc() + print "Exception in %s.parse_input(): %s" % (self.__class__.__name__, e) + + def get_default_value(self): + '''default_value can be a string, a dict, or a function. + If it's a string it's a global default for all attributes. + If it's a dict the default is looked up in the dict index by attribute. + If it's a function, the function is called with 1 parameter, the attribute + and it should return the default value for the attriubte or None''' + + if not self.lhs_complete: raise ValueError("attribute not parsed") + default_value_type = type(self.default_value) + if default_value_type is DictType: + return self.default_value.get(self.lhs, None) + elif default_value_type is FunctionType: + return self.default_value(self.lhs) + elif default_value_type is StringsType: + return self.default_value + else: + return None + + def get_lhs_completions(self, text): + if text: + self.completions = [lhs for lhs in self.lhs_names if lhs.startswith(text)] + else: + self.completions = self.lhs_names + + def complete(self, text, state): + self.line_buffer= readline.get_line_buffer() + self.parse_input() + if not self.lhs_complete: + # lhs is not complete, set up to complete the lhs + if state == 0: + beg = readline.get_begidx() + end = readline.get_endidx() + self.get_lhs_completions(self.line_buffer[beg:end]) + if state >= len(self.completions): return None + return self.completions[state] + + + elif not self.operator_complete: + # lhs is complete, but the operator is not so we complete + # by inserting the operator manually. + # Also try to complete the default value at this time. + readline.insert_text('%s ' % self.operator) + default_value = self.get_default_value() + if default_value is not None: + readline.insert_text(default_value) + readline.redisplay() + return None + else: + # lhs and operator are complete, if the the rhs is blank + # (either empty or only only whitespace) then attempt + # to complete by inserting the default value, otherwise + # there is nothing we can complete to so we're done. + if self.rhs.strip(): + return None + default_value = self.get_default_value() + if default_value is not None: + readline.insert_text(default_value) + readline.redisplay() + return None + + def pre_input_hook(self): + readline.insert_text('%s %s ' % (self.initial_lhs, self.operator)) + readline.redisplay() + + def read_input(self, prompt, initial_lhs=None): + self.initial_lhs = initial_lhs + try: + self._reset() + if initial_lhs is None: + readline.set_pre_input_hook(None) + else: + readline.set_pre_input_hook(self.pre_input_hook) + self.line_buffer = raw_input(prompt).strip() + self.parse_input() + if self.strip_rhs and self.rhs is not None: + return self.lhs, self.rhs.strip() + else: + return self.lhs, self.rhs + except EOFError: + return None, None + + def get_pairs(self, prompt, mandatory_attrs=None, validate_callback=None, must_match=True, value_required=True): + pairs = {} + if mandatory_attrs: + mandatory_attrs_remaining = copy.copy(mandatory_attrs) + else: + mandatory_attrs_remaining = [] + + print "Enter name = value" + print "Press to accept, a blank line terminates input" + print "Pressing will auto completes name, assignment, and value" + print + while True: + if mandatory_attrs_remaining: + attribute, value = self.read_input(prompt, mandatory_attrs_remaining[0]) + else: + attribute, value = self.read_input(prompt) + if attribute is None: + # Are we done? + if mandatory_attrs_remaining: + print "ERROR, you must specify: %s" % (','.join(mandatory_attrs_remaining)) + continue + else: + break + if value is None: + if value_required: + print "ERROR: you must specify a value for %s" % attribute + continue + else: + if must_match and attribute not in self.lhs_names: + print "ERROR: %s is not a valid name" % (attribute) + continue + if validate_callback is not None: + if not validate_callback(attribute, value): + print "ERROR: %s is not valid for %s" % (value, attribute) + continue + try: + mandatory_attrs_remaining.remove(attribute) + except ValueError: + pass + + pairs[attribute] = value + return pairs diff --git a/ipa-python/radius_client.py b/ipa-python/radius_client.py index 2709c4d9..907e0210 100644 --- a/ipa-python/radius_client.py +++ b/ipa-python/radius_client.py @@ -21,6 +21,7 @@ import getpass import re from ipa.entity import Entity +import ipa.ipavalidate as ipavalidate __all__ = ['RadiusClient', 'get_secret', @@ -29,6 +30,7 @@ __all__ = ['RadiusClient', 'validate_name', 'validate_nastype', 'validate_desc', + 'validate', ] #------------------------------------------------------------------------------ @@ -41,7 +43,10 @@ valid_name_len = (1,31) valid_nastype_len = (1,31) valid_ip_addr_len = (1,255) -valid_ip_addr_msg = "IP address must be either a DNS name (letters,digits,dot,hyphen, beginning with a letter),or a dotted octet followed by an optional mask (e.g 192.168.1.0/24)" +valid_ip_addr_msg = '''\ +IP address must be either a DNS name (letters,digits,dot,hyphen, beginning with +a letter),or a dotted octet followed by an optional mask (e.g 192.168.1.0/24)''' + valid_desc_msg = "Description must text string" #------------------------------------------------------------------------------ @@ -101,38 +106,60 @@ def validate_length(value, limits): def valid_length_msg(name, limits): return "%s length must be at least %d and not more than %d" % (name, limits[0], limits[1]) +def err_msg(variable, variable_name=None): + if variable_name is None: variable_name = 'value' + print "ERROR: %s = %s" % (variable_name, variable) + #------------------------------------------------------------------------------ -def validate_ip_addr(ip_addr): +def validate_ip_addr(ip_addr, variable_name=None): if not validate_length(ip_addr, valid_ip_addr_len): + err_msg(ip_addr, variable_name) print valid_length_msg('ip address', valid_ip_addr_len) return False if not valid_ip_addr(ip_addr): + err_msg(ip_addr, variable_name) print valid_ip_addr_msg return False return True -def validate_secret(secret): +def validate_secret(secret, variable_name=None): if not validate_length(secret, valid_secret_len): + err_msg(secret, variable_name) print valid_length_msg('secret', valid_secret_len) return False return True -def validate_name(name): +def validate_name(name, variable_name=None): if not validate_length(name, valid_name_len): + err_msg(name, variable_name) print valid_length_msg('name', valid_name_len) return False return True -def validate_nastype(nastype): +def validate_nastype(nastype, variable_name=None): if not validate_length(nastype, valid_nastype_len): + err_msg(nastype, variable_name) print valid_length_msg('NAS Type', valid_nastype_len) return False return True -def validate_desc(desc): +def validate_desc(desc, variable_name=None): if ipavalidate.plain(desc, notEmpty=True) != 0: print valid_desc_msg return False return True +def validate(attribute, value): + if attribute == 'Client-IP-Address': + return validate_ip_addr(value, attribute) + if attribute == 'Secret': + return validate_secret(value, attribute) + if attribute == 'NAS-Type': + return validate_nastype(value, attribute) + if attribute == 'Name': + return validate_name(value, attribute) + if attribute == 'Description': + return validate_desc(value, attribute) + return True + diff --git a/ipa-python/radius_util.py b/ipa-python/radius_util.py new file mode 100644 index 00000000..482b9f19 --- /dev/null +++ b/ipa-python/radius_util.py @@ -0,0 +1,231 @@ +# Authors: John Dennis +# +# Copyright (C) 2007 Red Hat +# see file 'COPYING' for use and warranty information +# +# 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; version 2 only +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import sys +import os +import re +import ldap +import ldap.filter + +from ipa import ipautil + + +__all__ = [ + 'RADIUS_PKG_NAME', + 'RADIUS_PKG_CONFIG_DIR', + 'RADIUS_SERVICE_NAME', + 'RADIUS_USER', + 'RADIUS_IPA_KEYTAB_FILEPATH', + 'RADIUS_LDAP_ATTR_MAP_FILEPATH', + 'RADIUSD_CONF_FILEPATH', + 'RADIUSD_CONF_TEMPLATE_FILEPATH', + 'RADIUSD', + + 'clients_container', + 'radius_clients_basedn', + 'radius_client_filter', + 'radius_client_dn', + + 'profiles_container', + 'radius_profiles_basedn', + 'radius_profile_filter', + 'radius_profile_dn', + + 'client_ldap_attr_to_name', + 'client_name_to_ldap_attr', + + 'read_pairs_file', +] + +#------------------------------------------------------------------------------ + +RADIUS_PKG_NAME = 'freeradius' +RADIUS_PKG_CONFIG_DIR = '/etc/raddb' + +RADIUS_SERVICE_NAME = 'radius' +RADIUS_USER = 'radiusd' + +RADIUS_IPA_KEYTAB_FILEPATH = os.path.join(RADIUS_PKG_CONFIG_DIR, 'ipa.keytab') +RADIUS_LDAP_ATTR_MAP_FILEPATH = os.path.join(RADIUS_PKG_CONFIG_DIR, 'ldap.attrmap') +RADIUSD_CONF_FILEPATH = os.path.join(RADIUS_PKG_CONFIG_DIR, 'radiusd.conf') +RADIUSD_CONF_TEMPLATE_FILEPATH = os.path.join(ipautil.SHARE_DIR, 'radius.radiusd.conf.template') + +RADIUSD = '/usr/sbin/radiusd' + +#------------------------------------------------------------------------------ + +def reverse_map_dict(src_dict): + reverse_dict = {} + + for k,v in src_dict.items(): + if reverse_dict.has_key(v): + raise ValueError("reverse_map_dict: collision on (%s) with values (%s),(%s)" % \ + v, reverse_dict[v], src_dict[k]) + reverse_dict[v] = k + return reverse_dict + +#------------------------------------------------------------------------------ + +client_ldap_attr_to_name = ipautil.CIDict({ + 'radiusClientIPAddress' : 'Client-IP-Address', + 'radiusClientSecret' : 'Secret', + 'radiusClientNASType' : 'NAS-Type', + 'radiusClientShortName' : 'Name', + 'description' : 'Description', + }) + +client_name_to_ldap_attr = reverse_map_dict(client_ldap_attr_to_name) + +#------------------------------------------------------------------------------ + +profile_ldap_attr_to_name = { + 'radiusArapFeatures' : 'Arap-Features', + 'radiusArapSecurity' : 'Arap-Security', + 'radiusArapZoneAccess' : 'Arap-Zone-Access', + 'radiusAuthType' : 'Auth-Type', + 'radiusCallbackId' : 'Callback-Id', + 'radiusCallbackNumber' : 'Callback-Number', + 'radiusCalledStationId' : 'Called-Station-Id', + 'radiusCallingStationId' : 'Calling-Station-Id', + 'radiusClass' : 'Class', + 'radiusClientIPAddress' : 'Client-IP-Address', + 'radiusExpiration' : 'Expiration', + 'radiusFilterId' : 'Filter-Id', + 'radiusFramedAppleTalkLink' : 'Framed-AppleTalk-Link', + 'radiusFramedAppleTalkNetwork' : 'Framed-AppleTalk-Network', + 'radiusFramedAppleTalkZone' : 'Framed-AppleTalk-Zone', + 'radiusFramedCompression' : 'Framed-Compression', + 'radiusFramedIPAddress' : 'Framed-IP-Address', + 'radiusFramedIPNetmask' : 'Framed-IP-Netmask', + 'radiusFramedIPXNetwork' : 'Framed-IPX-Network', + 'radiusFramedMTU' : 'Framed-MTU', + 'radiusFramedProtocol' : 'Framed-Protocol', + 'radiusFramedRoute' : 'Framed-Route', + 'radiusFramedRouting' : 'Framed-Routing', + 'radiusGroupName' : 'Group-Name', + 'radiusHint' : 'Hint', + 'radiusHuntgroupName' : 'Huntgroup-Name', + 'radiusIdleTimeout' : 'Idle-Timeout', + 'radiusLoginIPHost' : 'Login-IP-Host', + 'radiusLoginLATGroup' : 'Login-LAT-Group', + 'radiusLoginLATNode' : 'Login-LAT-Node', + 'radiusLoginLATPort' : 'Login-LAT-Port', + 'radiusLoginLATService' : 'Login-LAT-Service', + 'radiusLoginService' : 'Login-Service', + 'radiusLoginTCPPort' : 'Login-TCP-Port', + 'radiusLoginTime' : 'Login-Time', + 'radiusNASIpAddress' : 'NAS-IP-Address', + 'radiusPasswordRetry' : 'Password-Retry', + 'radiusPortLimit' : 'Port-Limit', + 'radiusProfileDn' : 'Profile-Dn', + 'radiusPrompt' : 'Prompt', + 'radiusProxyToRealm' : 'Proxy-To-Realm', + 'radiusRealm' : 'Realm', + 'radiusReplicateToRealm' : 'Replicate-To-Realm', + 'radiusReplyMessage' : 'Reply-Message', + 'radiusServiceType' : 'Service-Type', + 'radiusSessionTimeout' : 'Session-Timeout', + 'radiusSimultaneousUse' : 'Simultaneous-Use', + 'radiusStripUserName' : 'Strip-User-Name', + 'radiusTerminationAction' : 'Termination-Action', + 'radiusTunnelAssignmentId' : 'Tunnel-Assignment-Id', + 'radiusTunnelClientEndpoint' : 'Tunnel-Client-Endpoint', + 'radiusTunnelMediumType' : 'Tunnel-Medium-Type', + 'radiusTunnelPassword' : 'Tunnel-Password', + 'radiusTunnelPreference' : 'Tunnel-Preference', + 'radiusTunnelPrivateGroupId' : 'Tunnel-Private-Group-Id', + 'radiusTunnelServerEndpoint' : 'Tunnel-Server-Endpoint', + 'radiusTunnelType' : 'Tunnel-Type', + 'radiusUserCategory' : 'User-Category', + 'radiusVSA' : 'VSA', +} + +profile_name_to_ldap_attr = reverse_map_dict(profile_ldap_attr_to_name) + +#------------------------------------------------------------------------------ + +clients_container = 'cn=clients,cn=radius,cn=services,cn=etc' + +def radius_clients_basedn(container, suffix): + if container is None: container = clients_container + return '%s,%s' % (container, suffix) + +def radius_client_filter(ip_addr): + return "(&(radiusClientIPAddress=%s)(objectclass=radiusClientProfile))" % \ + ldap.filter.escape_filter_chars(ip_addr) + +def radius_client_dn(client, container, suffix): + if container is None: container = clients_container + return 'radiusClientIPAddress=%s,%s,%s' % (ldap.dn.escape_dn_chars(client), container, suffix) + +# -- + +profiles_container = 'cn=profiles,cn=radius,cn=services,cn=etc' + +def radius_profiles_basedn(container, suffix): + if container is None: container = profiles_container + return '%s,%s' % (container, suffix) + +def radius_profile_filter(uid): + return "(&(uid=%s)(objectclass=radiusprofile))" % \ + ldap.filter.escape_filter_chars(uid) + +def radius_profile_dn(uid, container, suffix): + if container is None: container = profiles_container + return 'uid=%s,%s,%s' % (ldap.dn.escape_dn_chars(uid), container, suffix) + + +#------------------------------------------------------------------------------ + +comment_re = re.compile('#.*$', re.MULTILINE) +def read_pairs_file(filename): + if filename == '-': + fd = sys.stdin + else: + fd = open(filename) + data = fd.read() + data = comment_re.sub('', data) # kill comments + pairs = ipautil.parse_key_value_pairs(data) + if fd != sys.stdin: fd.close() + return pairs + + +def get_ldap_attr_translations(): + comment_re = re.compile('#.*$') + radius_attr_to_ldap_attr = {} + ldap_attr_to_radius_attr = {} + try: + f = open(LDAP_ATTR_MAP_FILEPATH) + for line in f.readlines(): + line = comment_re.sub('', line).strip() + if not line: continue + attr_type, radius_attr, ldap_attr = line.split() + print 'type="%s" radius="%s" ldap="%s"' % (attr_type, radius_attr, ldap_attr) + radius_attr_to_ldap_attr[radius_attr] = {'ldap_attr':ldap_attr, 'attr_type':attr_type} + ldap_attr_to_radius_attr[ldap_attr] = {'radius_attr':radius_attr, 'attr_type':attr_type} + f.close() + except Exception, e: + logging.error('cold not read radius ldap attribute map file (%s): %s', LDAP_ATTR_MAP_FILEPATH, e) + pass # FIXME + + #for k,v in radius_attr_to_ldap_attr.items(): + # print '%s --> %s' % (k,v) + #for k,v in ldap_attr_to_radius_attr.items(): + # print '%s --> %s' % (k,v) + diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py index e756058a..531bf72b 100644 --- a/ipa-python/rpcclient.py +++ b/ipa-python/rpcclient.py @@ -594,12 +594,12 @@ class RPCClient: # radius support - def get_radius_client_by_ip_addr(self,ip_addr,sattrs=None): + def get_radius_client_by_ip_addr(self,ip_addr, container, sattrs=None): server = self.setup_server() - if sattrs is None: - sattrs = "__NONE__" + if container is None: container = "__NONE__" + if sattrs is None: sattrs = "__NONE__" try: - result = server.get_radius_client_by_ip_addr(ip_addr, sattrs) + result = server.get_radius_client_by_ip_addr(ip_addr, container, sattrs) except xmlrpclib.Fault, fault: raise ipaerror.gen_exception(fault.faultCode, fault.faultString) except socket.error, (value, msg): @@ -607,11 +607,13 @@ class RPCClient: return ipautil.unwrap_binary_data(result) - def add_radius_client(self,client): + def add_radius_client(self,client, container=None): server = self.setup_server() + if container is None: container = "__NONE__" + try: - result = server.add_radius_client(ipautil.wrap_binary_data(client)) + result = server.add_radius_client(ipautil.wrap_binary_data(client), container) except xmlrpclib.Fault, fault: raise ipaerror.gen_exception(fault.faultCode, fault.faultString) except socket.error, (value, msg): @@ -633,11 +635,12 @@ class RPCClient: return ipautil.unwrap_binary_data(result) - def delete_radius_client(self,ip_addr): + def delete_radius_client(self,ip_addr, container=None): server = self.setup_server() + if container is None: container = "__NONE__" try: - result = server.delete_radius_client(ip_addr) + result = server.delete_radius_client(ip_addr, container) except xmlrpclib.Fault, fault: raise ipaerror.gen_exception(fault.faultCode, fault.faultString) except socket.error, (value, msg): @@ -645,13 +648,14 @@ class RPCClient: return ipautil.unwrap_binary_data(result) - def find_radius_clients(self, criteria, sattrs=None, searchlimit=0, timelimit=-1): + def find_radius_clients(self, criteria, container=None, sattrs=None, searchlimit=0, timelimit=-1): server = self.setup_server() + if container is None: container = "__NONE__" try: # None values are not allowed in XML-RPC if sattrs is None: sattrs = "__NONE__" - result = server.find_radius_clients(criteria, sattrs, searchlimit, timelimit) + result = server.find_radius_clients(criteria, container, sattrs, searchlimit, timelimit) except xmlrpclib.Fault, fault: raise ipaerror.gen_exception(fault.faultCode, fault.faultString) except socket.error, (value, msg): -- cgit From 9a9a7e4058cd9fa7932f305290b543bc9f623add Mon Sep 17 00:00:00 2001 From: "jdennis@VAIO" Date: Sat, 24 Nov 2007 11:20:28 -0500 Subject: clean up attribute names clean up command line args in ipa-delradiusclient --- ipa-python/radius_util.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'ipa-python') diff --git a/ipa-python/radius_util.py b/ipa-python/radius_util.py index 482b9f19..caa72e3a 100644 --- a/ipa-python/radius_util.py +++ b/ipa-python/radius_util.py @@ -47,8 +47,11 @@ __all__ = [ 'radius_profile_filter', 'radius_profile_dn', - 'client_ldap_attr_to_name', - 'client_name_to_ldap_attr', + 'radius_client_ldap_attr_to_radius_attr', + 'radius_client_attr_to_ldap_attr', + + 'radius_profile_ldap_attr_to_radius_attr', + 'radius_profile_attr_to_ldap_attr', 'read_pairs_file', ] @@ -82,7 +85,7 @@ def reverse_map_dict(src_dict): #------------------------------------------------------------------------------ -client_ldap_attr_to_name = ipautil.CIDict({ +radius_client_ldap_attr_to_radius_attr = ipautil.CIDict({ 'radiusClientIPAddress' : 'Client-IP-Address', 'radiusClientSecret' : 'Secret', 'radiusClientNASType' : 'NAS-Type', @@ -90,11 +93,11 @@ client_ldap_attr_to_name = ipautil.CIDict({ 'description' : 'Description', }) -client_name_to_ldap_attr = reverse_map_dict(client_ldap_attr_to_name) +radius_client_attr_to_ldap_attr = reverse_map_dict(radius_client_ldap_attr_to_radius_attr) #------------------------------------------------------------------------------ -profile_ldap_attr_to_name = { +radius_profile_ldap_attr_to_radius_attr = { 'radiusArapFeatures' : 'Arap-Features', 'radiusArapSecurity' : 'Arap-Security', 'radiusArapZoneAccess' : 'Arap-Zone-Access', @@ -156,7 +159,7 @@ profile_ldap_attr_to_name = { 'radiusVSA' : 'VSA', } -profile_name_to_ldap_attr = reverse_map_dict(profile_ldap_attr_to_name) +radius_profile_attr_to_ldap_attr = reverse_map_dict(radius_profile_ldap_attr_to_radius_attr) #------------------------------------------------------------------------------ -- cgit From 4f33d674188268432b3d8ef0921be0de3e6c5ef7 Mon Sep 17 00:00:00 2001 From: John Dennis Date: Mon, 26 Nov 2007 11:12:58 -0500 Subject: remove radius_client.py, move contents to radius_util.py --- ipa-python/ipaclient.py | 4 +- ipa-python/radius_client.py | 165 -------------------------------------------- ipa-python/radius_util.py | 148 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 149 insertions(+), 168 deletions(-) delete mode 100644 ipa-python/radius_client.py (limited to 'ipa-python') diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py index e9b0002f..fab33775 100644 --- a/ipa-python/ipaclient.py +++ b/ipa-python/ipaclient.py @@ -27,7 +27,7 @@ import user import group import ipa import config -import radius_client +import radius_util class IPAClient: @@ -336,7 +336,7 @@ class IPAClient: # radius support def get_radius_client_by_ip_addr(self, ip_addr, container=None, sattrs=None): result = self.transport.get_radius_client_by_ip_addr(ip_addr, container, sattrs) - return radius_client.RadiusClient(result) + return radius_util.RadiusClient(result) def add_radius_client(self,client, container=None): client_dict = client.toDict() diff --git a/ipa-python/radius_client.py b/ipa-python/radius_client.py deleted file mode 100644 index 907e0210..00000000 --- a/ipa-python/radius_client.py +++ /dev/null @@ -1,165 +0,0 @@ -# Authors: John Dennis -# -# Copyright (C) 2007 Red Hat -# see file 'COPYING' for use and warranty information -# -# 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; version 2 only -# -# 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, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# - -import getpass -import re - -from ipa.entity import Entity -import ipa.ipavalidate as ipavalidate - -__all__ = ['RadiusClient', - 'get_secret', - 'validate_ip_addr', - 'validate_secret', - 'validate_name', - 'validate_nastype', - 'validate_desc', - 'validate', - ] - -#------------------------------------------------------------------------------ - -dotted_octet_re = re.compile(r"^(\d+)\.(\d+)\.(\d+)\.(\d+)(/(\d+))?$") -dns_re = re.compile(r"^[a-zA-Z][a-zA-Z0-9.-]+$") -# secret, name, nastype all have 31 char max in freeRADIUS, max ip address len is 255 -valid_secret_len = (1,31) -valid_name_len = (1,31) -valid_nastype_len = (1,31) -valid_ip_addr_len = (1,255) - -valid_ip_addr_msg = '''\ -IP address must be either a DNS name (letters,digits,dot,hyphen, beginning with -a letter),or a dotted octet followed by an optional mask (e.g 192.168.1.0/24)''' - -valid_desc_msg = "Description must text string" - -#------------------------------------------------------------------------------ - -class RadiusClient(Entity): - - def __init2__(self): - pass - - -#------------------------------------------------------------------------------ - -def get_secret(): - valid = False - while (not valid): - secret = getpass.getpass("Enter Secret: ") - confirm = getpass.getpass("Confirm Secret: ") - if (secret != confirm): - print "Secrets do not match" - continue - valid = True - return secret - -#------------------------------------------------------------------------------ - -def valid_ip_addr(text): - - # is it a dotted octet? If so there should be 4 integers seperated - # by a dot and each integer should be between 0 and 255 - # there may be an optional mask preceded by a slash (e.g. 1.2.3.4/24) - match = dotted_octet_re.search(text) - if match: - # dotted octet notation - i = 1 - while i <= 4: - octet = int(match.group(i)) - if octet > 255: return False - i += 1 - if match.group(5): - mask = int(match.group(6)) - if mask <= 32: - return True - else: - return False - return True - else: - # DNS name, can contain letters, numbers, dot and hypen, must start with a letter - if dns_re.search(text): return True - return False - -def validate_length(value, limits): - length = len(value) - if length < limits[0] or length > limits[1]: - return False - return True - -def valid_length_msg(name, limits): - return "%s length must be at least %d and not more than %d" % (name, limits[0], limits[1]) - -def err_msg(variable, variable_name=None): - if variable_name is None: variable_name = 'value' - print "ERROR: %s = %s" % (variable_name, variable) - -#------------------------------------------------------------------------------ - -def validate_ip_addr(ip_addr, variable_name=None): - if not validate_length(ip_addr, valid_ip_addr_len): - err_msg(ip_addr, variable_name) - print valid_length_msg('ip address', valid_ip_addr_len) - return False - if not valid_ip_addr(ip_addr): - err_msg(ip_addr, variable_name) - print valid_ip_addr_msg - return False - return True - -def validate_secret(secret, variable_name=None): - if not validate_length(secret, valid_secret_len): - err_msg(secret, variable_name) - print valid_length_msg('secret', valid_secret_len) - return False - return True - -def validate_name(name, variable_name=None): - if not validate_length(name, valid_name_len): - err_msg(name, variable_name) - print valid_length_msg('name', valid_name_len) - return False - return True - -def validate_nastype(nastype, variable_name=None): - if not validate_length(nastype, valid_nastype_len): - err_msg(nastype, variable_name) - print valid_length_msg('NAS Type', valid_nastype_len) - return False - return True - -def validate_desc(desc, variable_name=None): - if ipavalidate.plain(desc, notEmpty=True) != 0: - print valid_desc_msg - return False - return True - -def validate(attribute, value): - if attribute == 'Client-IP-Address': - return validate_ip_addr(value, attribute) - if attribute == 'Secret': - return validate_secret(value, attribute) - if attribute == 'NAS-Type': - return validate_nastype(value, attribute) - if attribute == 'Name': - return validate_name(value, attribute) - if attribute == 'Description': - return validate_desc(value, attribute) - return True - diff --git a/ipa-python/radius_util.py b/ipa-python/radius_util.py index caa72e3a..24eb949a 100644 --- a/ipa-python/radius_util.py +++ b/ipa-python/radius_util.py @@ -21,9 +21,12 @@ import sys import os import re import ldap +import getpass import ldap.filter from ipa import ipautil +from ipa.entity import Entity +import ipa.ipavalidate as ipavalidate __all__ = [ @@ -37,6 +40,9 @@ __all__ = [ 'RADIUSD_CONF_TEMPLATE_FILEPATH', 'RADIUSD', + 'RadiusClient', + 'RadiusProfile', + 'clients_container', 'radius_clients_basedn', 'radius_client_filter', @@ -54,7 +60,15 @@ __all__ = [ 'radius_profile_attr_to_ldap_attr', 'read_pairs_file', -] + + 'get_secret', + 'validate_ip_addr', + 'validate_secret', + 'validate_name', + 'validate_nastype', + 'validate_desc', + 'validate', + ] #------------------------------------------------------------------------------ @@ -71,6 +85,35 @@ RADIUSD_CONF_TEMPLATE_FILEPATH = os.path.join(ipautil.SHARE_DIR, 'radius.rad RADIUSD = '/usr/sbin/radiusd' +#------------------------------------------------------------------------------ + +dotted_octet_re = re.compile(r"^(\d+)\.(\d+)\.(\d+)\.(\d+)(/(\d+))?$") +dns_re = re.compile(r"^[a-zA-Z][a-zA-Z0-9.-]+$") +# secret, name, nastype all have 31 char max in freeRADIUS, max ip address len is 255 +valid_secret_len = (1,31) +valid_name_len = (1,31) +valid_nastype_len = (1,31) +valid_ip_addr_len = (1,255) + +valid_ip_addr_msg = '''\ +IP address must be either a DNS name (letters,digits,dot,hyphen, beginning with +a letter),or a dotted octet followed by an optional mask (e.g 192.168.1.0/24)''' + +valid_desc_msg = "Description must text string" + +#------------------------------------------------------------------------------ + +class RadiusClient(Entity): + + def __init2__(self): + pass + +class RadiusProfile(Entity): + + def __init2__(self): + pass + + #------------------------------------------------------------------------------ def reverse_map_dict(src_dict): @@ -232,3 +275,106 @@ def get_ldap_attr_translations(): #for k,v in ldap_attr_to_radius_attr.items(): # print '%s --> %s' % (k,v) +def get_secret(): + valid = False + while (not valid): + secret = getpass.getpass("Enter Secret: ") + confirm = getpass.getpass("Confirm Secret: ") + if (secret != confirm): + print "Secrets do not match" + continue + valid = True + return secret + +#------------------------------------------------------------------------------ + +def valid_ip_addr(text): + + # is it a dotted octet? If so there should be 4 integers seperated + # by a dot and each integer should be between 0 and 255 + # there may be an optional mask preceded by a slash (e.g. 1.2.3.4/24) + match = dotted_octet_re.search(text) + if match: + # dotted octet notation + i = 1 + while i <= 4: + octet = int(match.group(i)) + if octet > 255: return False + i += 1 + if match.group(5): + mask = int(match.group(6)) + if mask <= 32: + return True + else: + return False + return True + else: + # DNS name, can contain letters, numbers, dot and hypen, must start with a letter + if dns_re.search(text): return True + return False + +def validate_length(value, limits): + length = len(value) + if length < limits[0] or length > limits[1]: + return False + return True + +def valid_length_msg(name, limits): + return "%s length must be at least %d and not more than %d" % (name, limits[0], limits[1]) + +def err_msg(variable, variable_name=None): + if variable_name is None: variable_name = 'value' + print "ERROR: %s = %s" % (variable_name, variable) + +#------------------------------------------------------------------------------ + +def validate_ip_addr(ip_addr, variable_name=None): + if not validate_length(ip_addr, valid_ip_addr_len): + err_msg(ip_addr, variable_name) + print valid_length_msg('ip address', valid_ip_addr_len) + return False + if not valid_ip_addr(ip_addr): + err_msg(ip_addr, variable_name) + print valid_ip_addr_msg + return False + return True + +def validate_secret(secret, variable_name=None): + if not validate_length(secret, valid_secret_len): + err_msg(secret, variable_name) + print valid_length_msg('secret', valid_secret_len) + return False + return True + +def validate_name(name, variable_name=None): + if not validate_length(name, valid_name_len): + err_msg(name, variable_name) + print valid_length_msg('name', valid_name_len) + return False + return True + +def validate_nastype(nastype, variable_name=None): + if not validate_length(nastype, valid_nastype_len): + err_msg(nastype, variable_name) + print valid_length_msg('NAS Type', valid_nastype_len) + return False + return True + +def validate_desc(desc, variable_name=None): + if ipavalidate.plain(desc, notEmpty=True) != 0: + print valid_desc_msg + return False + return True + +def validate(attribute, value): + if attribute == 'Client-IP-Address': + return validate_ip_addr(value, attribute) + if attribute == 'Secret': + return validate_secret(value, attribute) + if attribute == 'NAS-Type': + return validate_nastype(value, attribute) + if attribute == 'Name': + return validate_name(value, attribute) + if attribute == 'Description': + return validate_desc(value, attribute) + return True -- cgit From 09238510ff0dced7998e6a1b72f450070e8c6116 Mon Sep 17 00:00:00 2001 From: John Dennis Date: Mon, 26 Nov 2007 19:30:33 -0500 Subject: add command line utilites for radius profiles --- ipa-python/ipaclient.py | 36 ++++++++++++++++++++-- ipa-python/radius_util.py | 5 +-- ipa-python/rpcclient.py | 77 ++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 110 insertions(+), 8 deletions(-) (limited to 'ipa-python') diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py index fab33775..7a140308 100644 --- a/ipa-python/ipaclient.py +++ b/ipa-python/ipaclient.py @@ -338,7 +338,7 @@ class IPAClient: result = self.transport.get_radius_client_by_ip_addr(ip_addr, container, sattrs) return radius_util.RadiusClient(result) - def add_radius_client(self,client, container=None): + def add_radius_client(self, client, container=None): client_dict = client.toDict() # dn is set on the server-side @@ -348,7 +348,7 @@ class IPAClient: result = self.transport.add_radius_client(client_dict, container) return result - def update_radius_client(self,client): + def update_radius_client(self, client): result = self.transport.update_radius_client(client.origDataDict(), client.toDict()) return result @@ -366,3 +366,35 @@ class IPAClient: return users + def get_radius_profile_by_uid(self, uid, user_profile=None, sattrs=None): + result = self.transport.get_radius_profile_by_uid(uid, user_profile, sattrs) + return radius_util.RadiusClient(result) + + def add_radius_profile(self, profile, user_profile=None): + profile_dict = profile.toDict() + + # dn is set on the server-side + del profile_dict['dn'] + + # convert to a regular dict before sending + result = self.transport.add_radius_profile(profile_dict, user_profile) + return result + + def update_radius_profile(self, profile): + result = self.transport.update_radius_profile(profile.origDataDict(), profile.toDict()) + return result + + def delete_radius_profile(self, ip_addr, user_profile=None): + return self.transport.delete_radius_profile(ip_addr, user_profile) + + def find_radius_profiles(self, criteria, user_profile=None, sattrs=None, searchlimit=0, timelimit=-1): + result = self.transport.find_radius_profiles(criteria, user_profile, sattrs, searchlimit, timelimit) + counter = result[0] + + users = [counter] + for attrs in result[1:]: + if attrs is not None: + users.append(user.User(attrs)) + + return users + diff --git a/ipa-python/radius_util.py b/ipa-python/radius_util.py index 24eb949a..e502fece 100644 --- a/ipa-python/radius_util.py +++ b/ipa-python/radius_util.py @@ -140,7 +140,8 @@ radius_client_attr_to_ldap_attr = reverse_map_dict(radius_client_ldap_attr_to_ra #------------------------------------------------------------------------------ -radius_profile_ldap_attr_to_radius_attr = { +radius_profile_ldap_attr_to_radius_attr = ipautil.CIDict({ + 'uid' : 'UID', 'radiusArapFeatures' : 'Arap-Features', 'radiusArapSecurity' : 'Arap-Security', 'radiusArapZoneAccess' : 'Arap-Zone-Access', @@ -200,7 +201,7 @@ radius_profile_ldap_attr_to_radius_attr = { 'radiusTunnelType' : 'Tunnel-Type', 'radiusUserCategory' : 'User-Category', 'radiusVSA' : 'VSA', -} +}) radius_profile_attr_to_ldap_attr = reverse_map_dict(radius_profile_ldap_attr_to_radius_attr) diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py index 531bf72b..ed23015e 100644 --- a/ipa-python/rpcclient.py +++ b/ipa-python/rpcclient.py @@ -594,7 +594,7 @@ class RPCClient: # radius support - def get_radius_client_by_ip_addr(self,ip_addr, container, sattrs=None): + def get_radius_client_by_ip_addr(self, ip_addr, container, sattrs=None): server = self.setup_server() if container is None: container = "__NONE__" if sattrs is None: sattrs = "__NONE__" @@ -607,7 +607,7 @@ class RPCClient: return ipautil.unwrap_binary_data(result) - def add_radius_client(self,client, container=None): + def add_radius_client(self, client, container=None): server = self.setup_server() if container is None: container = "__NONE__" @@ -621,7 +621,7 @@ class RPCClient: return ipautil.unwrap_binary_data(result) - def update_radius_client(self,oldclient,newclient): + def update_radius_client(self, oldclient, newclient): server = self.setup_server() try: @@ -635,7 +635,7 @@ class RPCClient: return ipautil.unwrap_binary_data(result) - def delete_radius_client(self,ip_addr, container=None): + def delete_radius_client(self, ip_addr, container=None): server = self.setup_server() if container is None: container = "__NONE__" @@ -663,3 +663,72 @@ class RPCClient: return ipautil.unwrap_binary_data(result) + def get_radius_profile_by_uid(self, ip_addr, user_profile, sattrs=None): + server = self.setup_server() + if user_profile is None: user_profile = "__NONE__" + if sattrs is None: sattrs = "__NONE__" + try: + result = server.get_radius_profile_by_uid(ip_addr, user_profile, sattrs) + except xmlrpclib.Fault, fault: + raise ipaerror.gen_exception(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return ipautil.unwrap_binary_data(result) + + def add_radius_profile(self, profile, user_profile=None): + server = self.setup_server() + + if user_profile is None: user_profile = "__NONE__" + + try: + result = server.add_radius_profile(ipautil.wrap_binary_data(profile), user_profile) + except xmlrpclib.Fault, fault: + raise ipaerror.gen_exception(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return ipautil.unwrap_binary_data(result) + + def update_radius_profile(self, oldprofile, newprofile): + server = self.setup_server() + + try: + result = server.update_radius_profile(ipautil.wrap_binary_data(oldprofile), + ipautil.wrap_binary_data(newprofile)) + except xmlrpclib.Fault, fault: + raise ipaerror.gen_exception(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return ipautil.unwrap_binary_data(result) + + + def delete_radius_profile(self, ip_addr, user_profile=None): + server = self.setup_server() + if user_profile is None: user_profile = "__NONE__" + + try: + result = server.delete_radius_profile(ip_addr, user_profile) + except xmlrpclib.Fault, fault: + raise ipaerror.gen_exception(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return ipautil.unwrap_binary_data(result) + + def find_radius_profiles(self, criteria, user_profile=None, sattrs=None, searchlimit=0, timelimit=-1): + server = self.setup_server() + if user_profile is None: user_profile = "__NONE__" + try: + # None values are not allowed in XML-RPC + if sattrs is None: + sattrs = "__NONE__" + result = server.find_radius_profiles(criteria, user_profile, sattrs, searchlimit, timelimit) + except xmlrpclib.Fault, fault: + raise ipaerror.gen_exception(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return ipautil.unwrap_binary_data(result) + -- cgit From c5a43a01686ae23e5381bc3b3f4c590774b865f8 Mon Sep 17 00:00:00 2001 From: John Dennis Date: Mon, 26 Nov 2007 20:59:53 -0500 Subject: add ItemCompleter class --- ipa-python/ipautil.py | 102 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) (limited to 'ipa-python') diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py index 407406de..b5fa9794 100644 --- a/ipa-python/ipautil.py +++ b/ipa-python/ipautil.py @@ -619,7 +619,7 @@ class AttributeValueCompleter: except EOFError: return None, None - def get_pairs(self, prompt, mandatory_attrs=None, validate_callback=None, must_match=True, value_required=True): + def get_pairs(self, prompt, mandatory_attrs=None, validate_callback=None, must_match=Trueo, value_required=True): pairs = {} if mandatory_attrs: mandatory_attrs_remaining = copy.copy(mandatory_attrs) @@ -661,3 +661,103 @@ class AttributeValueCompleter: pairs[attribute] = value return pairs + +class ItemCompleter: + ''' + Prompts the user for items in a list of items with auto completion. + TAB completes partial input. + More than one item can be specifed during input, whitespace and/or comma's seperate. + Example: + + possible_items = ['foo', 'bar'] + c = ItemCompleter(possible_items) + c.open() + # Use read_input() to limit input to a single carriage return (e.g. ) + #items = c.read_input("Enter: ") + # Use get_items to iterate until a blank line is entered. + items = c.get_items("Enter: ") + c.close() + print "items=%s" % (items) + + ''' + + def __init__(self, items, must_match=True): + self.items = items + self.must_match = must_match + self.initial_input = None + self.item_delims = ' \t,' + self.split_re = re.compile('[%s]+' % self.item_delims) + + def open(self): + # Save state + self.prev_completer = readline.get_completer() + self.prev_completer_delims = readline.get_completer_delims() + + # Set up for ourself + readline.parse_and_bind("tab: complete") + readline.set_completer(self.complete) + readline.set_completer_delims(self.item_delims) + + def close(self): + # Restore previous state + readline.set_completer_delims(self.prev_completer_delims) + readline.set_completer(self.prev_completer) + + def get_item_completions(self, text): + if text: + self.completions = [lhs for lhs in self.items if lhs.startswith(text)] + else: + self.completions = self.items + + def complete(self, text, state): + self.line_buffer= readline.get_line_buffer() + if state == 0: + beg = readline.get_begidx() + end = readline.get_endidx() + self.get_item_completions(self.line_buffer[beg:end]) + if state >= len(self.completions): return None + return self.completions[state] + + def pre_input_hook(self): + readline.insert_text('%s %s ' % (self.initial_input, self.operator)) + readline.redisplay() + + def read_input(self, prompt, initial_input=None): + items = [] + + self.initial_input = initial_input + try: + if initial_input is None: + readline.set_pre_input_hook(None) + else: + readline.set_pre_input_hook(self.pre_input_hook) + self.line_buffer = raw_input(prompt).strip() + items = self.split_re.split(self.line_buffer) + print items + for item in items[:]: + if not item: items.remove(item) + if self.must_match: + for item in items[:]: + if item not in self.items: + print "ERROR: %s is not valid" % (item) + items.remove(item) + return items + except EOFError: + return items + + def get_items(self, prompt): + items = [] + + print "Enter name [name ...]" + print "Press to accept, control-D terminates input" + print "Pressing auto completes name" + print + while True: + new_items = self.read_input(prompt) + if new_items is None: break + for item in new_items: + if item in items: continue + items.append(item) + + return items + -- cgit From 78b5987101c3d489c8397da05546d72e24aeea4c Mon Sep 17 00:00:00 2001 From: John Dennis Date: Mon, 26 Nov 2007 23:11:49 -0500 Subject: add parse_items(), read_items_file() move read_pairs_file() to ipautil --- ipa-python/ipautil.py | 34 +++++++++++++++++++++++++++++++++- ipa-python/radius_util.py | 15 --------------- 2 files changed, 33 insertions(+), 16 deletions(-) (limited to 'ipa-python') diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py index b5fa9794..f8310ae4 100644 --- a/ipa-python/ipautil.py +++ b/ipa-python/ipautil.py @@ -416,6 +416,39 @@ def parse_key_value_pairs(input): kv_dict[key] = value return kv_dict +def parse_items(text): + '''Given text with items separated by whitespace or comma, return a list of those items''' + split_re = re.compile('[ ,\t\n]+') + items = split_re.split(text) + for item in items[:]: + if not item: items.remove(item) + return items + +def read_pairs_file(filename): + comment_re = re.compile('#.*$', re.MULTILINE) + if filename == '-': + fd = sys.stdin + else: + fd = open(filename) + text = fd.read() + text = comment_re.sub('', text) # kill comments + pairs = ipautil.parse_key_value_pairs(text) + if fd != sys.stdin: fd.close() + return pairs + +def read_items_file(filename): + comment_re = re.compile('#.*$', re.MULTILINE) + if filename == '-': + fd = sys.stdin + else: + fd = open(filename) + text = fd.read() + text = comment_re.sub('', text) # kill comments + items = ipautil.parse_items(text) + if fd != sys.stdin: fd.close() + return items + + class AttributeValueCompleter: ''' Gets input from the user in the form "lhs operator rhs" @@ -733,7 +766,6 @@ class ItemCompleter: readline.set_pre_input_hook(self.pre_input_hook) self.line_buffer = raw_input(prompt).strip() items = self.split_re.split(self.line_buffer) - print items for item in items[:]: if not item: items.remove(item) if self.must_match: diff --git a/ipa-python/radius_util.py b/ipa-python/radius_util.py index e502fece..96bc0f89 100644 --- a/ipa-python/radius_util.py +++ b/ipa-python/radius_util.py @@ -59,8 +59,6 @@ __all__ = [ 'radius_profile_ldap_attr_to_radius_attr', 'radius_profile_attr_to_ldap_attr', - 'read_pairs_file', - 'get_secret', 'validate_ip_addr', 'validate_secret', @@ -240,19 +238,6 @@ def radius_profile_dn(uid, container, suffix): #------------------------------------------------------------------------------ -comment_re = re.compile('#.*$', re.MULTILINE) -def read_pairs_file(filename): - if filename == '-': - fd = sys.stdin - else: - fd = open(filename) - data = fd.read() - data = comment_re.sub('', data) # kill comments - pairs = ipautil.parse_key_value_pairs(data) - if fd != sys.stdin: fd.close() - return pairs - - def get_ldap_attr_translations(): comment_re = re.compile('#.*$') radius_attr_to_ldap_attr = {} -- cgit From 5d1ca46ea79d5a70362248b06dd4587518f78150 Mon Sep 17 00:00:00 2001 From: John Dennis Date: Tue, 27 Nov 2007 16:16:10 -0500 Subject: do a better job of handling attribute deletion --- ipa-python/ipautil.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) (limited to 'ipa-python') diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py index f8310ae4..8839e6cf 100644 --- a/ipa-python/ipautil.py +++ b/ipa-python/ipautil.py @@ -25,7 +25,6 @@ import logging import subprocess import os import stat -import copy import readline import traceback from types import * @@ -481,7 +480,7 @@ class AttributeValueCompleter: c = AttributeValueCompleter(attrs, defaults) c.open() - mandatory_attrs_remaining = copy.copy(mandatory_attrs) + mandatory_attrs_remaining = mandatory_attrs[:] while True: if mandatory_attrs_remaining: @@ -652,10 +651,10 @@ class AttributeValueCompleter: except EOFError: return None, None - def get_pairs(self, prompt, mandatory_attrs=None, validate_callback=None, must_match=Trueo, value_required=True): + def get_pairs(self, prompt, mandatory_attrs=None, validate_callback=None, must_match=True, value_required=True): pairs = {} if mandatory_attrs: - mandatory_attrs_remaining = copy.copy(mandatory_attrs) + mandatory_attrs_remaining = mandatory_attrs[:] else: mandatory_attrs_remaining = [] @@ -714,9 +713,8 @@ class ItemCompleter: ''' - def __init__(self, items, must_match=True): + def __init__(self, items): self.items = items - self.must_match = must_match self.initial_input = None self.item_delims = ' \t,' self.split_re = re.compile('[%s]+' % self.item_delims) @@ -768,26 +766,25 @@ class ItemCompleter: items = self.split_re.split(self.line_buffer) for item in items[:]: if not item: items.remove(item) - if self.must_match: - for item in items[:]: - if item not in self.items: - print "ERROR: %s is not valid" % (item) - items.remove(item) return items except EOFError: return items - def get_items(self, prompt): + def get_items(self, prompt, must_match=True): items = [] print "Enter name [name ...]" - print "Press to accept, control-D terminates input" + print "Press to accept, blank line or control-D terminates input" print "Pressing auto completes name" print while True: new_items = self.read_input(prompt) - if new_items is None: break + if not new_items: break for item in new_items: + if must_match: + if item not in self.items: + print "ERROR: %s is not valid" % (item) + continue if item in items: continue items.append(item) -- cgit From c939c5d289daaf4c855caa2a6816e7eeba7e2661 Mon Sep 17 00:00:00 2001 From: John Dennis Date: Tue, 27 Nov 2007 22:52:11 -0500 Subject: fix regular expression used in parse_key_value_pairs() --- ipa-python/ipautil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipa-python') diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py index 8839e6cf..9584f928 100644 --- a/ipa-python/ipautil.py +++ b/ipa-python/ipautil.py @@ -378,7 +378,7 @@ def format_list(items, quote=None, page_width=80): col += 1 return '\n'.join(rows) -key_value_re = re.compile("([^\s=]+)\s*=\s*((\S+)|(?P['\\\"])((?P=quote)|(.*?[^\\\])(?P=quote)))") +key_value_re = re.compile("(\w+)\s*=\s*(([^\s'\\\"]+)|(?P['\\\"])((?P=quote)|(.*?[^\\\])(?P=quote)))") def parse_key_value_pairs(input): ''' Given a string composed of key=value pairs parse it and return a dict of the key/value pairs. Keys must be a word, a key must be followed -- cgit From d7a7ba4f45d189c841c28bd5bfbe1f4b4b721154 Mon Sep 17 00:00:00 2001 From: John Dennis Date: Wed, 28 Nov 2007 12:06:06 -0500 Subject: add user profile command line arg to all radius profile command line tools to select between shared and per user profiles modify AttributeValueCompleter so default values prefer previously entered values in editing session --- ipa-python/ipautil.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'ipa-python') diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py index 5f7d396a..7006e195 100644 --- a/ipa-python/ipautil.py +++ b/ipa-python/ipautil.py @@ -528,6 +528,7 @@ class AttributeValueCompleter: self.lhs_delims = lhs_delims self.operator = operator self.strip_rhs = strip_rhs + self.pairs = None self._reset() def _reset(self): @@ -589,6 +590,13 @@ class AttributeValueCompleter: and it should return the default value for the attriubte or None''' if not self.lhs_complete: raise ValueError("attribute not parsed") + + # If the user previously provided a value let that override the supplied default + if self.pairs is not None: + prev_value = self.pairs.get(self.lhs) + if prev_value is not None: return prev_value + + # No previous user provided value, query for a default default_value_type = type(self.default_value) if default_value_type is DictType: return self.default_value.get(self.lhs, None) @@ -663,7 +671,7 @@ class AttributeValueCompleter: return None, None def get_pairs(self, prompt, mandatory_attrs=None, validate_callback=None, must_match=True, value_required=True): - pairs = {} + self.pairs = {} if mandatory_attrs: mandatory_attrs_remaining = mandatory_attrs[:] else: @@ -702,8 +710,8 @@ class AttributeValueCompleter: except ValueError: pass - pairs[attribute] = value - return pairs + self.pairs[attribute] = value + return self.pairs class ItemCompleter: ''' -- cgit From 4e1d291d15e198a0517a2c6787f865fe41147440 Mon Sep 17 00:00:00 2001 From: John Dennis Date: Fri, 30 Nov 2007 20:29:12 -0500 Subject: change location of radius data in ldap from cn=radius,cn=services,cn=etc to cn=radius --- ipa-python/radius_util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ipa-python') diff --git a/ipa-python/radius_util.py b/ipa-python/radius_util.py index 96bc0f89..1f6e7902 100644 --- a/ipa-python/radius_util.py +++ b/ipa-python/radius_util.py @@ -205,7 +205,7 @@ radius_profile_attr_to_ldap_attr = reverse_map_dict(radius_profile_ldap_attr_to_ #------------------------------------------------------------------------------ -clients_container = 'cn=clients,cn=radius,cn=services,cn=etc' +clients_container = 'cn=clients,cn=radius' def radius_clients_basedn(container, suffix): if container is None: container = clients_container @@ -221,7 +221,7 @@ def radius_client_dn(client, container, suffix): # -- -profiles_container = 'cn=profiles,cn=radius,cn=services,cn=etc' +profiles_container = 'cn=profiles,cn=radius' def radius_profiles_basedn(container, suffix): if container is None: container = profiles_container -- cgit From 3b4f0db73e73912e39baa4a4c8b8c2e9ae3ab5be Mon Sep 17 00:00:00 2001 From: Karl MacMillan Date: Thu, 6 Dec 2007 17:17:43 -0500 Subject: Convert the setup of ssl from a shell script to a python module. This is in preparation for user supplied certs. --- ipa-python/ipautil.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'ipa-python') diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py index ffe75a90..3c2b37f7 100644 --- a/ipa-python/ipautil.py +++ b/ipa-python/ipautil.py @@ -27,6 +27,7 @@ from random import Random from time import gmtime import os, sys, traceback, readline import stat +import shutil from types import * @@ -101,6 +102,15 @@ def dir_exists(filename): except: return False +def install_file(fname, dest): + if file_exists(dest): + os.rename(dest, dest + ".orig") + shutil.move(fname, dest) + +def backup_file(fname): + if file_exists(fname): + os.rename(fname, fname + ".orig") + class CIDict(dict): """ Case-insensitive but case-respecting dictionary. -- cgit From f796e50000e5c198a510300e2293ed460e7113aa Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Mon, 10 Dec 2007 16:12:58 -0500 Subject: Add simple UI for command-line programs to be able to select when multiple entries are returned. --- ipa-python/ipaadminutil.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 ipa-python/ipaadminutil.py (limited to 'ipa-python') diff --git a/ipa-python/ipaadminutil.py b/ipa-python/ipaadminutil.py new file mode 100644 index 00000000..0acc75b0 --- /dev/null +++ b/ipa-python/ipaadminutil.py @@ -0,0 +1,75 @@ +# Authors: Rob Crittenden +# +# Copyright (C) 2007 Red Hat +# see file 'COPYING' for use and warranty information +# +# 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; version 2 or later +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import string +import tempfile +import logging +import subprocess +import os + +def select_user(counter, users): + i = 1 + print "%s entries were found. Which one would you like to display?" % counter + for ent in users: + print "%s: %s (%s)" % (i, ent.getValues('cn'), ent.getValues('uid')) + i += 1 + while True: + resp = raw_input("Choose one: (1 - %s), 0 for all, q to quit: " % counter) + if resp == "q": + return "q" + if resp == "0": + userindex = -1 + break; + try: + userindex = int(resp) - 1 + if (userindex >= 0 and userindex <= counter): + break; + break; + except: + # fall through to the error msg + pass + + print "Please enter a number between 1 and %s" % counter + + return userindex + +def select_group(counter, groups): + i = 1 + print "%s entries were found. Which one would you like to display?" % counter + for ent in groups: + print "%s: %s" % (i, ent.getValues('cn')) + i += 1 + while True: + resp = raw_input("Choose one: (1 - %s), 0 for all, q to quit: " % counter) + if resp == "q": + return "q" + if resp == "0": + groupindex = -1 + break; + try: + groupindex = int(resp) - 1 + if (groupindex >= 0 and groupindex <= counter): + break; + except: + # fall through to the error msg + pass + + print "Please enter a number between 1 and %s" % counter + + return groupindex -- cgit From 3defaaf7bac1d48f5006713c5dc2aa226028f5b9 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Tue, 11 Dec 2007 10:58:39 -0500 Subject: Make admintools discover the domain using DNS calls to find the LDAP server. --- ipa-python/config.py | 56 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 5 deletions(-) (limited to 'ipa-python') diff --git a/ipa-python/config.py b/ipa-python/config.py index a17e585b..c1a3915d 100644 --- a/ipa-python/config.py +++ b/ipa-python/config.py @@ -20,6 +20,10 @@ import ConfigParser from optparse import OptionParser +import krbV +import socket +import ipa.dnsclient + class IPAConfigError(Exception): def __init__(self, msg=''): self.msg = msg @@ -55,11 +59,51 @@ def __parse_config(): p.read("/etc/ipa/ipa.conf") try: - config.default_realm = p.get("defaults", "realm") - config.default_server = p.get("defaults", "server") + if not config.default_realm: + config.default_realm = p.get("defaults", "realm") + if not config.default_server: + config.default_server = p.get("defaults", "server") except: pass +def __discover_config(): + try: + if not config.default_realm: + krbctx = krbV.default_context() + config.default_realm = krbctx.default_realm + if not config.default_realm: + return False + + if not config.default_server: + #try once with REALM -> domain + name = "_ldap._tcp."+config.default_realm+"." + rs = ipa.dnsclient.query(name, ipa.dnsclient.DNS_C_IN, ipa.dnsclient.DNS_T_SRV) + rl = len(rs) + + #try cycling on domain components of FQDN + if rl == 0: + name = socket.getfqdn() + while rl == 0: + tok = name.find(".") + if tok == -1: + return False + name = name[tok+1:] + q = "_ldap._tcp." + name + "." + rs = ipa.dnsclient.query(q, ipa.dnsclient.DNS_C_IN, ipa.dnsclient.DNS_T_SRV) + rl = len(rs) + + for r in rs: + if r.dns_type == ipa.dnsclient.DNS_T_SRV: + rsrv = r.rdata.server.rstrip(".") + # we take only the first one returned for now + config.default_server = rsrv + return True + + #if none found + return False + except: + return False + def usage(): return """ --realm\tset the IPA realm --server\tset the IPA server @@ -92,15 +136,17 @@ def __parse_args(args): def init_config(args=None): - __parse_config() out_args = None if args: out_args = __parse_args(args) + __discover_config() + __parse_config() + if not config.default_realm: - raise IPAConfigError("realm not specified in config file or on command line") + raise IPAConfigError("realm not found, nor specified in config file or on command line") if not config.default_server: - raise IPAConfigError("server not specified in config file or on command line") + raise IPAConfigError("server not found, nor specified in config file or on command line") if out_args: return out_args -- cgit