summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimo Sorce <ssorce@redhat.com>2007-09-07 14:26:26 -0400
committerSimo Sorce <ssorce@redhat.com>2007-09-07 14:26:26 -0400
commit873bbbd2de901f20fb3323f8888e0c60f0708495 (patch)
treebde660888bf89f714e4c3144df50ae64e134407e
parent566018f4d48f18fd6bdb3ad481e92c865b2a41e3 (diff)
parent2377e8bcb05a11488a7e2cda05b574b64de9de9e (diff)
downloadfreeipa-873bbbd2de901f20fb3323f8888e0c60f0708495.tar.gz
freeipa-873bbbd2de901f20fb3323f8888e0c60f0708495.tar.xz
freeipa-873bbbd2de901f20fb3323f8888e0c60f0708495.zip
Merging with upstream
-rw-r--r--ipa-admintools/Makefile4
-rw-r--r--ipa-admintools/ipa-finduser4
-rw-r--r--ipa-admintools/ipa-usermod6
-rw-r--r--ipa-python/ipautil.py98
-rw-r--r--ipa-python/test/test_ipautil.py97
-rwxr-xr-xipa-server/freeipa-server.spec1
-rw-r--r--ipa-server/freeipa-server.spec.in1
-rw-r--r--ipa-server/ipa-gui/ipagui/controllers.py19
-rw-r--r--ipa-server/ipa-gui/ipagui/forms/user.py49
-rw-r--r--ipa-server/ipa-gui/ipagui/helpers/__init__.py1
-rw-r--r--ipa-server/ipa-gui/ipagui/helpers/userhelper.py29
-rw-r--r--ipa-server/ipa-gui/ipagui/static/css/style.css36
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/master.kid47
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/useredit.kid18
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/usereditform.kid23
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/userlist.kid14
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/usernewform.kid32
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/usershow.kid20
-rw-r--r--ipa-server/ipa-install/README45
-rw-r--r--ipa-server/ipa-slapi-plugins/Makefile2
-rw-r--r--ipa-server/ipa-slapi-plugins/dna/Makefile34
-rw-r--r--ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif13
-rw-r--r--ipa-server/ipa-slapi-plugins/dna/dna.c1174
-rw-r--r--ipa-server/ipaserver/dsinstance.py2
-rw-r--r--ipa-server/ipaserver/ipaldap.py55
-rw-r--r--ipa-server/ipaserver/krbinstance.py2
-rw-r--r--ipa-server/xmlrpc-server/funcs.py239
-rw-r--r--ipa-server/xmlrpc-server/ipaxmlrpc.py12
28 files changed, 1883 insertions, 194 deletions
diff --git a/ipa-admintools/Makefile b/ipa-admintools/Makefile
index 4bed3b9a6..47822fc91 100644
--- a/ipa-admintools/Makefile
+++ b/ipa-admintools/Makefile
@@ -7,6 +7,10 @@ install:
install -m 755 ipa-finduser $(SBINDIR)
install -m 755 ipa-usermod $(SBINDIR)
install -m 755 ipa-deluser $(SBINDIR)
+ install -m 755 ipa-addgroup $(SBINDIR)
+ install -m 755 ipa-delgroup $(SBINDIR)
+ install -m 755 ipa-findgroup $(SBINDIR)
+ install -m 755 ipa-groupmod $(SBINDIR)
clean:
rm -f *~ *.pyc
diff --git a/ipa-admintools/ipa-finduser b/ipa-admintools/ipa-finduser
index 167ac23d7..409d2e3de 100644
--- a/ipa-admintools/ipa-finduser
+++ b/ipa-admintools/ipa-finduser
@@ -50,7 +50,9 @@ def main():
client = ipaclient.IPAClient()
users = client.find_users(args[1], sattrs=['dn','uid','cn','homeDirectory'])
- if len(users) == 0:
+ counter = users[0]
+ users = users[1:]
+ if counter == 0:
print "No entries found for", args[1]
return 0
diff --git a/ipa-admintools/ipa-usermod b/ipa-admintools/ipa-usermod
index 0c61f4097..317289a60 100644
--- a/ipa-admintools/ipa-usermod
+++ b/ipa-admintools/ipa-usermod
@@ -59,6 +59,9 @@ def main():
except ipa.ipaerror.IPAError, e:
print "%s" % e.message
return 1
+ except kerberos.GSSError, e:
+ print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])
+ return 1
if options.gecos:
user.setValue('gecos', options.gecos)
@@ -79,6 +82,9 @@ def main():
except xmlrpclib.ProtocolError, e:
print "Unable to connect to IPA server: %s" % (e.errmsg)
return 1
+ except ipa.ipaerror.IPAError, e:
+ print "%s" % (e.message)
+ return 1
return 0
diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py
index f6d62f7a3..2989b4211 100644
--- a/ipa-python/ipautil.py
+++ b/ipa-python/ipautil.py
@@ -30,6 +30,7 @@ import stat
from string import lower
import re
import xmlrpclib
+import datetime
def realm_to_suffix(realm_name):
s = realm_name.split(".")
@@ -233,3 +234,100 @@ def unwrap_binary_data(data):
else:
return data
+class GeneralizedTimeZone(datetime.tzinfo):
+ """This class is a basic timezone wrapper for the offset specified
+ in a Generalized Time. It is dst-ignorant."""
+ def __init__(self,offsetstr="Z"):
+ super(GeneralizedTimeZone, self).__init__()
+
+ self.name = offsetstr
+ self.houroffset = 0
+ self.minoffset = 0
+
+ if offsetstr == "Z":
+ self.houroffset = 0
+ self.minoffset = 0
+ else:
+ if (len(offsetstr) >= 3) and re.match(r'[-+]\d\d', offsetstr):
+ self.houroffset = int(offsetstr[0:3])
+ offsetstr = offsetstr[3:]
+ if (len(offsetstr) >= 2) and re.match(r'\d\d', offsetstr):
+ self.minoffset = int(offsetstr[0:2])
+ offsetstr = offsetstr[2:]
+ if len(offsetstr) > 0:
+ raise ValueError()
+ if self.houroffset < 0:
+ self.minoffset *= -1
+
+ def utcoffset(self, dt):
+ return datetime.timedelta(hours=self.houroffset, minutes=self.minoffset)
+
+ def dst(self, dt):
+ return datetime.timedelta(0)
+
+ def tzname(self, dt):
+ return self.name
+
+
+def parse_generalized_time(timestr):
+ """Parses are Generalized Time string (as specified in X.680),
+ returning a datetime object. Generalized Times are stored inside
+ the krbPasswordExpiration attribute in LDAP.
+
+ This method doesn't attempt to be perfect wrt timezones. If python
+ can't be bothered to implement them, how can we..."""
+
+ if len(timestr) < 8:
+ return None
+ try:
+ date = timestr[:8]
+ time = timestr[8:]
+
+ year = int(date[:4])
+ month = int(date[4:6])
+ day = int(date[6:8])
+
+ hour = min = sec = msec = 0
+ tzone = None
+
+ if (len(time) >= 2) and re.match(r'\d', time[0]):
+ hour = int(time[:2])
+ time = time[2:]
+ if len(time) >= 2 and (time[0] == "," or time[0] == "."):
+ hour_fraction = "."
+ time = time[1:]
+ while (len(time) > 0) and re.match(r'\d', time[0]):
+ hour_fraction += time[0]
+ time = time[1:]
+ total_secs = int(float(hour_fraction) * 3600)
+ min, sec = divmod(total_secs, 60)
+
+ if (len(time) >= 2) and re.match(r'\d', time[0]):
+ min = int(time[:2])
+ time = time[2:]
+ if len(time) >= 2 and (time[0] == "," or time[0] == "."):
+ min_fraction = "."
+ time = time[1:]
+ while (len(time) > 0) and re.match(r'\d', time[0]):
+ min_fraction += time[0]
+ time = time[1:]
+ sec = int(float(min_fraction) * 60)
+
+ if (len(time) >= 2) and re.match(r'\d', time[0]):
+ sec = int(time[:2])
+ time = time[2:]
+ if len(time) >= 2 and (time[0] == "," or time[0] == "."):
+ sec_fraction = "."
+ time = time[1:]
+ while (len(time) > 0) and re.match(r'\d', time[0]):
+ sec_fraction += time[0]
+ time = time[1:]
+ msec = int(float(sec_fraction) * 1000000)
+
+ if (len(time) > 0):
+ tzone = GeneralizedTimeZone(time)
+
+ return datetime.datetime(year, month, day, hour, min, sec, msec, tzone)
+
+ except ValueError:
+ return None
diff --git a/ipa-python/test/test_ipautil.py b/ipa-python/test/test_ipautil.py
index 54ff1dc26..2755f71ea 100644
--- a/ipa-python/test/test_ipautil.py
+++ b/ipa-python/test/test_ipautil.py
@@ -21,6 +21,7 @@ import sys
sys.path.insert(0, ".")
import unittest
+import datetime
import ipautil
@@ -207,6 +208,102 @@ class TestCIDict(unittest.TestCase):
self.assert_(item in items)
items.discard(item)
+class TestTimeParser(unittest.TestCase):
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def testSimple(self):
+ timestr = "20070803"
+
+ time = ipautil.parse_generalized_time(timestr)
+ self.assertEqual(2007, time.year)
+ self.assertEqual(8, time.month)
+ self.assertEqual(3, time.day)
+ self.assertEqual(0, time.hour)
+ self.assertEqual(0, time.minute)
+ self.assertEqual(0, time.second)
+
+ def testHourMinSec(self):
+ timestr = "20051213141205"
+
+ time = ipautil.parse_generalized_time(timestr)
+ self.assertEqual(2005, time.year)
+ self.assertEqual(12, time.month)
+ self.assertEqual(13, time.day)
+ self.assertEqual(14, time.hour)
+ self.assertEqual(12, time.minute)
+ self.assertEqual(5, time.second)
+
+ def testFractions(self):
+ timestr = "2003092208.5"
+
+ time = ipautil.parse_generalized_time(timestr)
+ self.assertEqual(2003, time.year)
+ self.assertEqual(9, time.month)
+ self.assertEqual(22, time.day)
+ self.assertEqual(8, time.hour)
+ self.assertEqual(30, time.minute)
+ self.assertEqual(0, time.second)
+
+ timestr = "199203301544,25"
+
+ time = ipautil.parse_generalized_time(timestr)
+ self.assertEqual(1992, time.year)
+ self.assertEqual(3, time.month)
+ self.assertEqual(30, time.day)
+ self.assertEqual(15, time.hour)
+ self.assertEqual(44, time.minute)
+ self.assertEqual(15, time.second)
+
+ timestr = "20060401185912,8"
+
+ time = ipautil.parse_generalized_time(timestr)
+ self.assertEqual(2006, time.year)
+ self.assertEqual(4, time.month)
+ self.assertEqual(1, time.day)
+ self.assertEqual(18, time.hour)
+ self.assertEqual(59, time.minute)
+ self.assertEqual(12, time.second)
+ self.assertEqual(800000, time.microsecond)
+
+ def testTimeZones(self):
+ timestr = "20051213141205Z"
+
+ time = ipautil.parse_generalized_time(timestr)
+ self.assertEqual(0, time.tzinfo.houroffset)
+ self.assertEqual(0, time.tzinfo.minoffset)
+ offset = time.tzinfo.utcoffset(None)
+ self.assertEqual(0, offset.seconds)
+
+ timestr = "20051213141205+0500"
+
+ time = ipautil.parse_generalized_time(timestr)
+ self.assertEqual(5, time.tzinfo.houroffset)
+ self.assertEqual(0, time.tzinfo.minoffset)
+ offset = time.tzinfo.utcoffset(None)
+ self.assertEqual(5 * 60 * 60, offset.seconds)
+
+ timestr = "20051213141205-0500"
+
+ time = ipautil.parse_generalized_time(timestr)
+ self.assertEqual(-5, time.tzinfo.houroffset)
+ self.assertEqual(0, time.tzinfo.minoffset)
+ # NOTE - the offset is always positive - it's minutes
+ # _east_ of UTC
+ offset = time.tzinfo.utcoffset(None)
+ self.assertEqual((24 - 5) * 60 * 60, offset.seconds)
+
+ timestr = "20051213141205-0930"
+
+ time = ipautil.parse_generalized_time(timestr)
+ self.assertEqual(-9, time.tzinfo.houroffset)
+ self.assertEqual(-30, time.tzinfo.minoffset)
+ offset = time.tzinfo.utcoffset(None)
+ self.assertEqual(((24 - 9) * 60 * 60) - (30 * 60), offset.seconds)
+
if __name__ == '__main__':
unittest.main()
diff --git a/ipa-server/freeipa-server.spec b/ipa-server/freeipa-server.spec
index 05d84bc77..2d1c721cb 100755
--- a/ipa-server/freeipa-server.spec
+++ b/ipa-server/freeipa-server.spec
@@ -50,6 +50,7 @@ rm -rf %{buildroot}
%attr(755,root,root) %{plugin_dir}/libipa_pwd_extop.so
%attr(755,root,root) %{plugin_dir}/libipa-memberof-plugin.so
+%attr(755,root,root) %{plugin_dir}/libipa-dna-plugin.so
%changelog
diff --git a/ipa-server/freeipa-server.spec.in b/ipa-server/freeipa-server.spec.in
index 4071a409e..68cfee012 100644
--- a/ipa-server/freeipa-server.spec.in
+++ b/ipa-server/freeipa-server.spec.in
@@ -50,6 +50,7 @@ rm -rf %{buildroot}
%attr(755,root,root) %{plugin_dir}/libipa_pwd_extop.so
%attr(755,root,root) %{plugin_dir}/libipa-memberof-plugin.so
+%attr(755,root,root) %{plugin_dir}/libipa-dna-plugin.so
%changelog
diff --git a/ipa-server/ipa-gui/ipagui/controllers.py b/ipa-server/ipa-gui/ipagui/controllers.py
index 5fb4be06b..538e66b40 100644
--- a/ipa-server/ipa-gui/ipagui/controllers.py
+++ b/ipa-server/ipa-gui/ipagui/controllers.py
@@ -17,6 +17,7 @@ import ipa.ipaclient
import ipa.user
import xmlrpclib
import forms.user
+from helpers import userhelper
from ipa import ipaerror
ipa.config.init_config()
@@ -47,6 +48,14 @@ class Root(controllers.RootController):
def index(self):
return dict()
+ @expose()
+ def topsearch(self, **kw):
+ if kw.get('searchtype') == "Users":
+ return self.userlist(uid=kw.get('searchvalue'))
+ else:
+ return self.index()
+
+
########
# User #
@@ -107,7 +116,7 @@ class Root(controllers.RootController):
def userupdate(self, **kw):
"""Updates an existing user"""
restrict_post()
- if kw.get('submit') == 'Cancel':
+ if kw.get('submit') == 'Cancel Edit':
turbogears.flash("Edit user cancelled")
raise turbogears.redirect('/usershow', uid=kw.get('uid'))
@@ -188,7 +197,7 @@ class Root(controllers.RootController):
def userindex(self):
raise turbogears.redirect("/userlist")
- @expose()
+ # @expose()
def generate_password(self):
password = ""
generator = random.SystemRandom()
@@ -203,6 +212,9 @@ class Root(controllers.RootController):
if (len(givenname) == 0) or (len(sn) == 0):
return ""
+ givenname = givenname.lower()
+ sn = sn.lower()
+
uid = givenname[0] + sn[:7]
try:
client.get_user_by_uid(uid)
@@ -244,6 +256,9 @@ class Root(controllers.RootController):
if (len(givenname) == 0) or (len(sn) == 0):
return ""
+ givenname = givenname.lower()
+ sn = sn.lower()
+
# TODO - get from config
domain = "freeipa.org"
diff --git a/ipa-server/ipa-gui/ipagui/forms/user.py b/ipa-server/ipa-gui/ipagui/forms/user.py
index b9b6f33d4..078e06ddd 100644
--- a/ipa-server/ipa-gui/ipagui/forms/user.py
+++ b/ipa-server/ipa-gui/ipagui/forms/user.py
@@ -3,29 +3,41 @@ from turbogears import validators, widgets
class UserFields():
uid = widgets.TextField(name="uid", label="Login")
- userpassword = widgets.TextField(name="userpassword", label="Password")
+ userpassword = widgets.PasswordField(name="userpassword", label="Password")
+ userpassword_confirm = widgets.PasswordField(name="userpassword_confirm",
+ label="Confirm Password")
uidnumber = widgets.TextField(name="uidnumber", label="UID")
gidnumber = widgets.TextField(name="gidnumber", label="GID")
givenname = widgets.TextField(name="givenname", label="First name")
sn = widgets.TextField(name="sn", label="Last name")
mail = widgets.TextField(name="mail", label="E-mail address")
telephonenumber = widgets.TextField(name="telephonenumber", label="Phone")
- nsAccountLock = widgets.CheckBox(name="nsAccountLock", label="Account Deactivated")
-
- uid.validator = validators.PlainText(not_empty=True)
- userpassword.validator = validators.String(not_empty=True)
- givenname.validator = validators.String(not_empty=True)
- sn.validator = validators.String(not_empty=True)
- mail.validator = validators.Email(not_empty=True)
- # validators.PhoneNumber may be a bit too picky, requiring an area code
- telephonenumber.validator = validators.PlainText(not_empty=True)
+ # nsAccountLock = widgets.CheckBox(name="nsAccountLock", label="Account Deactivated")
+ nsAccountLock = widgets.SingleSelectField(name="nsAccountLock",
+ label="Account Status",
+ options = [("", "active"), ("true", "inactive")])
uid_hidden = widgets.HiddenField(name="uid")
uidnumber_hidden = widgets.HiddenField(name="uidnumber")
gidnumber_hidden = widgets.HiddenField(name="gidnumber")
+ krbPasswordExpiration_hidden = widgets.HiddenField(name="krbPasswordExpiration")
user_orig = widgets.HiddenField(name="user_orig")
+class UserNewValidator(validators.Schema):
+ uid = validators.PlainText(not_empty=True)
+ userpassword = validators.String(not_empty=False)
+ userpassword_confirm = validators.String(not_empty=False)
+ givenname = validators.String(not_empty=True)
+ sn = validators.String(not_empty=True)
+ mail = validators.Email(not_empty=True)
+ # validators.PhoneNumber may be a bit too picky, requiring an area code
+ # telephonenumber = validators.PlainText(not_empty=False)
+
+ chained_validators = [
+ validators.FieldsMatch('userpassword', 'userpassword_confirm')
+ ]
+
class UserNewForm(widgets.Form):
params = ['user']
@@ -34,6 +46,8 @@ class UserNewForm(widgets.Form):
UserFields.uidnumber, UserFields.gidnumber,
UserFields.sn, UserFields.mail]
+ validator = UserNewValidator()
+
def __init__(self, *args, **kw):
super(UserNewForm,self).__init__(*args, **kw)
(self.template_c, self.template) = widgets.meta.load_kid_template("ipagui.templates.usernewform")
@@ -46,6 +60,18 @@ class UserNewForm(widgets.Form):
def has_foo(self):
return False
+class UserEditValidator(validators.Schema):
+ userpassword = validators.String(not_empty=False)
+ userpassword_confirm = validators.String(not_empty=False)
+ givenname = validators.String(not_empty=True)
+ sn = validators.String(not_empty=True)
+ mail = validators.Email(not_empty=True)
+ # validators.PhoneNumber may be a bit too picky, requiring an area code
+ # telephonenumber = validators.PlainText(not_empty=False)
+
+ chained_validators = [
+ validators.FieldsMatch('userpassword', 'userpassword_confirm')
+ ]
class UserEditForm(widgets.Form):
params = ['user']
@@ -53,8 +79,11 @@ class UserEditForm(widgets.Form):
fields = [UserFields.givenname, UserFields.sn, UserFields.mail,
UserFields.uid_hidden, UserFields.user_orig,
UserFields.uidnumber_hidden, UserFields.gidnumber_hidden,
+ UserFields.krbPasswordExpiration_hidden,
]
+ validator = UserEditValidator()
+
def __init__(self, *args, **kw):
super(UserEditForm,self).__init__(*args, **kw)
(self.template_c, self.template) = widgets.meta.load_kid_template("ipagui.templates.usereditform")
diff --git a/ipa-server/ipa-gui/ipagui/helpers/__init__.py b/ipa-server/ipa-gui/ipagui/helpers/__init__.py
new file mode 100644
index 000000000..143f486c0
--- /dev/null
+++ b/ipa-server/ipa-gui/ipagui/helpers/__init__.py
@@ -0,0 +1 @@
+# __init__.py
diff --git a/ipa-server/ipa-gui/ipagui/helpers/userhelper.py b/ipa-server/ipa-gui/ipagui/helpers/userhelper.py
new file mode 100644
index 000000000..e1ade3a2c
--- /dev/null
+++ b/ipa-server/ipa-gui/ipagui/helpers/userhelper.py
@@ -0,0 +1,29 @@
+import sys
+import datetime
+
+from ipa import ipautil
+
+def password_expires_in(datestr):
+ """Returns the number of days that password expires in. Returns a negative number
+ if the password is already expired."""
+ if (datestr == None) or (datestr == ""):
+ return sys.maxint
+
+ expdate = ipautil.parse_generalized_time(datestr)
+ if not expdate:
+ return sys.maxint
+
+ delta = expdate - datetime.datetime.now()
+ return delta.days
+
+def password_is_expired(days):
+ return days < 0
+
+def password_expires_soon(days):
+ return (not password_is_expired(days)) and (days < 7)
+
+def account_status_display(status):
+ if status == "true":
+ return "inactive"
+ else:
+ return "active"
diff --git a/ipa-server/ipa-gui/ipagui/static/css/style.css b/ipa-server/ipa-gui/ipagui/static/css/style.css
index 9ea86ae01..9654ebf15 100644
--- a/ipa-server/ipa-gui/ipagui/static/css/style.css
+++ b/ipa-server/ipa-gui/ipagui/static/css/style.css
@@ -17,6 +17,7 @@ body {
background:#ccc; /* should be same as #sidebar */
margin:0 auto;
width:100%;
+ clear:both;
}
@@ -24,9 +25,26 @@ body {
background:#fff;
}
-#header h1 {
- padding:5px;
- margin:0;
+#header #logo {
+ float:left;
+}
+
+#header #headerinfo {
+ text-align:right;
+ padding-right:10px;
+}
+
+#header #headerinfo #login {
+}
+
+#header #headerinfo #topsearch {
+ padding-top: 15px;
+}
+
+.searchtext {
+ background-color:#E5F1F4;
+ border:1px solid #8E8E8E;
+ color:#444444;
}
@@ -75,7 +93,11 @@ body {
float:left;
width:10%;
padding: 5px;
- font-size: small;
+ font-size: medium;
+}
+
+#sidebar p {
+ line-height: 150%;
}
#sidebar h2 {
@@ -140,6 +162,12 @@ body {
font-weight: bolder;
}
+.warning_message {
+ font-size: 120%;
+ color: #ee0000;
+ font-weight: bolder;
+}
+
.fielderror {
color: red;
font-weight: bold;
diff --git a/ipa-server/ipa-gui/ipagui/templates/master.kid b/ipa-server/ipa-gui/ipagui/templates/master.kid
index 2f39afc41..3be1f4c09 100644
--- a/ipa-server/ipa-gui/ipagui/templates/master.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/master.kid
@@ -24,35 +24,66 @@
</span>
</div>
- <div id="page">
- <div id="header">
+ <div id="header">
+ <div id="logo">
<a href="${tg.url('/')}"><img
- src="${tg.url('/static/images/logo.png')}"
- border="0"
- /></a>
+ src="${tg.url('/static/images/logo.png')}"
+ border="0"
+ /></a>
+ </div>
+ <div id="headerinfo">
+ <div id="login">
+ Logged in as: ace
+ </div>
+ <div id="topsearch">
+ <form action="${tg.url('/topsearch')}" method="post">
+ <select name="searchtype">
+ <option>Users</option>
+ <option>Groups</option>
+ </select>
+ <input class="searchtext" id="topsearchbox" type="text"
+ name="searchvalue"
+ value="Type search terms here."
+ onfocus="clearsearch()" />
+ <input type="submit" value="Search"/>
+ </form>
+ <script type="text/javascript">
+ function clearsearch() {
+ topsearchbox = document.getElementById('topsearchbox');
+ topsearchbox.onfocus = null;
+ topsearchbox.value = "";
+ }
+ </script>
+ </div>
</div>
+ </div>
+ <div id="page">
<div id="nav"><!--
This used to have links. Keeping around in case we move them back...
--></div>
<div id="sidebar">
<h2>Tasks</h2>
+ <p>
<a href="${tg.url('/usernew')}">Add Person</a><br/>
<a href="${tg.url('/userlist')}">Find People</a><br/>
- <br />
+ </p>
+ <p>
<a href="${tg.url('/groupindex')}">Add Group</a><br/>
<a href="${tg.url('/groupindex')}">Find Groups</a><br/>
- <br />
+ </p>
+ <p>
<a href="${tg.url('/')}">Manage Policy</a><br/>
<a href="${tg.url('/')}">Self Service</a><br/>
+ </p>
</div>
<div py:replace="[item.text]+item[:]"></div>
<div id="footer">
- <a href="http://www.freeipa.com/">Powered by FreeIPA</a>
+ <a href="http://www.freeipa.com/" target="_blank">Powered by FreeIPA</a>
</div>
</div>
diff --git a/ipa-server/ipa-gui/ipagui/templates/useredit.kid b/ipa-server/ipa-gui/ipagui/templates/useredit.kid
index db47ab298..1f31139d1 100644
--- a/ipa-server/ipa-gui/ipagui/templates/useredit.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/useredit.kid
@@ -8,6 +8,24 @@
<body>
<h2>Edit Person</h2>
+<?python
+from ipagui.helpers import userhelper
+pw_expires_days = userhelper.password_expires_in(user.get("krbPasswordExpiration"))
+pw_expires_soon = userhelper.password_expires_soon(pw_expires_days)
+pw_is_expired = userhelper.password_is_expired(pw_expires_days)
+if pw_expires_days != 1:
+ days_suffix = "s"
+else:
+ days_suffix = ""
+?>
+
+ <div py:if='pw_expires_soon' class="warning_message">
+ Password will expire in ${pw_expires_days} day${days_suffix}
+ </div>
+ <div py:if='pw_is_expired' class="warning_message">
+ Password has expired
+ </div>
+
${form.display(action="userupdate", value=user)}
</body>
</html>
diff --git a/ipa-server/ipa-gui/ipagui/templates/usereditform.kid b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid
index dc61f38c5..6227c4440 100644
--- a/ipa-server/ipa-gui/ipagui/templates/usereditform.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid
@@ -56,8 +56,9 @@
<span py:replace="user.userpassword.display(value_for(user.userpassword))" />
<span py:if="tg.errors.get('userpassword')" class="fielderror"
py:content="tg.errors.get('userpassword')" />
- <span id="password_text">********</span>
+ <!--
+ <span id="password_text">********</span>
<input id="genpassword_button" type="button" value="Generate Password"
disabled="true"
onclick="new Ajax.Request('${tg.url('/generate_password')}',
@@ -89,6 +90,20 @@
}
}
</script>
+ -->
+ </td>
+ </tr>
+
+ <tr>
+ <th valign="top">
+ <label class="fieldlabel" for="${user.userpassword_confirm.field_id}"
+ py:content="user.userpassword_confirm.label" />:
+ </th>
+ <td valign="top">
+ <span py:replace="user.userpassword_confirm.display(
+ value_for(user.userpassword_confirm))" />
+ <span py:if="tg.errors.get('userpassword_confirm')" class="fielderror"
+ py:content="tg.errors.get('userpassword_confirm')" />
</td>
</tr>
@@ -158,11 +173,13 @@
<tr>
<th>
<br />
- <input type="submit" class="submitbutton" name="submit" value="Submit"/>
+ <input type="submit" class="submitbutton" name="submit"
+ value="Update Person"/>
</th>
<td>
<br />
- <input type="submit" class="submitbutton" name="submit" value="Cancel" />
+ <input type="submit" class="submitbutton" name="submit"
+ value="Cancel Edit" />
</td>
<td></td>
</tr>
diff --git a/ipa-server/ipa-gui/ipagui/templates/userlist.kid b/ipa-server/ipa-gui/ipagui/templates/userlist.kid
index de4c4eb2f..1f3e72b00 100644
--- a/ipa-server/ipa-gui/ipagui/templates/userlist.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/userlist.kid
@@ -6,20 +6,18 @@
<title>Find People</title>
</head>
<body>
- <h2>Find People</h2>
<div id="search">
<form action="${tg.url('/userlist')}" method="post">
- Search:
<input id="uid" type="text" name="uid" value="${uid}" />
- <input type="submit" />
+ <input type="submit" value="Find People"/>
</form>
<script type="text/javascript">
document.getElementById("uid").focus();
</script>
</div>
- <div py:if='users != None'>
+ <div py:if='(users != None) and (len(users) > 0)'>
<h2>${len(users)} results returned:</h2>
- <table id="resultstable" py:if='len(users) > 0'>
+ <table id="resultstable">
<tr>
<th>
<label class="fieldlabel" py:content="fields.uid.label" />
@@ -61,9 +59,9 @@
</td>
</tr>
</table>
- <div py:if='len(users) == 0'>
- No results found.
- </div>
+ </div>
+ <div py:if='(users != None) and (len(users) == 0)'>
+ <h2>No results found for "${uid}"</h2>
</div>
</body>
</html>
diff --git a/ipa-server/ipa-gui/ipagui/templates/usernewform.kid b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid
index d4863a3df..daf131432 100644
--- a/ipa-server/ipa-gui/ipagui/templates/usernewform.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid
@@ -27,6 +27,9 @@
<span py:if="tg.errors.get('sn')" class="fielderror"
py:content="tg.errors.get('sn')" />
<script type="text/javascript">
+ var uid_suggest = ""
+ var mail_suggest = ""
+
function autofill(self) {
givenname = document.getElementById('form_givenname');
sn = document.getElementById('form_sn');
@@ -35,22 +38,24 @@
}
uid = document.getElementById('form_uid');
mail = document.getElementById('form_mail');
- if (uid.value == "") {
+ if ((uid.value == "") || (uid.value == uid_suggest)) {
new Ajax.Request('${tg.url('/suggest_uid')}', {
method: 'get',
parameters: {'givenname': givenname.value, 'sn': sn.value},
onSuccess: function(transport) {
uid.value = transport.responseText;
+ uid_suggest = uid.value;
new Effect.Highlight(uid);
}
});
}
- if (mail.value == "") {
+ if ((mail.value == "") || (mail.value == mail_suggest)) {
new Ajax.Request('${tg.url('/suggest_email')}', {
method: 'get',
parameters: {'givenname': givenname.value, 'sn': sn.value},
onSuccess: function(transport) {
mail.value = transport.responseText;
+ mail_suggest = mail.value;
new Effect.Highlight(mail);
}
});
@@ -87,6 +92,7 @@
<span py:if="tg.errors.get('userpassword')" class="fielderror"
py:content="tg.errors.get('userpassword')" />
+ <!--
<input type="button" value="Generate Password"
onclick="new Ajax.Request('${tg.url('/generate_password')}',
{
@@ -96,6 +102,20 @@
transport.responseText;
}
});" />
+ -->
+ </td>
+ </tr>
+
+ <tr>
+ <th>
+ <label class="fieldlabel" for="${user.userpassword_confirm.field_id}"
+ py:content="user.userpassword_confirm.label" />:
+ </th>
+ <td>
+ <span py:replace="user.userpassword_confirm.display(
+ value_for(user.userpassword_confirm))" />
+ <span py:if="tg.errors.get('userpassword_confirm')" class="fielderror"
+ py:content="tg.errors.get('userpassword_confirm')" />
</td>
</tr>
@@ -167,15 +187,11 @@
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr>
- <th>
- <br />
- <input type="submit" class="submitbutton" name="submit" value="Submit"/>
- </th>
+ <th></th>
<td>
<br />
- <input type="submit" class="submitbutton" name="submit" value="Cancel" />
+ <input type="submit" class="submitbutton" name="submit" value="Add Person"/>
</td>
- <td></td>
</tr>
</table>
diff --git a/ipa-server/ipa-gui/ipagui/templates/usershow.kid b/ipa-server/ipa-gui/ipagui/templates/usershow.kid
index 4e73eba35..aff400c54 100644
--- a/ipa-server/ipa-gui/ipagui/templates/usershow.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/usershow.kid
@@ -8,6 +8,24 @@
<body>
<h2>View Person</h2>
+<?python
+from ipagui.helpers import userhelper
+pw_expires_days = userhelper.password_expires_in(user.get("krbPasswordExpiration"))
+pw_expires_soon = userhelper.password_expires_soon(pw_expires_days)
+pw_is_expired = userhelper.password_is_expired(pw_expires_days)
+if pw_expires_days != 1:
+ days_suffix = "s"
+else:
+ days_suffix = ""
+?>
+
+ <div py:if='pw_expires_soon' class="warning_message">
+ Password will expire in ${pw_expires_days} day${days_suffix}
+ </div>
+ <div py:if='pw_is_expired' class="warning_message">
+ Password has expired
+ </div>
+
<div class="formsection">Identity Details</div>
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr>
@@ -68,7 +86,7 @@
<th>
<label class="fieldlabel" py:content="fields.nsAccountLock.label" />:
</th>
- <td>${user.get("nsAccountLock")}</td>
+ <td>${userhelper.account_status_display(user.get("nsAccountLock"))}</td>
</tr>
</table>
diff --git a/ipa-server/ipa-install/README b/ipa-server/ipa-install/README
index fd6b74736..16fc4a799 100644
--- a/ipa-server/ipa-install/README
+++ b/ipa-server/ipa-install/README
@@ -2,7 +2,8 @@
Required packages:
krb5-server
-fedora-ds-base / fedora-ds-base-devel
+fedora-ds-base
+fedora-ds-base-devel
openldap-clients
krb5-server-ldap
cyrus-sasl-gssapi
@@ -13,12 +14,40 @@ openssl-devel
Installation example:
-TEMPORARY: (until fedora ds scripts are fixed)
-please use the fedora-ds.init.patch under share/ to patch your init scripts before
-running ipa-server-install
+TEMPORARY: until bug https://bugzilla.redhat.com/show_bug.cgi?id=248169 is
+ fixed.
-cd ipa-install
-make install
-cd ..
-/usr/sbin/ipa-server-install -u fds -r FREEIPA.ORG -p freeipa -m ipafree
+Please apply the fedora-ds.init.patch in freeipa/ipa-server/ipa-install/share/
+to patch your init scripts before running ipa-server-install. This tells
+FDS where to find its kerberos keytab.
+Things done as root are denoted by #. Things done as a unix user are denoted
+by %.
+
+# cd freeipa
+# patch -p0 < ipa-server/ipa-install/share/fedora-ds.init.patch
+
+Now to do the installation.
+
+# cd freeipa
+# make install
+# /usr/sbin/ipa-server-install -u fds -r FREEIPA.ORG -p freeipa -P ipafree
+
+For more verbose output add the -d flag
+
+You have a basic working system with one super administrator (named admin).
+
+To create another administrative user:
+
+% kinit admin@FREEIPA.ORG
+% /usr/sbin/ipa-adduser -f Test -l User test
+% ldappasswd -Y GSSAPI -h localhost -s password uid=test,cn=users,cn=accounts,dc=freeipa,dc=org
+% /usr/sbin/ipa-groupmod -a test admins
+
+An admin user is just a regular user in the group admin.
+
+Now you can destroy the old ticket and log in as test:
+
+% kdestroy
+% kinit test@FREEIPA.ORG
+% /usr/sbin/ipa-finduser test
diff --git a/ipa-server/ipa-slapi-plugins/Makefile b/ipa-server/ipa-slapi-plugins/Makefile
index a5d1c1913..23bcd94bd 100644
--- a/ipa-server/ipa-slapi-plugins/Makefile
+++ b/ipa-server/ipa-slapi-plugins/Makefile
@@ -1,4 +1,4 @@
-SUBDIRS=ipa-pwd-extop ipa-memberof
+SUBDIRS=ipa-pwd-extop ipa-memberof dna
all:
@for subdir in $(SUBDIRS); do \
diff --git a/ipa-server/ipa-slapi-plugins/dna/Makefile b/ipa-server/ipa-slapi-plugins/dna/Makefile
new file mode 100644
index 000000000..a4fccab23
--- /dev/null
+++ b/ipa-server/ipa-slapi-plugins/dna/Makefile
@@ -0,0 +1,34 @@
+DIRSRV ?= dirsrv
+PREFIX ?= $(DESTDIR)/usr
+LIBDIR ?= $(PREFIX)/lib/$(DIRSRV)/plugins
+LIB64DIR ?= $(PREFIX)/lib64/$(DIRSRV)/plugins
+SHAREDIR = $(DESTDIR)/usr/share/ipa
+
+SONAME = libipa-dna-plugin.so
+LDFLAGS += -llber
+CFLAGS ?= -g -Wall -Wshadow
+CFLAGS += -I/usr/include/$(DIRSRV) -I/usr/include/nss3 -I/usr/include/mozldap -I/usr/include/nspr4 -fPIC -DPIC
+
+OBJS = $(patsubst %.c,%.o,$(wildcard *.c))
+
+all: $(OBJS)
+ $(CC) $(LDFLAGS) $(OBJS) -Wl,-soname -Wl,$(SONAME) -shared -o $(SONAME)
+
+%.o: %.c
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+install:
+ -mkdir -p $(LIBDIR)
+ if [ -e $(PREFIX)/lib/$(DIRSRV) ]; then \
+ install -m 644 $(SONAME) $(LIBDIR); \
+ else \
+ install -m 644 $(SONAME) $(LIB64DIR); \
+ fi
+ install -m 644 *.ldif $(SHAREDIR)
+
+clean:
+ rm -f *.o
+ rm -f $(SONAME)
+ rm -f *~
+
+
diff --git a/ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif b/ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif
new file mode 100644
index 000000000..a133fcf46
--- /dev/null
+++ b/ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif
@@ -0,0 +1,13 @@
+dn: cn=ipa-dna,cn=plugins,cn=config
+objectclass: top
+objectclass: nsSlapdPlugin
+objectclass: extensibleObject
+cn: ipa-dna
+nsslapd-pluginpath: libipa-dna-plugin
+nsslapd-plugininitfunc: dna_init
+nsslapd-plugintype: postoperation
+nsslapd-pluginenabled: on
+nsslapd-pluginid: ipa-dna
+nsslapd-pluginversion: 1.0
+nsslapd-pluginvendor: Red Hat
+nsslapd-plugindescription: Distributed numeric assignment plugin
diff --git a/ipa-server/ipa-slapi-plugins/dna/dna.c b/ipa-server/ipa-slapi-plugins/dna/dna.c
new file mode 100644
index 000000000..399f89f00
--- /dev/null
+++ b/ipa-server/ipa-slapi-plugins/dna/dna.c
@@ -0,0 +1,1174 @@
+/** BEGIN COPYRIGHT BLOCK
+ * 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 of the License.
+ *
+ * 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.
+ *
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
+ * right to link the code of this Program with code not covered under the GNU
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
+ * permitted under this exception must only link to the code of this Program
+ * through those well defined interfaces identified in the file named EXCEPTION
+ * found in the source code files (the "Approved Interfaces"). The files of
+ * Non-GPL Code may instantiate templates or use macros or inline functions from
+ * the Approved Interfaces without causing the resulting work to be covered by
+ * the GNU General Public License. Only Red Hat, Inc. may make changes or
+ * additions to the list of Approved Interfaces. You must obey the GNU General
+ * Public License in all respects for all of the Program code and other code used
+ * in conjunction with the Program except the Non-GPL Code covered by this
+ * exception. If you modify this file, you may extend this exception to your
+ * version of the file, but you are not obligated to do so. If you do not wish to
+ * provide this exception without modification, you must delete this exception
+ * statement from your version and license this file solely under the GPL without
+ * exception.
+ *
+ *
+ * Author: Pete Rowley
+ *
+ * Copyright (C) 2007 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+
+/**
+ * Distributed Numeric Assignment plug-in
+ */
+#include "slapi-plugin.h"
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+/*#include "portable.h"*/
+#include "nspr.h"
+/*#include "slapi-private.h"*/
+/*#include "dirlite_strings.h"*/
+/*#include "dirver.h"*/
+
+#include "prclist.h"
+#include "ldif.h"
+
+/* get file mode flags for unix */
+#ifndef _WIN32
+#include <sys/stat.h>
+#endif
+
+#define DNA_PLUGIN_SUBSYSTEM "dna-plugin"
+#define DNA_PLUGIN_VERSION 0x00010000
+
+#define DNA_DN "cn=ipa-dna,cn=plugins,cn=config" /* temporary */
+
+#define DNA_SUCCESS 0
+#define DNA_FAILURE -1
+
+/**
+ * DNA config types
+ */
+#define DNA_TYPE "dnaType"
+#define DNA_PREFIX "dnaPrefix"
+#define DNA_NEXTVAL "dnaNextValue"
+#define DNA_INTERVAL "dnaInterval"
+#define DNA_GENERATE "dnaMagicRegen"
+#define DNA_FILTER "dnaFilter"
+#define DNA_SCOPE "dnaScope"
+
+#define FEATURE_DESC "Distributed Numeric Assignment"
+#define PLUGIN_DESC "Distributed Numeric Assignment plugin"
+
+static Slapi_PluginDesc pdesc = { FEATURE_DESC,
+ "FreeIPA project", "FreeIPA/1.0",
+ PLUGIN_DESC };
+
+
+/**
+ * linked list of config entries
+ */
+
+struct _defs {
+ PRCList list;
+ char *dn;
+ char *type;
+ char *prefix;
+ unsigned long nextval;
+ unsigned long interval;
+ struct slapi_filter *filter;
+ char *generate;
+ char *scope;
+} dna_anchor;
+typedef struct _defs configEntry;
+static PRCList *config;
+static PRRWLock *g_dna_cache_lock;
+
+static void *_PluginID = NULL;
+static char *_PluginDN = NULL;
+
+
+/*
+ * new value lock
+ */
+static Slapi_Mutex *g_new_value_lock;
+
+/**
+ *
+ * DNA plug-in management functions
+ *
+ */
+int dna_init(Slapi_PBlock *pb);
+static int dna_start(Slapi_PBlock *pb);
+static int dna_close(Slapi_PBlock *pb);
+static int dna_postop_init(Slapi_PBlock *pb);
+
+/**
+ *
+ * Local operation functions
+ *
+ */
+static int loadPluginConfig();
+static int parseConfigEntry(Slapi_Entry *e);
+static void deleteConfig();
+static void freeConfigEntry(configEntry **entry);
+
+/**
+ *
+ * helpers
+ *
+ */
+static char *dna_get_dn(Slapi_PBlock *pb);
+static int dna_dn_is_config(char *dn);
+static int dna_get_next_value(configEntry *config_entry, char **next_value_ret);
+
+/**
+ *
+ * the ops (where the real work is done)
+ *
+ */
+static int dna_config_check_post_op(Slapi_PBlock *pb);
+static int dna_pre_op( Slapi_PBlock *pb, int modtype );
+static int dna_mod_pre_op( Slapi_PBlock *pb );
+static int dna_add_pre_op( Slapi_PBlock *pb );
+
+/**
+ * debug functions - global, for the debugger
+ */
+void dnaDumpConfig();
+void dnaDumpConfigEntry(configEntry *);
+
+/**
+ * set the debug level
+ */
+#ifdef _WIN32
+int *module_ldap_debug = 0;
+
+void plugin_init_debug_level(int *level_ptr)
+{
+ module_ldap_debug = level_ptr;
+}
+#endif
+
+/**
+ *
+ * Deal with cache locking
+ *
+ */
+void dna_read_lock()
+{
+ PR_RWLock_Rlock(g_dna_cache_lock);
+}
+
+void dna_write_lock()
+{
+ PR_RWLock_Wlock(g_dna_cache_lock);
+}
+
+void dna_unlock()
+{
+ PR_RWLock_Unlock(g_dna_cache_lock);
+}
+
+/**
+ *
+ * Get the dna plug-in version
+ *
+ */
+int dna_version()
+{
+ return DNA_PLUGIN_VERSION;
+}
+
+/**
+ * Plugin identity mgmt
+ */
+void setPluginID(void * pluginID)
+{
+ _PluginID=pluginID;
+}
+
+void * getPluginID()
+{
+ return _PluginID;
+}
+
+void setPluginDN(char *pluginDN)
+{
+ _PluginDN = pluginDN;
+}
+
+char * getPluginDN()
+{
+ return _PluginDN;
+}
+
+/*
+ dna_init
+ -------------
+ adds our callbacks to the list
+*/
+int dna_init( Slapi_PBlock *pb )
+{
+ int status = DNA_SUCCESS;
+ char * plugin_identity=NULL;
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_init\n");
+
+ /**
+ * Store the plugin identity for later use.
+ * Used for internal operations
+ */
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
+ PR_ASSERT (plugin_identity);
+ setPluginID(plugin_identity);
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
+ (void *) dna_start ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *) dna_close ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN,
+ (void *) dna_mod_pre_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN,
+ (void *) dna_add_pre_op ) != 0 ||
+ /* the config change checking post op */
+ slapi_register_plugin(
+ "postoperation", /* op type */
+ 1, /* Enabled */
+ "dna_init", /* this function desc */
+ dna_postop_init, /* init func for post op */
+ PLUGIN_DESC, /* plugin desc */
+ NULL, /* ? */
+ plugin_identity /* access control */
+ )
+ )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
+ "dna_init: failed to register plugin\n" );
+ status = DNA_FAILURE;
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_init\n");
+ return status;
+}
+
+
+static int dna_postop_init(Slapi_PBlock *pb)
+{
+ int status = DNA_SUCCESS;
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN,
+ (void *) dna_config_check_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN,
+ (void *) dna_config_check_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN,
+ (void *) dna_config_check_post_op ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN,
+ (void *) dna_config_check_post_op ) != 0
+ )
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
+ "dna_postop_init: failed to register plugin\n" );
+ status = DNA_FAILURE;
+ }
+
+ return status;
+}
+
+/*
+ dna_start
+ --------------
+ Kicks off the config cache.
+ It is called after dna_init.
+*/
+static int dna_start( Slapi_PBlock *pb )
+{
+ char * plugindn = NULL;
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_start\n");
+
+ config = &dna_anchor.list;
+ g_dna_cache_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "dna");
+ g_new_value_lock = slapi_new_mutex();
+
+ if(!g_dna_cache_lock || !g_new_value_lock)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
+ "dna_start: lock creation failed\n" );
+
+ return DNA_FAILURE;
+ }
+
+ /**
+ * Get the plug-in target dn from the system
+ * and store it for future use. This should avoid
+ * hardcoding of DN's in the code.
+ */
+ slapi_pblock_get(pb, SLAPI_TARGET_DN, &plugindn);
+ if (plugindn == NULL || strlen(plugindn) == 0)
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM ,
+ "dna_start: had to use hard coded config dn\n");
+ plugindn = DNA_DN;
+ }
+ else
+ {
+ slapi_log_error( SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM ,
+ "dna_start: config at %s\n", plugindn);
+
+ }
+
+ setPluginDN(plugindn);
+
+ /**
+ * Load the config for our plug-in
+ */
+ PR_INIT_CLIST(config);
+ if (loadPluginConfig() != DNA_SUCCESS)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
+ "dna_start: unable to load plug-in configuration\n" );
+ return DNA_FAILURE;
+ }
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM , "dna: ready for service\n");
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_start\n");
+
+ return DNA_SUCCESS;
+}
+
+/*
+ dna_close
+ --------------
+ closes down the cache
+*/
+static int dna_close( Slapi_PBlock *pb )
+{
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_close\n");
+
+ deleteConfig();
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_close\n");
+
+ return DNA_SUCCESS;
+}
+
+/*
+ * config looks like this
+ * - cn=myplugin
+ * --- ou=posix
+ * ------ cn=accounts
+ * ------ cn=groups
+ * --- cn=samba
+ * --- cn=etc
+ * ------ cn=etc etc
+ */
+static int loadPluginConfig()
+{
+ int status = DNA_SUCCESS;
+ int result;
+ int i;
+ Slapi_PBlock *search_pb;
+ Slapi_Entry **entries = NULL;
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> loadPluginConfig\n");
+
+ dna_write_lock();
+ deleteConfig();
+
+ search_pb = slapi_pblock_new();
+
+ slapi_search_internal_set_pb(search_pb, DNA_DN, LDAP_SCOPE_SUBTREE,
+ "objectclass=*", NULL, 0, NULL, NULL, getPluginID(), 0);
+ slapi_search_internal_pb(search_pb);
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result);
+
+ if (status != DNA_SUCCESS)
+ {
+ status = DNA_SUCCESS;
+ goto cleanup;
+ }
+
+ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if (NULL == entries || entries[0] == NULL)
+ {
+ status = DNA_SUCCESS;
+ goto cleanup;
+ }
+
+ for (i = 0; (entries[i] != NULL); i++)
+ {
+ status = parseConfigEntry(entries[i]);
+ }
+
+cleanup:
+ slapi_free_search_results_internal(search_pb);
+ slapi_pblock_destroy(search_pb);
+ dna_unlock();
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- loadPluginConfig\n");
+
+ return status;
+}
+
+static int parseConfigEntry(Slapi_Entry *e)
+{
+ char *value = NULL;
+ configEntry *entry = NULL;
+ configEntry *config_entry = NULL;
+ PRCList *list = NULL;
+ int entry_added = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> parseConfigEntry\n");
+
+ entry = (configEntry*) slapi_ch_calloc(1, sizeof(configEntry));
+ if(0 == entry)
+ goto bail;
+
+ value = slapi_entry_get_ndn(e);
+ if(value) {
+ entry->dn = strdup(value);
+ }
+
+ slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dn [%s] \n",entry->dn,0,0);
+
+ value = slapi_entry_attr_get_charptr(e, DNA_TYPE);
+ if(value) {
+ entry->type = value;
+ }
+ else
+ goto bail;
+
+ slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaType [%s] \n",entry->type,0,0);
+
+ value = slapi_entry_attr_get_charptr(e, DNA_NEXTVAL);
+ if (value) {
+ entry->nextval = strtoul(value,0,0);
+ slapi_ch_free_string(&value);
+ value = 0;
+ }
+ else
+ goto bail;
+
+ slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaNextValue [%d] \n",entry->nextval,0,0);
+
+ value = slapi_entry_attr_get_charptr(e, DNA_PREFIX);
+ if (value) {
+ entry->prefix = value;
+ }
+
+ slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaPrefix [%s] \n",entry->prefix,0,0);
+
+ value = slapi_entry_attr_get_charptr(e, DNA_INTERVAL);
+ if (value) {
+ entry->interval = strtoul(value,0,0);
+ slapi_ch_free_string(&value);
+ value = 0;
+ }
+ else
+ goto bail;
+
+ slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaInterval [%s] \n",value,0,0);
+
+ value = slapi_entry_attr_get_charptr(e, DNA_GENERATE);
+ if (value) {
+ entry->generate = value;
+ }
+
+ slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaMagicRegen [%s] \n",entry->generate,0,0);
+
+ value = slapi_entry_attr_get_charptr(e, DNA_FILTER);
+ if (value) {
+ entry->filter = slapi_str2filter(value);
+ }
+ else
+ goto bail;
+
+ slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaFilter [%s] \n",value,0,0);
+
+ slapi_ch_free_string(&value);
+ value = 0;
+
+ value = slapi_entry_attr_get_charptr(e, DNA_SCOPE);
+ if (value) {
+ char *canonical_dn = slapi_dn_normalize(value);
+ entry->scope = canonical_dn;
+ }
+
+ slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "----------> dnaScope [%s] \n",entry->scope,0,0);
+
+
+ /**
+ * Finally add the entry to the list
+ * we group by type then by filter
+ * and finally sort by dn length with longer dn's
+ * first - this allows the scope checking
+ * code to be simple and quick and
+ * cunningly linear
+ */
+ if(!PR_CLIST_IS_EMPTY(config))
+ {
+ list = PR_LIST_HEAD(config);
+ while(list != config)
+ {
+ config_entry = (configEntry*)list;
+
+ if(slapi_attr_type_cmp(config_entry->type, entry->type,1))
+ goto next;
+
+ if(slapi_filter_compare(config_entry->filter, entry->filter))
+ goto next;
+
+ if(slapi_dn_issuffix(entry->scope,config_entry->scope))
+ {
+ PR_INSERT_BEFORE(&(entry->list), list);
+ slapi_log_error( SLAPI_LOG_CONFIG,
+ DNA_PLUGIN_SUBSYSTEM ,
+ "store [%s] before [%s] \n",entry->scope,config_entry->scope,0);
+ entry_added = 1;
+ break;
+ }
+
+next:
+ list = PR_NEXT_LINK (list);
+
+ if(config == list)
+ {
+ /* add to tail */
+ PR_INSERT_BEFORE(&(entry->list), list);
+ slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "store [%s] at tail\n",entry->scope,0,0);
+ entry_added = 1;
+ break;
+ }
+ }
+ }
+ else
+ {
+ /* first entry */
+ PR_INSERT_LINK(&(entry->list), config);
+ slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM , "store [%s] at head \n",entry->scope,0,0);
+ entry_added = 1;
+ }
+
+bail:
+ if(0 == entry_added)
+ {
+ slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM ,
+ "config entry [%s] skipped\n",entry->dn,0,0);
+ freeConfigEntry(&entry);
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- parseConfigEntry\n");
+
+ return DNA_SUCCESS;
+}
+
+static void freeConfigEntry(configEntry **entry)
+{
+ configEntry *e = *entry;
+
+ if(e->dn)
+ {
+ slapi_log_error( SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM ,
+ "freeing config entry [%s]\n",e->dn,0,0);
+ slapi_ch_free_string(&e->dn);
+ }
+
+ if(e->type)
+ slapi_ch_free_string(&e->type);
+
+ if(e->prefix)
+ slapi_ch_free_string(&e->prefix);
+
+ if(e->filter)
+ slapi_filter_free(e->filter,1);
+
+ if(e->generate)
+ slapi_ch_free_string(&e->generate);
+
+ if(e->scope)
+ slapi_ch_free_string(&e->scope);
+
+ slapi_ch_free((void**)entry);
+}
+
+static void deleteConfigEntry(PRCList *entry)
+{
+ PR_REMOVE_LINK(entry);
+ freeConfigEntry((configEntry**)&entry);
+}
+
+static void deleteConfig()
+{
+ PRCList *list;
+
+ while(!PR_CLIST_IS_EMPTY(config))
+ {
+ list = PR_LIST_HEAD(config);
+ deleteConfigEntry(list);
+ }
+
+ return;
+}
+
+
+/****************************************************
+ Helpers
+****************************************************/
+
+static char *dna_get_dn(Slapi_PBlock *pb)
+{
+ char *dn = 0;
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_get_dn\n");
+
+ if(slapi_pblock_get( pb, SLAPI_TARGET_DN, &dn ))
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM, "dna_get_dn: failed to get dn of changed entry");
+ goto bail;
+ }
+
+/* slapi_dn_normalize( dn );
+*/
+bail:
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_get_dn\n");
+
+ return dn;
+}
+
+/* config check
+ matching config dn or a descendent reloads config
+*/
+static int dna_dn_is_config(char *dn)
+{
+ int ret = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_is_config\n");
+
+ if(slapi_dn_issuffix(dn, getPluginDN()))
+ {
+ ret=1;
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_is_config\n");
+
+ return ret;
+}
+
+
+/****************************************************
+ Functions that actually do things other
+ than config and startup
+****************************************************/
+
+
+/*
+ * Perform ldap operationally atomic increment
+ * Return the next value to be assigned
+ * Method:
+ * 1. retrieve entry
+ * 2. remove current value, add new value in one operation
+ * 3. if failed, and less than 3 times, goto 1
+ */
+static int dna_get_next_value(configEntry *config_entry, char **next_value_ret)
+{
+ int ret = LDAP_SUCCESS;
+ Slapi_DN *dn = 0;
+ char *attrlist[3];
+ Slapi_Entry *e = 0;
+ int attempts = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_get_next_value\n");
+
+ /* get pre-requisites to search */
+ dn = slapi_sdn_new_dn_byref(config_entry->dn);
+ attrlist[0] = DNA_NEXTVAL;
+ attrlist[1] = DNA_INTERVAL;
+ attrlist[2] = 0;
+
+
+ /* the operation is constructed such that race conditions
+ * to increment the value are detected and avoided - one wins,
+ * one loses - however, there is no need for the server to compete
+ * with itself so we lock here
+ */
+
+ slapi_lock_mutex(g_new_value_lock);
+
+ while(attempts < 3 && LDAP_SUCCESS == ret)
+ {
+ attempts++;
+
+ /* do update */
+ if(e)
+ {
+ slapi_entry_free(e);
+ e = 0;
+ }
+
+ ret = slapi_search_internal_get_entry( dn, attrlist, &e,getPluginID());
+ if(LDAP_SUCCESS == ret)
+ {
+ char *old_value;
+
+ old_value = slapi_entry_attr_get_charptr(e, DNA_NEXTVAL);
+ if(old_value)
+ {
+ LDAPMod mod_add;
+ LDAPMod mod_delete;
+ LDAPMod *mods[3];
+ Slapi_PBlock *pb = slapi_pblock_new();
+ char *delete_val[2];
+ char *add_val[2];
+ char new_value[16];
+ char *interval = 0;
+
+ mods[0] = &mod_delete;
+ mods[1] = &mod_add;
+ mods[2] = 0;
+
+ if(0 == pb)
+ goto bail;
+
+ interval = slapi_entry_attr_get_charptr(e, DNA_INTERVAL);
+ if(0 == interval)
+ {
+ slapi_pblock_destroy(pb);
+ slapi_ch_free_string(&old_value);
+ goto bail;
+ }
+
+ /* perform increment */
+
+ sprintf(new_value, "%lu",
+ strtoul(interval,0,0) +
+ strtoul(old_value,0,0));
+
+ delete_val[0] = old_value;
+ delete_val[1] = 0;
+
+ mod_delete.mod_op = LDAP_MOD_DELETE;
+ mod_delete.mod_type = DNA_NEXTVAL;
+ mod_delete.mod_values = delete_val;
+
+ add_val[0] = new_value;
+ add_val[1] = 0;
+
+ mod_add.mod_op = LDAP_MOD_ADD;
+ mod_add.mod_type = DNA_NEXTVAL;
+ mod_add.mod_values = add_val;
+
+
+ mods[0] = &mod_delete;
+ mods[1] = &mod_add;
+ mods[2] = 0;
+
+ slapi_modify_internal_set_pb(
+ pb, config_entry->dn,
+ mods, 0, 0,
+ getPluginID(), 0);
+
+ slapi_modify_internal_pb(pb);
+
+ slapi_pblock_get(pb,
+ SLAPI_PLUGIN_INTOP_RESULT,
+ &ret);
+
+ slapi_pblock_destroy(pb);
+ slapi_ch_free_string(&interval);
+
+ if(LDAP_SUCCESS == ret)
+ {
+ *next_value_ret = old_value;
+ break;
+ }
+ else
+ {
+ slapi_ch_free_string(&old_value);
+ if(LDAP_NO_SUCH_ATTRIBUTE != ret)
+ {
+ /* not the result of a race
+ to change the value
+ */
+ break;
+ }
+ else
+ /* we lost the race to mod
+ try again
+ */
+ ret = LDAP_SUCCESS;
+ }
+ }
+ else
+ break;
+ }
+ else
+ break;
+ }
+
+bail:
+
+ slapi_unlock_mutex(g_new_value_lock);
+
+ if(dn)
+ slapi_sdn_free(&dn);
+
+ if(e)
+ slapi_entry_free(e);
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_get_next_value\n");
+
+ return ret;
+}
+
+/* for mods and adds:
+ where dn's are supplied, the closest in scope
+ is used as long as the type and filter
+ are identical - otherwise all matches count
+*/
+
+static int dna_pre_op(Slapi_PBlock *pb, int modtype)
+{
+ char *dn = 0;
+ PRCList *list = 0;
+ configEntry *config_entry = 0;
+ struct slapi_entry *e = 0;
+ char *last_type = 0;
+ char *value = 0;
+ int generate = 0;
+ Slapi_Mods *smods = 0;
+ Slapi_Mod *smod = 0;
+ LDAPMod **mods;
+ int free_entry = 0;
+ int ret = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_pre_op\n");
+
+ if(0 == (dn = dna_get_dn(pb)))
+ goto bail;
+
+ if(dna_dn_is_config(dn))
+ goto bail;
+
+ if(LDAP_CHANGETYPE_ADD == modtype)
+ {
+ slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &e);
+ }
+ else
+ {
+ /* xxxPAR: Ideally SLAPI_MODIFY_EXISTING_ENTRY should be
+ * available but it turns out that is only true if you are
+ * a dbm backend pre-op plugin - lucky dbm backend pre-op
+ * plugins.
+ * I think that is wrong since the entry is useful for filter
+ * tests and schema checks and this plugin shouldn't be limited
+ * to a single backend type, but I don't want that fight right
+ * now so we go get the entry here
+ *
+ slapi_pblock_get( pb, SLAPI_MODIFY_EXISTING_ENTRY, &e);
+ */
+ Slapi_DN *tmp_dn = slapi_sdn_new_dn_byref(dn);
+ if(tmp_dn)
+ {
+ slapi_search_internal_get_entry(
+ tmp_dn, 0, &e,getPluginID());
+ slapi_sdn_free(&tmp_dn);
+ free_entry = 1;
+ }
+
+ /* grab the mods - we'll put them back later with
+ * our modifications appended
+ */
+ slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods);
+ smods = slapi_mods_new();
+ slapi_mods_init_passin(smods, mods);
+ }
+
+ if(0 == e)
+ goto bailmod;
+
+ dna_read_lock();
+
+ if(!PR_CLIST_IS_EMPTY(config))
+ {
+ list = PR_LIST_HEAD(config);
+
+ while(list != config && LDAP_SUCCESS == ret)
+ {
+ config_entry = (configEntry*)list;
+
+ /* did we already service this type? */
+ if(last_type)
+ {
+ if(!slapi_attr_type_cmp(config_entry->type, last_type,1))
+ goto next;
+ }
+
+ /* is the entry in scope? */
+ if(config_entry->scope)
+ {
+ if(!slapi_dn_issuffix(dn, config_entry->scope))
+ goto next;
+ }
+
+ /* does the entry match the filter? */
+ if(config_entry->filter)
+ {
+ if(LDAP_SUCCESS != slapi_vattr_filter_test(pb,
+ e,
+ config_entry->filter,0))
+ goto next;
+ }
+
+
+ if(LDAP_CHANGETYPE_ADD == modtype)
+ {
+ /* does attribute contain the magic value
+ or is the type not there?
+ */
+ value = slapi_entry_attr_get_charptr(
+ e, config_entry->type);
+ if((value &&
+ !slapi_UTF8CASECMP(
+ config_entry->generate,
+ value)) ||
+ 0 == value)
+ {
+ generate = 1;
+ }
+ }
+ else
+ {
+ /* check mods for magic value */
+ Slapi_Mod *next_mod = slapi_mod_new();
+ smod = slapi_mods_get_first_smod(
+ smods,
+ next_mod);
+ while(smod)
+ {
+ char *type = (char *)
+ slapi_mod_get_type(smod);
+
+ if(slapi_attr_types_equivalent(
+ type,
+ config_entry->type))
+ {
+ struct berval *bv =
+ slapi_mod_get_first_value(
+ smod);
+ int len = strlen(
+ config_entry->
+ generate);
+
+
+ if(len == bv->bv_len)
+ {
+ if(!slapi_UTF8NCASECMP(
+ bv->bv_val,
+ config_entry->
+ generate,
+ len))
+
+ generate = 1;
+ break;
+ }
+ }
+
+ slapi_mod_done(next_mod);
+ smod = slapi_mods_get_next_smod(
+ smods,
+ next_mod);
+ }
+
+ slapi_mod_free(&next_mod);
+ }
+
+ if(generate)
+ {
+ char *new_value;
+ int len;
+
+ /* create the value to add */
+ if((ret = dna_get_next_value(config_entry,&value)))
+ break;
+
+ len = strlen(value) + 1;
+ if(config_entry->prefix)
+ {
+ len += strlen(config_entry->prefix);
+ }
+
+ new_value = slapi_ch_malloc(len);
+
+ if(config_entry->prefix)
+ {
+ strcpy(new_value,
+ config_entry->prefix);
+ strcat(new_value, value);
+ }
+ else
+ strcpy(new_value, value);
+
+ /* do the mod */
+ if(LDAP_CHANGETYPE_ADD == modtype)
+ {
+ /* add - add to entry */
+ slapi_entry_attr_set_charptr(
+ e,
+ config_entry->type,
+ new_value);
+ }
+ else
+ {
+ /* mod - add to mods */
+ slapi_mods_add_string(
+ smods,
+ LDAP_MOD_REPLACE,
+ config_entry->type,
+ new_value);
+ }
+
+ /* free up */
+ slapi_ch_free_string(&value);
+ slapi_ch_free_string(&new_value);
+
+ /* make sure we don't generate for this
+ * type again
+ */
+ if(LDAP_SUCCESS == ret)
+ {
+ last_type = config_entry->type;
+ }
+
+ generate = 0;
+ }
+next:
+ list = PR_NEXT_LINK (list);
+ }
+ }
+
+ dna_unlock();
+
+bailmod:
+ if(LDAP_CHANGETYPE_MODIFY == modtype)
+ {
+ /* these are the mods you made, really,
+ * I didn't change them, honest, just had a quick look
+ */
+ mods = slapi_mods_get_ldapmods_passout(smods);
+ slapi_pblock_set( pb, SLAPI_MODIFY_MODS, mods);
+ slapi_mods_free(&smods);
+ }
+
+bail:
+
+ if(free_entry && e)
+ slapi_entry_free(e);
+
+ if(ret)
+ slapi_log_error( SLAPI_LOG_PLUGIN, DNA_PLUGIN_SUBSYSTEM , "dna_pre_op: operation failure [%d]\n", ret);
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_pre_op\n");
+
+ return ret;
+}
+
+
+static int dna_add_pre_op( Slapi_PBlock *pb )
+{
+ return dna_pre_op(pb, LDAP_CHANGETYPE_ADD);
+}
+
+static int dna_mod_pre_op( Slapi_PBlock *pb )
+{
+ return dna_pre_op(pb, LDAP_CHANGETYPE_MODIFY);
+}
+
+static int dna_config_check_post_op(Slapi_PBlock *pb)
+{
+ char *dn;
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "--> dna_config_check_post_op\n");
+
+ if((dn = dna_get_dn(pb)))
+ {
+ if(dna_dn_is_config(dn))
+ loadPluginConfig();
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM , "<-- dna_config_check_post_op\n");
+
+ return 0;
+}
+
+/****************************************************
+ End of
+ Functions that actually do things other
+ than config and startup
+****************************************************/
+
+/**
+ * debug functions to print config
+ */
+void dnaDumpConfig()
+{
+ PRCList *list;
+
+ dna_read_lock();
+
+ if(!PR_CLIST_IS_EMPTY(config))
+ {
+ list = PR_LIST_HEAD(config);
+ while(list != config)
+ {
+ dnaDumpConfigEntry((configEntry*)list);
+ list = PR_NEXT_LINK (list);
+ }
+ }
+
+ dna_unlock();
+}
+
+
+void dnaDumpConfigEntry(configEntry *entry)
+{
+ printf("<- type --------------> %s\n", entry->type);
+ printf("<---- prefix ---------> %s\n", entry->prefix);
+ printf("<---- next value -----> %lu\n", entry->nextval);
+ printf("<---- interval -------> %lu\n", entry->interval);
+ printf("<---- generate flag --> %s\n", entry->generate);
+}
+
+
diff --git a/ipa-server/ipaserver/dsinstance.py b/ipa-server/ipaserver/dsinstance.py
index 841bc31f2..1f0704486 100644
--- a/ipa-server/ipaserver/dsinstance.py
+++ b/ipa-server/ipaserver/dsinstance.py
@@ -24,7 +24,7 @@ import tempfile
import shutil
import logging
import pwd
-from util import *
+from ipa.ipautil import *
SHARE_DIR = "/usr/share/ipa/"
diff --git a/ipa-server/ipaserver/ipaldap.py b/ipa-server/ipaserver/ipaldap.py
index 164509263..c0452b05a 100644
--- a/ipa-server/ipaserver/ipaldap.py
+++ b/ipa-server/ipaserver/ipaldap.py
@@ -35,13 +35,14 @@ import cStringIO
import time
import operator
import struct
+import ldap.sasl
from ldap.controls import LDAPControl,DecodeControlTuples,EncodeControlTuples
-from ldap.modlist import modifyModlist
-
from ldap.ldapobject import SimpleLDAPObject
-
from ipa import ipaerror, ipautil
+# Global variable to define SASL auth
+sasl_auth = ldap.sasl.sasl({},'GSSAPI')
+
class Entry:
"""This class represents an LDAP Entry object. An LDAP entry consists of a DN
and a list of attributes. Each attribute consists of a name and a list of
@@ -196,22 +197,34 @@ class IPAdmin(SimpleLDAPObject):
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
def __localinit__(self):
- SimpleLDAPObject.__init__(self,'ldaps://%s:%d' % (self.host,self.port))
+ """If a CA certificate is provided then it is assumed that we are
+ doing SSL client authentication with proxy auth.
+
+ If a CA certificate is not present then it is assumed that we are
+ using a forwarded kerberos ticket for SASL auth. SASL provides
+ its own encryption.
+ """
+ if self.cacert is not None:
+ SimpleLDAPObject.__init__(self,'ldaps://%s:%d' % (self.host,self.port))
+ else:
+ SimpleLDAPObject.__init__(self,'ldap://%s:%d' % (self.host,self.port))
def __init__(self,host,port,cacert,bindcert,bindkey,proxydn=None):
- """We just set our instance variables and wrap the methods - the real work is
- done in __localinit__ and __initPart2 - these are separated out this way so
- that we can call them from places other than instance creation e.g. when
- using the start command, we just need to reconnect, not create a new instance"""
+ """We just set our instance variables and wrap the methods - the real
+ work is done in __localinit__ and __initPart2 - these are separated
+ out this way so that we can call them from places other than
+ instance creation e.g. when we just need to reconnect, not create a
+ new instance"""
# ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
- ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,cacert)
- ldap.set_option(ldap.OPT_X_TLS_CERTFILE,bindcert)
- ldap.set_option(ldap.OPT_X_TLS_KEYFILE,bindkey)
+ if cacert is not None:
+ ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,cacert)
+ ldap.set_option(ldap.OPT_X_TLS_CERTFILE,bindcert)
+ ldap.set_option(ldap.OPT_X_TLS_KEYFILE,bindkey)
self.__wrapmethods()
self.port = port or 389
- self.sslport = 0
self.host = host
+ self.cacert = cacert
self.bindcert = bindcert
self.bindkey = bindkey
self.proxydn = proxydn
@@ -251,6 +264,12 @@ class IPAdmin(SimpleLDAPObject):
def set_proxydn(self, proxydn):
self.proxydn = proxydn
+ def set_keytab(self, keytab):
+ if keytab is not None:
+ os.environ["KRB5CCNAME"] = keytab
+ self.sasl_interactive_bind_s("", sasl_auth)
+ self.proxydn = None
+
def getEntry(self,*args):
"""This wraps the search function. It is common to just get one entry"""
@@ -346,7 +365,8 @@ class IPAdmin(SimpleLDAPObject):
sctrl = self.__get_server_controls__()
try:
- self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
+ if sctrl is not None:
+ self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
self.add_s(*args)
except ldap.ALREADY_EXISTS:
raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE)
@@ -366,7 +386,8 @@ class IPAdmin(SimpleLDAPObject):
raise ipaerror.gen_exception(ipaerror.LDAP_EMPTY_MODLIST)
try:
- self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
+ if sctrl is not None:
+ self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
self.modify_s(dn, modlist)
# this is raised when a 'delete' attribute isn't found.
# it indicates the previous attribute was removed by another
@@ -428,7 +449,8 @@ class IPAdmin(SimpleLDAPObject):
modlist.append((operation, "nsAccountlock", "true"))
try:
- self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
+ if sctrl is not None:
+ self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
self.modify_s(dn, modlist)
except ldap.LDAPError, e:
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
@@ -440,7 +462,8 @@ class IPAdmin(SimpleLDAPObject):
sctrl = self.__get_server_controls__()
try:
- self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
+ if sctrl is not None:
+ self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
self.delete_s(*args)
except ldap.LDAPError, e:
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
diff --git a/ipa-server/ipaserver/krbinstance.py b/ipa-server/ipaserver/krbinstance.py
index e17ab525b..d39a44e13 100644
--- a/ipa-server/ipaserver/krbinstance.py
+++ b/ipa-server/ipaserver/krbinstance.py
@@ -29,7 +29,7 @@ import os
import pwd
import socket
import time
-from util import *
+from ipa.ipautil import *
def host_to_domain(fqdn):
s = fqdn.split(".")
diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py
index f4e01b342..66fabf4be 100644
--- a/ipa-server/xmlrpc-server/funcs.py
+++ b/ipa-server/xmlrpc-server/funcs.py
@@ -47,20 +47,31 @@ DefaultGroupContainer = "cn=groups,cn=accounts"
# this is not anticipated.
class IPAConnPool:
def __init__(self):
- self.numentries = 0
self.freelist = []
- def getConn(self, host, port, bindca, bindcert, bindkey, proxydn=None):
- self.numentries = self.numentries + 1
+ def getConn(self, host, port, bindca, bindcert, bindkey, proxydn=None, keytab=None):
+ conn = None
if len(self.freelist) > 0:
- conn = self.freelist.pop()
- else:
+ for i in range(len(self.freelist)):
+ c = self.freelist[i]
+ if ((c.host == host) and (c.port == port)):
+ conn = self.freelist.pop(i)
+ break
+ if conn is None:
conn = ipaserver.ipaldap.IPAdmin(host,port,bindca,bindcert,bindkey)
- conn.set_proxydn(proxydn)
+ if proxydn is not None:
+ conn.set_proxydn(proxydn)
+ else:
+ conn.set_keytab(keytab)
return conn
def releaseConn(self, conn):
- self.freelist.append(conn)
+ # We can't re-use SASL connections. If proxydn is None it means
+ # we have a keytab set. See ipaldap.set_keytab
+ if conn.proxydn is None:
+ conn.unbind_s()
+ else:
+ self.freelist.append(conn)
class IPAServer:
@@ -68,7 +79,8 @@ class IPAServer:
global _LDAPPool
# FIXME, this needs to be auto-discovered
self.host = 'localhost'
- self.port = 636
+ self.port = 389
+ self.sslport = 636
self.bindcert = "/usr/share/ipa/cert.pem"
self.bindkey = "/usr/share/ipa/key.pem"
self.bindca = "/usr/share/ipa/cacert.asc"
@@ -79,24 +91,84 @@ class IPAServer:
self.basedn = ipa.ipautil.realm_to_suffix(ipa.config.config.get_realm())
self.scope = ldap.SCOPE_SUBTREE
self.princ = None
+ self.keytab = None
def set_principal(self, princ):
self.princ = princ
+
+ def set_keytab(self, keytab):
+ self.keytab = keytab
def get_dn_from_principal(self, princ):
- """Given a kerberls principal get the LDAP uid"""
+ """Given a kerberos principal get the LDAP uid"""
global _LDAPPool
filter = "(krbPrincipalName=" + princ + ")"
# The only anonymous search we should have
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,None)
+ conn = _LDAPPool.getConn(self.host,self.sslport,self.bindca,self.bindcert,self.bindkey,None,None)
try:
- ent = m1.getEntry(self.basedn, self.scope, filter, ['dn'])
+ ent = conn.getEntry(self.basedn, self.scope, filter, ['dn'])
finally:
- _LDAPPool.releaseConn(m1)
+ _LDAPPool.releaseConn(conn)
return "dn:" + ent.dn
+ def __setup_connection(self, opts):
+ """Set up common things done in the connection.
+ If there is a keytab then return None as the proxy dn and the keytab
+ otherwise return the proxy dn and None as the keytab.
+
+ We only want one or the other used at one time and we prefer
+ the keytab. So if there is a keytab, return that and None for
+ proxy dn to make calling getConn() easier.
+ """
+
+ if opts:
+ if opts.get('keytab'):
+ self.set_keytab(opts['keytab'])
+ self.set_principal(None)
+ else:
+ self.set_keytab(None)
+ self.set_principal(opts['remoteuser'])
+ else:
+ self.set_keytab(None)
+ # The caller should have already set the principal
+
+ if self.princ is not None:
+ return self.get_dn_from_principal(self.princ), None
+ else:
+ return None, self.keytab
+
+ def getConnection(self, opts):
+ """Wrapper around IPAConnPool.getConn() so we don't have to pass
+ around self.* every time a connection is needed.
+
+ For SASL connections (where we have a keytab) we can't set
+ the SSL variables for certificates. It confuses the ldap
+ module.
+ """
+ global _LDAPPool
+
+ (proxy_dn, keytab) = self.__setup_connection(opts)
+
+ if keytab is not None:
+ bindca = None
+ bindcert = None
+ bindkey = None
+ port = self.port
+ else:
+ bindca = self.bindca
+ bindcert = self.bindcert
+ bindkey = self.bindkey
+ port = self.sslport
+
+ return _LDAPPool.getConn(self.host,port,bindca,bindcert,bindkey,proxy_dn,keytab)
+
+ def releaseConnection(self, conn):
+ global _LDAPPool
+
+ _LDAPPool.releaseConn(conn)
+
def convert_entry(self, ent):
entry = dict(ent.data)
entry['dn'] = ent.dn
@@ -110,24 +182,17 @@ class IPAServer:
entry[key] = value[0]
return entry
-
def __get_entry (self, base, filter, sattrs=None, opts=None):
"""Get a specific entry. Return as a dict of values.
Multi-valued fields are represented as lists.
"""
- global _LDAPPool
ent=""
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
+ conn = self.getConnection(opts)
try:
- ent = m1.getEntry(base, self.scope, filter, sattrs)
+ ent = conn.getEntry(base, self.scope, filter, sattrs)
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
return self.convert_entry(ent)
@@ -137,8 +202,6 @@ class IPAServer:
oldentry is a dict
newentry is a dict
"""
- global _LDAPPool
-
oldentry = self.convert_scalar_values(oldentry)
newentry = self.convert_scalar_values(newentry)
@@ -150,16 +213,11 @@ class IPAServer:
except KeyError, e:
raise ipaerror.gen_exception(ipaerror.LDAP_MISSING_DN)
- if opts:
- self.set_principal(opts['remoteuser'])
-
- proxydn = self.get_dn_from_principal(self.princ)
-
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,proxydn)
+ conn = self.getConnection(opts)
try:
- res = m1.updateEntry(moddn, oldentry, newentry)
+ res = conn.updateEntry(moddn, oldentry, newentry)
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
return res
def __safe_filter(self, criteria):
@@ -234,8 +292,6 @@ class IPAServer:
attribute name and the value is either a string or in the case
of a multi-valued field a list of values. user_container sets
where in the tree the user is placed."""
- global _LDAPPool
-
if user_container is None:
user_container = DefaultUserContainer
@@ -288,16 +344,11 @@ class IPAServer:
for u in user:
entry.setValues(u, user[u])
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
+ conn = self.getConnection(opts)
try:
- res = m1.addEntry(entry)
+ res = conn.addEntry(entry)
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
return res
def get_add_schema (self):
@@ -348,20 +399,13 @@ class IPAServer:
"""Return a list containing a User object for each
existing user.
"""
- global _LDAPPool
-
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
filter = "(objectclass=posixAccount)"
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
+ conn = self.getConnection(opts)
try:
- all_users = m1.getList(self.basedn, self.scope, filter, None)
+ all_users = conn.getList(self.basedn, self.scope, filter, None)
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
users = []
for u in all_users:
@@ -372,13 +416,6 @@ class IPAServer:
def find_users (self, criteria, sattrs=None, opts=None):
"""Returns a list: counter followed by the results.
If the results are truncated, counter will be set to -1."""
- global _LDAPPool
-
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
# Assume the list of fields to search will come from a central
# configuration repository. A good format for that would be
# a comma-separated list of fields
@@ -389,26 +426,26 @@ class IPAServer:
criteria_words = re.split(r'\s+', criteria)
criteria_words = filter(lambda value:value!="", criteria_words)
if len(criteria_words) == 0:
- return []
+ return [0]
(exact_match_filter, partial_match_filter) = self.__generate_match_filters(
search_fields, criteria_words)
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
+ conn = self.getConnection(opts)
try:
try:
- exact_results = m1.getListAsync(self.basedn, self.scope,
+ exact_results = conn.getListAsync(self.basedn, self.scope,
exact_match_filter, sattrs)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
exact_results = [0]
try:
- partial_results = m1.getListAsync(self.basedn, self.scope,
+ partial_results = conn.getListAsync(self.basedn, self.scope,
partial_match_filter, sattrs)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
partial_results = [0]
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
exact_counter = exact_results[0]
partial_counter = partial_results[0]
@@ -450,13 +487,6 @@ class IPAServer:
def mark_user_deleted (self, uid, opts=None):
"""Mark a user as inactive in LDAP. We aren't actually deleting
users here, just making it so they can't log in, etc."""
- global _LDAPPool
-
- if opts:
- self.set_principal(opts['remoteuser'])
-
- proxydn = self.get_dn_from_principal(self.princ)
-
user = self.get_user_by_uid(uid, ['dn', 'uid', 'nsAccountlock'], opts)
# Are we doing an add or replace operation?
@@ -467,11 +497,11 @@ class IPAServer:
else:
has_key = False
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,proxydn)
+ conn = self.getConnection(opts)
try:
- res = m1.inactivateEntry(user['dn'], has_key)
+ res = conn.inactivateEntry(user['dn'], has_key)
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
return res
def delete_user (self, uid, opts=None):
@@ -483,18 +513,15 @@ class IPAServer:
The memberOf plugin handles removing the user from any other
groups.
"""
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
user_dn = self.get_user_by_uid(uid, ['dn', 'uid', 'objectclass'], opts)
if user_dn is None:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
- res = m1.deleteEntry(user_dn['dn'])
- _LDAPPool.releaseConn(m1)
+ conn = self.getConnection(opts)
+ try:
+ res = conn.deleteEntry(user_dn['dn'])
+ finally:
+ self.releaseConnection(conn)
return res
# Group support
@@ -532,8 +559,6 @@ class IPAServer:
attribute name and the value is either a string or in the case
of a multi-valued field a list of values. group_container sets
where in the tree the group is placed."""
- global _LDAPPool
-
if group_container is None:
group_container = DefaultGroupContainer
@@ -554,38 +579,26 @@ class IPAServer:
for g in group:
entry.setValues(g, group[g])
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
+ conn = self.getConnection(opts)
try:
- res = m1.addEntry(entry)
+ res = conn.addEntry(entry)
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
def find_groups (self, criteria, sattrs=None, opts=None):
"""Return a list containing a User object for each
existing group that matches the criteria.
"""
- global _LDAPPool
-
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
criteria = self.__safe_filter(criteria)
filter = "(&(cn=%s)(objectClass=posixGroup))" % criteria
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
+ conn = self.getConnection(opts)
try:
- results = m1.getList(self.basedn, self.scope, filter, sattrs)
+ results = conn.getList(self.basedn, self.scope, filter, sattrs)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
results = []
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
groups = []
for u in results:
@@ -599,9 +612,6 @@ class IPAServer:
group is the cn of the group to be added to
"""
- if opts:
- self.set_principal(opts['remoteuser'])
-
old_group = self.get_group_by_cn(group, None, opts)
if old_group is None:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
@@ -652,9 +662,6 @@ class IPAServer:
group is the cn of the group to be removed from
"""
- if opts:
- self.set_principal(opts['remoteuser'])
-
old_group = self.get_group_by_cn(group, None, opts)
if old_group is None:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
@@ -718,19 +725,16 @@ class IPAServer:
The memberOf plugin handles removing the group from any other
groups.
"""
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
group = self.get_group_by_cn(group_cn, ['dn', 'cn'], opts)
if len(group) != 1:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
- res = m1.deleteEntry(group[0]['dn'])
- _LDAPPool.releaseConn(m1)
+ conn = self.getConnection(opts)
+ try:
+ res = conn.deleteEntry(group[0]['dn'])
+ finally:
+ self.releaseConnection(conn)
return res
def add_group_to_group(self, group, tgroup, opts=None):
@@ -739,9 +743,6 @@ class IPAServer:
tgroup is the cn of the group to be added to
"""
- if opts:
- self.set_principal(opts['remoteuser'])
-
old_group = self.get_group_by_cn(tgroup, None, opts)
if old_group is None:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py
index 5dc60b51b..f2ddd35e8 100644
--- a/ipa-server/xmlrpc-server/ipaxmlrpc.py
+++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py
@@ -126,13 +126,19 @@ class ModXMLRPCRequestHandler(object):
def register_instance(self,instance):
self.register_module(instance)
- def _marshaled_dispatch(self, data, remoteuser):
+ def _marshaled_dispatch(self, data, req):
"""Dispatches an XML-RPC method from marshalled (XML) data."""
params, method = loads(data)
+ # Populate the Apache environment variables
+ req.add_common_vars()
+
opts={}
- opts['remoteuser'] = remoteuser
+ opts['remoteuser'] = req.user
+
+ if req.subprocess_env.get("KRB5CCNAME") is not None:
+ opts['keytab'] = req.subprocess_env.get("KRB5CCNAME")
# Tack onto the end of the passed-in arguments any options we also
# need
@@ -263,7 +269,7 @@ class ModXMLRPCRequestHandler(object):
req.allow_methods(['POST'],1)
raise apache.SERVER_RETURN, apache.HTTP_METHOD_NOT_ALLOWED
- response = self._marshaled_dispatch(req.read(), req.user)
+ response = self._marshaled_dispatch(req.read(), req)
req.content_type = "text/xml"
req.set_content_length(len(response))