#!/usr/bin/env python ##################################################################### # $Id: moz2ldap.py,v 1.3 2004/01/03 14:28:25 stefanor Exp $ # # Stefano Rivera's Mozilla Address Book ldif to ldap ldif converter # Version 1.0 # Copyright (C) 2003 Stefano Rivera # # 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 ##################################################################### import base64, re, sys fieldRE = re.compile("^([^:]+):(:)? (.*)$") specialRE = re.compile("[^[:print:]]", re.M) # Matches text that needs base64 encoding dnRE = re.compile("^cn=(.*?)(, ?mail=(.*))?$") weirdDescRE = re.compile("^(.*?)<(.*?):(.*?)>(.*)\r?\n?$", re.M) # Sometimes they contain CRLF, so strip too... baseDN = "ou=Addresses, dc=owlsbarn, dc=rivera, dc=za, dc=net" myPeopleDn = "ou=People, " + baseDN myGroupsDn = "ou=Groups, " + baseDN remap = { "xmozillanickname": "mozillaNickname", "custom1": "mozillaCustom1", "custom2": "mozillaCustom2", "custom3": "mozillaCustom3", "custom4": "mozillaCustom4", "custom1": "mozillaCustom1", "homeurl": "mozillaHomeUrl", "mozilla_AimScreenName": "nsAIMid", "workurl": "mozillaWorkUrl", "xmozillausehtmlmail": "mozillaUseHtmlMail", "street": "streetAddress", "st": "stateOrProvinceName", "l": "localityName", } ##################################################################### def usage(): print("""Stefano Rivera's Mozilla to LDAP converter: Usage: moz2ldap.py [OPTION] [FILE]... Options: -h, --help Show this message. If no files are given, it will read standard input. """) ##################################################################### def fieldSort(a, b): "Get those dns first, closesly followed by objectclasses" if a[0] == "dn": return -100 elif b[0] == "dn": return 100 elif a[0] == "objectclass": return -50 elif b[0] == "objectclass": return 50 else: return cmp(a[0], b[0]) ##################################################################### def processFile(inputFile): "Process the given file" record = {} nextLine = inputFile.readline() while True: # Lines can break, so we need to read one line ahead... line = nextLine nextLine = inputFile.readline() if line == "": # EOF if len(record) != 0: processRecord(record) break elif line == "\r\n" or line == "\n" or line == "\r": # End of record if len(record) != 0: processRecord(record) record = {} else: line = line.strip() while nextLine.startswith(" ") or nextLine.startswith("\t"): # Continued line nextLine.strip() line += nextLine nextLine = inputFile.readline() f = fieldRE.match(line) if f == None: sys.stderr.write("Bad field '%s', skipping...\n" % line) continue name = f.group(1) if f.group(2) != None: #i.e. base64 encoded value = base64.decodestring(f.group(3)) else: value = f.group(3) if name not in record: record[name] = [] record[name].append(value) ##################################################################### def processRecord(record): "Check the record, fix it, and output it" if "dn" not in record: sys.stderr.write("Error, no dn!, skipping...\n") return if record['dn'][0].endswith(baseDN): # Already processed by me pass else: d = dnRE.match(record['dn'][0]) if d != None: cn = d.group(1) if d.group(2) != None: # i.e. Person dn = myPeopleDn else: dn = myGroupsDn if cn == " " or cn == "": sys.stderr.write("Missing cn in dn '%s', skipping...\n" % record['dn'][0]) return # Drop unneccesary quotation marks if "," not in cn and cn[0] == '"' and cn[-1] == '"': cn = cn[1:-1] # Handle commas gracefully cnField = cn if "," in cn: if cn[0] == '"' and cn[-1] == '"': cnField = cn[1:-1] else: cn = '"' + cn + '"' record['dn'][0] = "cn=%s, %s" % (cn, dn) record['cn'][0] = cnField else: sys.stderr.write("Bad dn: '%s', skipping...\n" % record['dn'][0]) return if "modifytimestamp" in record: del record['modifytimestamp'] if "mozillaAbPersonObsolete" in record['objectclass']: record['objectclass'].remove("mozillaAbPersonObsolete") record['objectclass'].append("mozillaOrgPerson") for old, new in remap.items(): if old in record: tmp = record[old] del record[old] record[new] = tmp if "mozillaUseHtmlMail" in record: # OpenLDAP likes capital boolean values record['mozillaUseHtmlMail'][0] = record['mozillaUseHtmlMail'][0].upper() if "groupOfNames" in record['objectclass']: # Reformat member dns newMembers = [] for member in record['member']: m = dnRE.match(member) if m != None: if m.group(2) != None: # i.e. Person dn = myPeopleDn else: dn = myGroupsDn newMembers.append("cn=%s, %s" % (m.group(1), dn)) else: sys.stderr.write("Bad member: '%s', skipping...\n" % member) record['member'] = newMembers # Try to preserve nickname if "xmozillanickname" in record: if "mozillaOrgPerson" not in record['objectclass']: record['objectclass'].append("mozillaOrgPerson") tmp = record['xmozillanickname'] del record['xmozillanickname'] record['mozillaNickname'] = tmp # Mozilla can be naughty and forget to obey MUSTs. :-( # I have already taken care of cn in the dn section if "person" in record['objectclass']: if not "sn" in record: record['sn'] = record['cn'][0].split()[-1:] # These cropped up in an addr book of mine. Either they come from Eudora imports or old Mozillas... # Handle gracefully if "description" in record: while True: w = weirdDescRE.match(record["description"][0]) if w == None: if record['description'][0].strip() == "": del record['description'] break # No such thing as do ... while in Python ;-) if w.group(2) == "otheremail": record['mozillaSecondEmail'] = [w.group(3)] record['description'][0] = w.group(1) + w.group(4) # Output record sr = record.items() sr.sort(fieldSort) for key, values in sr: for value in values: if value.startswith(" ") or specialRE.search(value) != None: value = base64.encodestring(value) sys.stdout.write("%s::" % key) for line in value.splitlines(True): sys.stdout.write(" %s" % line) else: sys.stdout.write("%s: %s\n" %(key, value)) sys.stdout.write("\n") ##################################################################### def main(): if len(sys.argv) > 1: if "-h" in sys.argv or "--help" in sys.argv: usage() sys.exit(0) for inputFileName in sys.argv[1:]: inputFile = open(inputFileName, "r") processFile(inputFile) inputFile.close() else: processFile(sys.stdin) if __name__ == '__main__': main()