2010-11-30

BOM Squad

So you have a lovely LDIF file of Active Directory schema that you want to import using the ldbmodify tool provided with Samba4... but when you attempt the import it fails with the error:
Error: First line of ldif must be a dn not 'dn'
Modified 0 records with 0 failures
Eh? @&^$*&@  &@^&*@ ^@&*^@&* It does start with a dn: attribute!
Once you cool down you look at the file using od, just in case, and you see:
0000000   o   ;   ?   d   n   :  sp   c   n   =   H   o   r   d   e   -
You've been bitten by the BOM! But even opening the file in vi you can't see the BOM because every tool knows about the BOM and deals with it - with the exception of anything LDIF related.
The trick is to break out dusty old sed and remove the BOM -
sed -e '1s/^\xef\xbb\xbf//' horde-person.ldf  > nobom.ldf
And double checking it with od again:
0000000   d   n   :  sp   c   n   =   H   o   r   d   e   -   A   g   o
The file now actually starts with a dn attribute!

2010-11-26

Samba4 & PHPLDAPAdmin

Samba4 includes a PHP LDAP admin configuration sample. Unfortunately it doesn't match up to the current version of PHP LDAP Admin. The configuration you want to put into config/config.php is:
$servers->newServer('ldap_pla');
$servers->setValue('server','name','Samba4 AD Server');
$servers->setValue('server','host','ldapi://%2Fopt%2Fad%2Fsamba4%2Fprivate%2Fldapi');
$servers->setValue('login','auth_type','session');
$servers->setValue('login','attr','dn');

Then you should get an PHP LDAP Admin login screen for your shiny Samba4 AD DSA. For the AD uninitiated the DN of the Administrator's account is "CN=Administrator,CN=Users,DC=ad,DC=mormail,DC=com" (for example, if you AD domain is "ad.mormail.com").  Using that DN and the domain administrator's password you should be able to login.
Another trick is to put that LDAPI LDAP URI into the /etc/openldap/ldap.conf  file so you can use the OpenLDAP LDAP CLI utilities [ldapsearch, ldapadd, ldapmodify, etc...] provided by your distribution.

Python Exceptions and "auto" Instantiation

Interesting thread on the python-list as to what happens regarding raising exceptions. The low-down: If you "raise ExceptionClass" and instance of that ExceptionClass is always created.

For example:
try: raise KeyError
except KeyError: pass

From the documentation:
raise evaluates the first expression as the exception object. It must be either a subclass or an instance of BaseException. If it is a class, the exception instance will be obtained when needed by instantiating the class with no arguments.

Some exception classes require arguments so the naked raise will fail with a TypeError.
 raise UnicodeDecodeError
Traceback (most recent call last):
  File "", line 1, in
TypeError: function takes exactly 5 arguments (0 given)

Seems to me it is clearer to just do the raise with instance syntax:
try:
  raise KeyError()
except KeyError as e:
  pass

2010-11-05

SQLAlchemy & Upcoming Birthdays

OpenGroupware Coils uses SQLAlchemy  as it's ORM. One of the desired features was a Logic command that efficiently returns contacts with upcoming birthdays.  In raw SQL this query would be very simple to write - but how to do it in SQLAlchemy? The answer: "sql.expression.extract" which will create an expression column equivalent to EXTRACT. With EXTRACT it is possible to compare to the year-of-day represented by a date.  The Python code looks like:
db = self._ctx.db_session()
# Get the current day-of-year
doy = datetime.today().timetuple().tm_yday
# Deal with year wrap-around
floor = doy - 2
if (floor < 1): floor +=365
ceiling = doy + 14
if (ceiling > 365): ceiling -= 365
# Create a field that is the SQL expression DOY(Contact.birth_date)
orm_doy = sql.expression.extract('doy', Contact.birth_date)
# Create the query
query = db.query(Contact).filter(and_(sql.expression.between(orm_doy, floor, ceiling),
                                      Contact.birth_date != None,
                                      Contact.is_account == self.accounts,
                                      Contact.status != 'archived'))
In this example "self.accounts" is an attribute with a value of 0 or 1.