directoryassistant-2.0/0000755000175000017500000000000010346660730016462 5ustar olivierolivier00000000000000directoryassistant-2.0/Makefile0000644000175000017500000000113010346653223020114 0ustar olivierolivier00000000000000prefix = /usr INSTALL = install directoryassistant.1.gz: python -c 'import ldap' python -c 'import gobject' cat directoryassistant.1 | gzip -9 > directoryassistant.1.gz install: directoryassistant.1.gz ${INSTALL} -d -m 755 ${prefix}/share/man/man1/ ${INSTALL} -d -m 755 ${prefix}/bin/ ${INSTALL} -g 0 -o root -m 0644 directoryassistant.1.gz ${prefix}/share/man/man1/ ${INSTALL} -g 0 -o root -m 0755 directoryassistant ${prefix}/bin/ uninstall: rm -f ${prefix}/share/man/man1/directoryassistant.1.gz rm -f ${prefix}/bin/directoryassistant clean: rm -f *.gz rm -f *~ distclean: clean directoryassistant-2.0/directoryassistant0000755000175000017500000004453510346660725022365 0ustar olivierolivier00000000000000#!/usr/bin/python # # Directory Assistant 2.0 - Copyright 2003,2004,2005 Olivier Sessink # # Redistribution and use in source and binary forms, with or without # modification, are permitted as stated in the included COPYRIGHT file. # VERSIONSTRING = '2.0' import gtk import gobject import ldap import string import ConfigParser import os class field: def __init__(self,name,title='',editable=0,multiline=0): self.name = name self.title = title self.editable = editable self.multiline = multiline LDAP_KEY_MAP = [] LDAP_KEY_MAP.append(field('cn','Common name',0,0)) LDAP_KEY_MAP.append(field('sn','Surname',1,0)) LDAP_KEY_MAP.append(field('givenName','Given name',1,0)) LDAP_KEY_MAP.append(field('o','Organisation',1,0)) LDAP_KEY_MAP.append(field('mail','Email',1,0)) LDAP_KEY_MAP.append(field('telephoneNumber','Work phone number',1,0)) LDAP_KEY_MAP.append(field('facsimileTelephoneNumber','Fax number',1,0)) LDAP_KEY_MAP.append(field('mobile','Mobile phone number',1,0)) LDAP_KEY_MAP.append(field('street','Street',1,0)) LDAP_KEY_MAP.append(field('postalAddress','Postal address',1,1)) LDAP_KEY_MAP.append(field('postalCode','Postal code',1,0)) LDAP_KEY_MAP.append(field('l','Locality',1,0)) LDAP_KEY_MAP.append(field('homePhone','Home phone number',1,0)) LDAP_KEY_MAP.append(field('homePostalAddress','Home address',1,1)) LDAP_KEY_MAP.append(field('description','Description',1,1)) def all_keys(): ret = [] for field in LDAP_KEY_MAP: ret = ret + [field.name] return ret def all_editable_keys(): ret = [] for field in LDAP_KEY_MAP: if (field.editable): ret = ret + [field.name] return ret def field_for_key(key): for field in LDAP_KEY_MAP: if (field.name == key): return field return None class MyEntry: widget = None multiline = 0 def __init__(self,multiline=0): self.multiline = multiline if (self.multiline): self.buffer = gtk.TextBuffer() self.view = gtk.TextView(self.buffer) self.widget = gtk.ScrolledWindow() self.widget.add(self.view) self.widget.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) self.widget.set_shadow_type(gtk.SHADOW_ETCHED_IN) else: self.widget = gtk.Entry() def get_text(self): if (self.multiline): iters = self.buffer.get_bounds() return self.buffer.get_text(iters[0],iters[1]) else: return self.widget.get_text() def set_text(self,text): if (self.multiline): self.buffer.set_text(text) else: self.widget.set_text(text) class LdapBackend: "This class will do all actual ldap communication" def connect(self,debug=0): print 'connect to ',self.ldapurl,'with version',self.ldapversion self.ld = ldap.initialize(self.ldapurl,trace_level=debug) if (self.ldapversion != None): self.ld.set_option(ldap.OPT_PROTOCOL_VERSION,self.ldapversion) if (self.binddn != None and self.bindpw != None): print 'try user ',self.binddn try: self.ld.simple_bind_s(self.binddn, self.bindpw) except ldap.INVALID_CREDENTIALS: print 'Invalid ldap user or password' def __init__(self,cfg,section,debug=0): self.name = section self.ldapurl = cfg.get(section, 'ldapurl') self.baseDN = cfg.get(section, 'base_dn') try: self.binddn = cfg.get(section, 'bind_dn') self.bindpw = cfg.get(section, 'bind_password') except ConfigParser.NoOptionError: self.binddn = None self.bindpw = None try: self.ldapversion = int(cfg.get(section, 'ldapversion')) except (ConfigParser.NoOptionError,ValueError): self.ldapversion = None try: self.addDN = cfg.get(section,'add_dn') except (ConfigParser.NoOptionError): self.addDN = self.baseDN self.connect(debug) def get_address(self,dn): filter = dn[:string.find(dn,',')] try: entry = self.ld.search_s(self.baseDN,ldap.SCOPE_SUBTREE,filter,all_keys())[0][1] except ldap.SERVER_DOWN: self.connect(0) entry = self.ld.search_s(self.baseDN,ldap.SCOPE_SUBTREE,filter,all_keys())[0][1] return entry def get_dnlist_name(self,name): if (len(name)>0): pat = '(|(cn=*'+name+'*)(sn=*'+name+'*)(givenName=*'+name+'*))' else: pat = '(cn=*)' print 'searching for '+pat+' in '+self.baseDN try: res = self.ld.search_s(self.baseDN,ldap.SCOPE_SUBTREE,pat,['cn']) except ldap.SERVER_DOWN: self.connect(0) res = self.ld.search_s(self.baseDN,ldap.SCOPE_SUBTREE,pat,['cn']) results = {} i=len(res)-1 print 'found ',len(res),'results' while (i >= 0): dn = res[i][0] cn = res[i][1]['cn'][0] results[dn] = cn i -= 1 return results def get_dnlist_organisation(self,name): if (len(name)>0): pat = '(o=*'+name+'*)' else: # pat = '(&(cn=*)(o=*))' pat = '(o=*)' print 'searching for '+pat+' in '+self.baseDN try: res = self.ld.search_s(self.baseDN,ldap.SCOPE_SUBTREE,pat,['o']) except ldap.SERVER_DOWN: self.connect(0) res = self.ld.search_s(self.baseDN,ldap.SCOPE_SUBTREE,pat,['o']) results = {} i=len(res)-1 while (i >= 0): dn = res[i][0] cn = res[i][1]['o'][0] results[dn] = cn i -= 1 return results def saveAddress(self,cn,newaddress): modlist = [] for key in all_editable_keys(): if (len(newaddress[key]) > 0): modlist.append((key, newaddress[key])) modlist.append(('objectClass', 'inetOrgPerson')) modlist.append(('cn', cn)) dn = 'cn='+cn+','+self.addDN try: self.ld.add_s(dn, modlist) return 1 except ldap.SERVER_DOWN: self.connect(0) self.ld.add_s(dn, modlist) return 1 except ldap.INSUFFICIENT_ACCESS: return 0 def modifyAddress(self,dn,cn,oldaddress,newaddress): modlist = [] for key in all_editable_keys(): if (len(newaddress[key]) == 0 and oldaddress.has_key(key) and len(oldaddress[key]) > 0): modlist.append((ldap.MOD_DELETE,key,())) else: modlist.append((ldap.MOD_REPLACE, key, newaddress[key])) # now check if the dn should be updated pos = string.find(dn, ',') if (pos > 0): new_dn = 'cn='+cn+','+dn[pos+1:] else: new_dn = 'cn='+cn+','+self.addDN try: self.ld.modify_s(dn, modlist) if (new_dn != dn): new_rdn = ldap.explode_dn(new_dn)[0] self.ld.modrdn_s(dn, new_rdn) return 1 except ldap.INSUFFICIENT_ACCESS: return 0 def deleteAddress(self,dn): try: self.ld.delete_s(dn) except ldap.LDAPError, e: print e class EditGui: "This class is the gtk gui for the editor" def getlistforkey(self,key): j = len(self.entry[key]) field = field_for_key(key) lst = [] for i in range(0,j): tmp = self.entry[key][i].get_text() if (len(tmp)>0): lst.append(tmp) return lst def getnewaddress(self): newaddress = {} for key in all_editable_keys(): newaddress[key] = self.getlistforkey(key) return newaddress def saveclicked(self,data): newaddress = self.getnewaddress() ret = 0 cn = self.entry['sn'][0].get_text()+ ' '+self.entry['givenName'][0].get_text() try: if (self.dn): self.lb.modifyAddress(self.dn,cn,self.address,newaddress) else: self.lb.saveAddress(cn, newaddress) self.window.destroy() return except ldap.SERVER_DOWN: message = 'address server down' except ldap.INSUFFICIENT_ACCESS: message = 'Permission was denied while trying to save the address' dialog = gtk.MessageDialog(parent=self.window, flags=gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK,message_format = message) dialog.set_title('Error') dialog.connect('response', lambda dialog, response: dialog.destroy()) dialog.show_all() def plusclicked(self,widget,key): field = field_for_key(key) tmp = MyEntry(field.multiline) self.entry[key].append(tmp) self.vbox[key].pack_start(tmp.widget) tmp.widget.show() def cancelclicked(self,data): self.window.destroy() def addfieldentry(self,address,field,table,startat): # print 'adding ',field.title,field.name,'at startat=',startat table.attach(gtk.Label(field.title), 0,1,startat, startat+1,xoptions=gtk.FILL) if (field.editable): button = gtk.Button('+') button.connect('clicked', self.plusclicked, field.name) self.entry[field.name] = [] self.vbox[field.name] = gtk.VBox(True) table.attach(self.vbox[field.name], 1,2,startat, startat+1,xoptions=gtk.EXPAND|gtk.FILL) i=0 if (not address.has_key(field.name)): self.entry[field.name].append(MyEntry(field.multiline)) self.vbox[field.name].pack_start(self.entry[field.name][i].widget) else: j = len(address[field.name]) for i in range(0,j): self.entry[field.name].append(MyEntry(field.multiline)) self.entry[field.name][i].set_text(address[field.name][i]) self.vbox[field.name].pack_start(self.entry[field.name][i].widget) table.attach(button, 2,3,startat, startat+1,xoptions=gtk.FILL,yoptions=0) else: if (address.has_key(field.name)): table.attach(gtk.Label(address[field.name][0]),1,2,startat, startat+1,xoptions=gtk.FILL) def __init__(self, lb, dn, address): self.address = address self.dn = dn self.lb = lb self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title("Edit Address") self.window.set_border_width(10) vbox1 = gtk.VBox(False, True) self.window.add(vbox1) hbox1 = gtk.HBox(True, True) vbox1.pack_start(hbox1) numkeys = len(LDAP_KEY_MAP) self.ltable = gtk.Table((numkeys/2)+1, 3, False) self.ltable.set_row_spacings(6) self.ltable.set_col_spacings(6) hbox1.pack_start(self.ltable) self.rtable = gtk.Table((numkeys/2)+1, 3, False) self.rtable.set_row_spacings(6) self.rtable.set_col_spacings(6) hbox1.pack_start(self.rtable) self.entry = {} self.vbox = {} i = 0 for field in LDAP_KEY_MAP: print field.name if (i > numkeys/2): self.addfieldentry(address,field,self.rtable,i-numkeys/2) else: self.addfieldentry(address,field,self.ltable,i) i = i + 1 bbox = gtk.HButtonBox() bbox.set_layout(gtk.BUTTONBOX_END) bbox.set_spacing(10) vbox1.pack_start(bbox) button = gtk.Button('gtk-cancel') button.set_use_stock(1) button.connect('clicked',self.cancelclicked) bbox.add(button) button = gtk.Button('gtk-save') button.set_use_stock(1) button.connect('clicked',self.saveclicked) bbox.add(button) self.window.show_all() class AddressGui: "This class is the GTK GUI for the main window" dn = None address = None lb = None cfg = None def delete_event(self, widget, data): return False def destroy(self, widget, data=None): gtk.main_quit() def treevClicked(self,treev,event): if (event.type == 4): # 4 seems to be single-click, so we refresh if (self.dn != None): self.address = self.lb.get_address(self.dn) self.set_address_label(self.address) # 5 seems to be doubleclick, and 6 tripleclick if (event.type >= 5): if (self.dn): ea = EditGui(self.lb,self.dn,self.address) def newClicked(self,bla): ea = EditGui(self.lb,'',{}) def deleteClicked(self,bla): if (self.dn != None): self.lb.deleteAddress(self.dn) self.dn = None self.address = None self.searchClicked(None) def get_all(self,entry,prefix,suffix): str = '' i=len(entry)-1 if (i >= 0): while (i >= 0): str += prefix+self.prepare(entry[i])+suffix i -= 1 return str def prepare(self,str): str = string.replace(str,'&', '&') str = string.replace(str,'<', '<') str = string.replace(str,'>', '>') return str def set_address_label(self,entry): print 'set_address_label to ',entry if (entry.has_key('cn')): str = ''+self.prepare(entry['cn'][0])+'\n' if (entry.has_key('mail')): str += self.get_all(entry['mail'],'','\n') for key in all_keys(): if (not key in ['cn', 'sn', 'givenName', 'mail','o']): if (entry.has_key(key)): str += self.get_all(entry[key],'',' \n') elif (entry.has_key('o')): str = ''+self.prepare(entry['o'][0])+'\n' for key in all_keys(): if (not key in ['o']): if (entry.has_key(key)): str += self.get_all(entry[key],'',' \n') print 'set str to',str self.label.set_markup(str) def searchClicked(self, bla): if (self.ldservers != None): server = self.ldservers.get_active_text() if (self.lb.name != server): print 'switch to new server '+server self.lb = LdapBackend(self.cfg,server,0) self.listm.clear() str = self.entry.get_text() try: tmp1 = self.lb.get_dnlist_name(str) iter = None for k, v in tmp1.iteritems(): iter = self.listm.prepend() self.listm.set(iter, 0, v, 1, k) # print '(cn) adding '+k+' with v='+v tmp2 = self.lb.get_dnlist_organisation(str) for k, v in tmp2.iteritems(): if (k not in tmp1): iter = self.listm.prepend() self.listm.set(iter, 0, v, 1, k) print '(o) adding '+k+' with v='+v else: print '(o) NOT adding '+k+' with v='+v if (iter != None): selection = self.treev.get_selection() selection.select_iter(iter) self.treev.grab_focus() except ldap.SERVER_DOWN: self.label.set_markup('address server not available') def closeClicked(self,bla): self.window.destroy() def selectionChanged(self, data): store = data.get_selected()[0] iter = data.get_selected()[1] try: self.dn = store.get_value(iter,1) self.address = self.lb.get_address(self.dn) self.set_address_label(self.address) except: self.dn = None self.address = None self.label.set_markup("no result") def __init__(self, cfg): self.cfg = cfg self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title("Directory Assistant "+VERSIONSTRING) self.window.set_border_width(10) try: pixbuf = gtk.gdk.pixbuf_new_from_file('/usr/local/share/directoryassistant/directoryassistant.png') self.window.set_icon(pixbuf) except: pass self.window.connect('delete_event', self.delete_event) self.window.connect('destroy', self.destroy) self.window.set_border_width(10) vbox = gtk.VBox(False,10) self.window.add(vbox) # the search toolbar hbox = gtk.HBox() vbox.pack_start(hbox, False, True) try: image = gtk.Image() image.set_from_file('/usr/local/share/directoryassistant/decoration.png') hbox.pack_start(image) except: pass self.table = gtk.Table(2, 2, False) self.table.set_row_spacings(10) self.table.set_col_spacings(10) hbox.pack_start(self.table) label = gtk.Label("Search for") self.table.attach(label,0,1,0,1) self.entry = gtk.Entry() self.entry.connect("activate", self.searchClicked) self.table.attach(self.entry,1,2,0,1) if (cfg.numvalidsections >1): label = gtk.Label("in address book") self.table.attach(label,0,1,1,2) self.ldservers = gtk.combo_box_new_text() for sect in cfg.sections(): if (self.lb == None): self.lb = LdapBackend(self.cfg,sect,0) self.ldservers.prepend_text(sect) self.ldservers.set_active(0) self.table.attach(self.ldservers,1,2,1,2) else: self.ldservers = None for sect in cfg.sections(): self.lb = LdapBackend(self.cfg,sect,0) break # the paned paned = gtk.HPaned() vbox.pack_start(paned, True, True) #the left pane scrolpane = gtk.ScrolledWindow() self.listm = gtk.ListStore(str,str) self.listm.set_sort_column_id(1, gtk.SORT_ASCENDING) self.treev = gtk.TreeView(self.listm) scrolpane.set_size_request(150,200) rend = gtk.CellRendererText() column = gtk.TreeViewColumn('Results', rend, text=0) self.treev.append_column(column) selection = self.treev.get_selection() selection.set_mode(gtk.SELECTION_SINGLE) selection.connect('changed', self.selectionChanged) self.treev.connect('button_press_event',self.treevClicked) scrolpane.add_with_viewport(self.treev) paned.add1(scrolpane) #the right pane frame = gtk.Frame() frame.set_shadow_type(gtk.SHADOW_IN) self.label = gtk.Label() self.label.set_size_request(250,200) # self.label.set_justify(gtk.JUSTIFY_LEFT) # self.label.set_alignment(xalign=0.5, yalign=0.5) self.label.set_selectable(True) self.label.set_markup("no results yet"); frame.add(self.label) paned.add2(frame) # buttonbox bbox = gtk.HButtonBox() bbox.set_layout(gtk.BUTTONBOX_END) bbox.set_spacing(10) button = gtk.Button('gtk-delete') button.set_use_stock(1) bbox.add(button) button.connect('clicked', self.deleteClicked) button = gtk.Button('gtk-new') button.set_use_stock(1) bbox.add(button) button.connect('clicked', self.newClicked) button = gtk.Button('gtk-close') button.connect('clicked', self.closeClicked) button.set_use_stock(1) bbox.add(button) button = gtk.Button('gtk-find') button.set_use_stock(1) button.connect('clicked',self.searchClicked) button.set_flags(gtk.CAN_DEFAULT) self.window.set_default(button) bbox.add(button) vbox.pack_start(bbox,False,True) self.window.show_all() try: self.entry.set_text(cfg.get('main', 'startup_search')) self.searchClicked(None) except: pass self.entry.grab_focus() def main(self): gtk.main() class ErrorMessage: def quit(self, wid, data): gtk.main_quit() def __init__(self, message): dialog = gtk.Dialog(title="ERROR", flags=gtk.DIALOG_MODAL, buttons=(gtk.STOCK_OK, 1)) label = gtk.Label() label.set_markup(''+message+'') dialog.vbox.pack_start(label, True, True, 0) dialog.connect('close',self.quit) dialog.connect('response',self.quit) dialog.show_all() gtk.main() class MyConfigParser(ConfigParser.ConfigParser): "This class will make sure the configfile is correct" numvalidsections = 0 def section_is_correct(self,section): if (self.has_option(section,'ldapurl') and self.has_option(section,'base_dn')): return 1 return 0 def check(self): filename = os.getenv('HOME')+'/.directoryassistant' self.read((filename, '/etc/directoryassistant')) for sect in self.sections(): if (self.section_is_correct(sect)): self.numvalidsections+=1 else: self.remove_section(sect) if (self.numvalidsections >0): return 1 if (os.path.exists(filename)): message = 'Your configfile '+filename+' does not contain\nboth the required fields ldapurl and basedn.' else: message = 'Your configfile '+filename+' did not yet exist, an empty config file is created, but you have to set the correct values.' fd = open(filename, 'w') fd.write("[main]\n#ldapurl = ldap://myserver/\n#bind_dn = cn=myaccount,o=myorg\n#bind_password = mysecret\n#base_dn = ou=Mydepartment,o=myorg\n#startup_search=myname") fd.close() em = ErrorMessage(message) return 0 if __name__ == "__main__": cfg = MyConfigParser() if (cfg.check()): ag = AddressGui(cfg) ag.main() else: print 'Please create '+os.getenv('HOME')+'/.directoryassistant file,\nfollowing the format below:' print """ [My Address Book] ldapurl = ldap://your.ldap.server.com/ bind_dn = cn=someaccount,o=someorg bind_password = yourpassword' base_dn = ou=department,ou=People,o=someorg""" directoryassistant-2.0/README0000644000175000017500000000267210346653223017350 0ustar olivierolivier00000000000000see http://olivier.sessink.nl/directoryassistant/ **About** Directory Assistant is a small application for managing a LDAP address book. The focus is to create a very easy to use program, with only the few but necessary features. The target is novice users that still need to keep their addresses in an LDAP server. If you want an advanced application targeted at power users, check out http://biot.com/gq/. **Download & install** Unpack the tarball, cd directoryassistant-1.0/ and run ./install.py. It will show you the few files that are copied. (note: if it complains about not having the gtk module installed, and you are sure you have it, make sure you run the ./install.py script under X) Configuration is done by config file. Since novice users anyway don't know how to configure LDAP access, I decided to remove that from the GUI. The config file is however very simple. An example is shown below: [My Ldap Server] ldapurl = ldap://your.ldap.server.com/ bind_dn = cn=someaccount,o=someorg bind_password = yourpassword base_dn = ou=department,ou=People,o=someorg add_dn = ou=myunit,ou=department,ou=People,o=someorg ldapversion = 3 startup_search = if you leave 'startup_search' empty, it will show all ldap entries The config should be stored in $HOME/.directoryassistant or /etc/direcoryassistant **Contact** I have to make an email adress for this project.. In the meanwhile contact me at olivier (at) olivier dot pk dot wau dot nl directoryassistant-2.0/directoryassistant.desktop0000644000175000017500000000043210346653223024011 0ustar olivierolivier00000000000000[Desktop Entry] Encoding=UTF-8 Name=Directory assistant Name[nl]=Adresboek assistent Comment=LDAP addressbook Comment[en]=LDAP adresboek Exec=directoryassistant Icon=directoryassistant.png Terminal=false Type=Application StartupNotify=false Categories=GTK;GNOME;Application;Office; directoryassistant-2.0/directoryassistant.png0000644000175000017500000000320510346653223023125 0ustar olivierolivier00000000000000PNG  IHDRw=LIDATxmYlWsmgxqd [Q*a@)E^pQZQ!!7\QqU P/BEBTQIMIJg4$v/x3.*'}:ߑ{=vxGq< B,ͬ;҆TA R hH$R#QF` =aXR`@k4# IH!цD/cHg$f!C`i*9ڲ5#Va(SD!R|iE_YQhI!+ՋE0qP}bΣs(:E[GH(>џ&y y.fm_XF]fEF+N(VF1qs֯mU66n&3DgNQoQ7x[uvf*$% [͐ j4hZhncEԎ]'W^L1%޺2Su K8oI&.ENW81 -IP:wL*s܏/lT$m5H5*Ӏ7O<>P]lpt}mDwg'1"YZ!YT̈́Fu ܀x]~qڕׯڲFJ.w-|01?F/3Y[q]>7p{j4/rMl z:Y?H*x'̆Wqsa6nBPht+Btvء'_>].2JS)?g m~蕓'9쾏5 (wٴf-_XPiZItPw}iCvB}jA;XHrԵ4x/0vϿ8",c&^4gο^?ѿQ~lYHfdK#]T~3r~$  -TʞB>Zq0Go9RJFh(Rp hy%Of}ؕ8'X*c#]ۃضAۻh144Mb7pE&&n]>xᩃizk.(hMCN8.:;=L0U ]<~l?rv0:;]b0ٙ {{bB;ܮDoA.G4_ep,0y{zEމ0">(Ĺ_>mv݈FrYbHbLc>VQpߖ]yxi`&FJ4_}GR `mFc2Z!,Vyi:R=ȁU48¤ x^M ff\l !Fl` H),N߼A# y^8AY(B>@xƲR", !15=S&+$l"ޜ4 HN x<`7RahEM<᰹k).h Ciɫcϗ";bITQp' KI Uta!$6%L6C9aIENDB`directoryassistant-2.0/directoryassistant.10000644000175000017500000000203110346653223022475 0ustar olivierolivier00000000000000.TH directoryassistant 1 10-12-2005 directoryassistant directoryassistant .SH NAME directoryassistant \- user friendly ldap addressbook frontend .SH DESCRIPTION Directory Assistant is a small application for managing a LDAP address book. The focus is to create a very easy to use program, with only the few but necessary features. The target is novice users that need to keep their addresses in an LDAP server. .SH FILES Configuration is done by config file. Since novice users anyway don't know how to configure LDAP access, I decided to remove that from the GUI. The config file should be at .I /etc/directoryassistant or .I ~/.directoryassistant and should have the format: .nf .sp [My Ldap Server] ldapurl = ldap://your.ldap.server.com/ base_dn = ou=department,ou=People,o=someorg .fi extended options are: .nf .sp bind_dn = cn=someaccount,o=someorg bind_password = yourpassword add_dn = ou=myunit,ou=department,ou=People,o=someorg ldapversion = 3 startup_search = .fi .SH AUTHOR Directoryassistant is written by Olivier Sessink directoryassistant-2.0/decoration.png0000644000175000017500000002062210346653223021320 0ustar olivierolivier00000000000000PNG  IHDRBBT IDATxڭy,]?sNU޷m 3cI!$$EH J@A(("D!B`'V`ϛͼ^UǩxFi[uw-›w=woGɛ꫿Ήs‰'^Wk sCAU9" E*4(XTTUyk||[O?wapN'ǏO(r VXj TkjWxxB>BuE{ ~zTǃBb9ZI> wRUlgzT{/ME8!>R?9AbD0fقg`D;VDD ±EC@CƋ? ̥^I]4$ ݋oWYy?Kb0lB$P>zkn#x#Q<JQ&7)x?]k{&4#)q=h,%Eh|{.G?\BS8F0&2phI}%!)PRĀLeJfpo~c F,YX1ɑV`,!a:]lx4dOKL,pcf03^-vl{X*SFN\C"g9gIÊXKX#܉N 1`7O}0>Cw/e^yTbYK(7dmgjňx ` /oL—ŗR?>M$-~~pH^4҄nӽ9;m:  )> A}J@aax(f [UpqqN]q*T^AUpK$T;ߥ`lo0ZߠFةe?snFPtJLIsvS^NqSe>kSOH@OaWJr Uvia^`&)4 Ẽ yJo=Hm/Q5/wV~l0ƀ520L9<$N1",5i.,lB1$h0p8l-6'SbI`^Hak Ey=b#1ͧX5QEb(5%~=_uko U^g0'{\{{&STJ,Wӭho6Rh`sqEgrUh9wx1T,E"k-sK"|ms?w[wo0yΫ++ԚmQ5T)˂;!AfK=jƲy7nq}}hB<Uhm\!vvY8%>Qik`)(<[(RS J,.3=uz7RTR/z'NWܳ w[Eԥ"s ҳ71 ZS.amgx"1k Znչ.&p2!XXX ,u6Գ D('ݨsqy'ϟsYX\$_֝gѤ%2Š}N,bjbmbbykVļ]  *ʊ3|'Y2xZYKjҝcyK=KO vyÝ=v} ϳf$-Ău<#xM0"!|O|_܇[:ا'gG._Ś?c-.I֑YKXxEY18 dIB"IPy|Xz/`~ !cb =R_S">oeWG4|z?,uDn-Zkɜ:jiF=hj5$Z ֢Iح#FiEPq礿9{@VCn֢xw#ߊ sDm49wWVV9wx`ktO Y=^|ċ2Y#q;Ҍ)˺\}~ig_v3=0Z yb1/a\XSc @ v~'JˣI /k靻k5 Rr l}N5"'sYneɷB[M*ڛPm{0|q ~?<BUWE}O LG!`bwJ!s}ΕJk8pk?c]j+g/PVF#UMb>ݙdh@BLc1(a6ؿy4M KؤC} |kFWI}OBA(1xpx>;lomAhLeUjmv6y; LA"k x"Bjbyӄt;$o}su>sYo+8vUQV%{>eۘ:=||73x_j.s],"5k'ww6xΝMog<.UBCךYi6{4Ex,1C(^˃6nRKrxG 7A J*PR(3Pupl=ŅU}C7_dby7 ְBk1Ͽ8mG?&/ظg:`TќQp!Ѯb2v ( S+sZ f>wGxg/^X"7H46T*~|p@)ivs=$РJD:3 裼p.R(ۥ7=~᭍'wL 4, Ԍ(T* 4@Rye]8Jmsu^\Ҡ,.JrKޠިlԩe)f,kEՀ0Ki#u>iG|Xy>< %$ +\>wO%8Э%Ei  B$*Fd.&x6tHoxi%+{sΐzQ\W<:.<4{7faS4JVj4ٮ)޾){؇%d S:a3t@4EȬ%#1rY hc]旐EFj5H.@ v?) Js,\hx"WvyM}dXҾ@O!g/Avp܋s!`:]U:\PpoԘa%t{ W+عEhwDIӘuC 5&9*<XѸC)y@!I-3j r Wy^Ƚgj)JDɽr''X90Ab/"I~%II]Ҭv5_%Xo| ̅GpKjE7e1.dN=b~z2j |zÛ\{ETàD짨2E Ԙ8ֳ(6>:Jh<;ЕSH-]|A82L.䘒*qU>c U:,ė$מ,;{^)n{+Pe+1xl U"JTTKJyT$MxK:v~ _@O},)a=S8>}[xT~PFhY"e|VmY_|m| #QMKL%qqHV}v.EEUQh'z"ŘW?sAѢeCA| Vi3=wC܃{wڐfֺ#{fuj Ag㊟Cz33Xc_Ma`Ƥ,ւdIB9Cc|DZC7b l*J~hTt : sHAH3ԥG2uH@'ԓY%k2:czbUOԫIU*!yˬp`0F8DI)sj;["2\q Cdxz0$ݨYU.p; S!Y/R &h.c/c 0Tɝ7's帓0 8hbtZ(FB>Jil7G)Bp\ CURZYcA"QD A1q!n2ӘXH$AVCAΡƪTrσ'rhhYI>O~7*$pYF)TicWh)8$fsb_f .#:~;Kg5Y8]AnJ`'H#5#fhIVd$Ǿݹ#;sT+QN`BVq{Lr' 9L .͢˔8U@)!@I|f=+ɿ9rIylf3ƕn⤍&DvUoȈ{C.v\4"ϧf$EgcS2 (Z&V%꟡CH^ *:I&4#Ǒď$1ʲWX',|Q%#T-'y4MRC.BEr;4MIDATEꍶ6J#@fARY _ғڔ ,i~^zDkFb`!p[yQTc;_W;W}V_sdT+[grC6G$TVQ?Iqs_W '\IP99@SD!jךdpWEw&E>O^j~߿q]Ή  G4^g{TUc ^fS ۉV\T1?ȯǯ-k,_I_*nݿ_tm9=+PS/f\5Pa_L*{%>"?Μ{LNfTIENDB`directoryassistant-2.0/COPYRIGHT0000644000175000017500000000272710346653223017764 0ustar olivierolivier00000000000000Copyright (c) 2003,2004,2005 Olivier Sessink All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.