1
/****************************************************************************
3
| Copyright (c) 2007 Novell, Inc.
6
| This program is free software; you can redistribute it and/or
7
| modify it under the terms of version 2 of the GNU General Public License as
8
| published by the Free Software Foundation.
10
| This program is distributed in the hope that it will be useful,
11
| but WITHOUT ANY WARRANTY; without even the implied warranty of
12
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
| GNU General Public License for more details.
15
| You should have received a copy of the GNU General Public License
16
| along with this program; if not, contact Novell, Inc.
18
| To contact Novell about this file by physical or electronic mail,
19
| you may find current contact information at www.novell.com
21
| Author: Bruce Getter <bgetter@novell.com>
22
|***************************************************************************/
26
using Novell.Directory.Ldap;
27
using Novell.Directory.Ldap.Utilclass;
30
using Simias.LdapProvider;
33
namespace Simias.OpenLdapProvider
36
/// Service class used to get an execution context
37
/// so we can register ourselves with the external
40
class OpenSync : Simias.IIdentitySyncProvider
43
private readonly string name = "Open LDAP Synchronization";
44
private readonly string description = "Open LDAP Synchronization provider to synchronize identities from Active Directory to a simias domain";
45
private bool abort = false;
47
private Simias.LdapProvider.Status syncStatus;
48
private static LdapSettings ldapSettings;
49
private Exception syncException;
51
private Store store = null;
52
private Domain domain = null;
54
private LdapConnection conn = null;
55
private Simias.IdentitySync.State state = null;
58
/// Used to log messages.
60
private static readonly ISimiasLog log =
61
SimiasLogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
66
/// Gets the name of the provider.
68
public string Name { get{ return name; } }
71
/// Gets the description of the provider.
73
public string Description { get{ return description; } }
76
#region Private Methods
78
private void ProcessSearchObjects( LdapConnection conn, LdapSettings settings )
80
foreach ( string searchContext in settings.SearchContexts )
82
string[] searchAttributes = { "objectClass" };
84
log.Debug( "SearchObject: " + searchContext );
88
LdapEntry ldapEntry = conn.Read( searchContext, searchAttributes );
89
LdapAttribute attrObjectClass = ldapEntry.getAttribute( "objectClass" );
90
String[] values = attrObjectClass.StringValueArray;
92
if ( IsUser( values ) == true )
94
// Process SearchDN as
95
log.Debug( "Processing User Object..." );
96
ProcessSearchUser( conn, searchContext );
98
else if ( IsGroup( values ) == true )
100
// Process SearchDN as
101
log.Debug( "Processing Group Object..." );
102
ProcessSearchGroup( conn, searchContext );
104
else if ( IsContainer( values ) == true )
106
// Process SearchDN as Container
107
log.Debug( "Processing Container Object..." );
108
ProcessSearchContainer( conn, searchContext );
112
log.Debug( "Invalid objectClass: " + values[0] );
113
log.Debug( attrObjectClass.ToString() );
116
catch( SimiasShutdownException s )
118
log.Error( s.Message );
121
catch ( LdapException e )
123
log.Error( e.LdapErrorMessage );
124
log.Error( e.StackTrace );
126
catch ( Exception e )
128
log.Error( e.Message );
129
log.Error( e.StackTrace );
134
// TODO: May need to investigate to see if the GUID needs to be stored in simias in a different
136
private string BuildGuidFilter( string guid )
138
string guidFilter = string.Format( "(uid={0})", guid );
142
private string GetLdapGuid( LdapEntry entry )
144
string ldapGuid = null;
148
LdapAttribute guidAttr = entry.getAttribute( "uid" );
149
if ( guidAttr != null && guidAttr.StringValue.Length != 0 )
151
ldapGuid = guidAttr.StringValue;
159
private bool IsUser( String[] objectClasses )
163
foreach( string s in objectClasses )
165
string lower = s.ToLower();
166
if ( lower == "inetorgperson" )
174
log.Error( "IsUser failed with exception" );
175
log.Error( e.Message );
180
private bool IsGroup( String[] objectClasses )
184
foreach( string s in objectClasses )
186
if ( s.ToLower() == "groupofnames" )
194
log.Error( "IsGroup failed with exception" );
195
log.Error( e.Message );
200
private bool IsContainer( String[] objectClasses )
202
bool isContainer = false;
206
foreach( string s in objectClasses )
208
string lower = s.ToLower();
209
if ( lower == "organization" )
211
log.Debug( "Processing Organization Object..." );
215
else if ( lower == "organizationalunit" )
218
log.Debug( "Processing OrganizationalUnit Object..." );
221
else if ( lower == "country" )
224
log.Debug( "Processing Country Object..." );
227
else if ( lower == "locality" )
230
log.Debug( "Processing Locality Object..." );
233
else if ( lower == "dcobject" )
236
log.Debug( "Processing DomainComponent Object..." );
243
log.Error( "IsContainer failed with exception" );
244
log.Error( e.Message );
250
private void ProcessSearchUser( LdapConnection connection, String searchUser )
252
// Since the first version of the iFolder 3.0 only
253
// exposes a username, firstname, lastname and full
254
// name, we'll limit the scope of the search
255
string[] searchAttributes = {
257
ldapSettings.NamingAttribute,
264
log.Debug( "ProcessSearchUser(" + searchUser + ")" );
268
LdapEntry ldapEntry = connection.Read( searchUser, searchAttributes );
269
ProcessUserEntry( ldapEntry );
271
catch( SimiasShutdownException s )
275
catch( LdapException e )
277
log.Error( e.LdapErrorMessage );
278
log.Error( e.StackTrace );
282
log.Error( e.Message );
283
log.Error( e.StackTrace );
287
// If the configured Simias Admin is different than the SimiasAdmin
288
// identified in the store, make all the changes necessary to
289
// make the configured admin the store admin.
290
private void ChangeSimiasAdmin( LdapConnection conn )
292
char[] dnDelimiters = {',', '='};
293
LdapEntry entry = null;
297
string[] searchAttributes = { "cn", "sn", "uid" };
301
// Nothing in the config for a SimiasAdmin - we're done here
302
if ( ldapSettings.AdminDN == null || ldapSettings.AdminDN == "" )
307
// If the SimiasAdmin has been changed in the Simias.config, which BTW
308
// is not something that is exposed in the normal management UI,
309
// we need to verify the new SimiasAdmin exists in the directory,
310
// check if the new admin exists in local domain memberlist (and
311
// if he doesn't create him), transfer default domain ownership
312
// to the new SimiasAdmin and lastly transfer ownership of all
313
// orphaned iFolders to the new SimiasAdmin.
317
entry = conn.Read( OpenSync.ldapSettings.AdminDN, searchAttributes );
319
catch( LdapException lEx )
321
log.Error( "Could not verify the newly configured Simias Administrator in the directory" );
322
log.Error( lEx.Message );
324
catch( Exception e1 )
326
log.Error( "Could not verify the newly configured Simias Administrator in the directory" );
327
log.Error( e1.Message );
335
ldapGuid = GetLdapGuid( entry );
336
if ( ldapGuid == null || ldapGuid == "" )
341
// Get the common name from the Simias.config.AdminDN entry
342
string[] components = OpenSync.ldapSettings.AdminDN.Split( dnDelimiters );
343
commonName = ( components[0].ToLower() == "cn" ) ? components[1] : components[0];
344
if ( commonName == null || commonName == "" )
349
store = Store.GetStore();
350
if ( domain == null )
352
domain = store.GetDomain( store.DefaultDomain );
353
if ( domain == null )
355
throw new SimiasException( "Enterprise domain does not exist!" );
359
Member member = domain.GetMemberByName( commonName );
360
if ( member == null )
362
// Create the member with the Ldap guid
364
new Member( commonName, ldapGuid, Simias.Storage.Access.Rights.ReadOnly );
365
member.Properties.ModifyProperty( "DN", ldapSettings.AdminDN );
368
Property lguid = new Property( "LdapGuid", ldapGuid );
369
lguid.LocalProperty = true;
370
member.Properties.ModifyProperty( lguid );
371
domain.Commit( member );
373
// Transfer ownership of all collections owned by the
374
// previous admin that have the orphaned property
376
ICSList subList = store.GetCollectionsByOwner( domain.Owner.ID, domain.ID );
377
foreach ( ShallowNode sn in subList )
379
// Get the collection object for this node.
380
Collection c = store.GetCollectionByID( sn.CollectionID );
383
orphaned = c.Properties.GetSingleProperty( "OrphanedOwner" );
384
if ( orphaned != null )
386
dn = c.Owner.Properties.GetSingleProperty( "DN" );
389
c.PreviousOwner = dn.Value.ToString();
393
c.Commit( c.ChangeOwner( member, Simias.Storage.Access.Rights.ReadWrite ) );
398
// For now I'm just going to leave the LdapGuid property
399
// on the old SimiasAdmin
400
dn = domain.Owner.Properties.GetSingleProperty( "DN" );
403
domain.PreviousOwner = dn.Value.ToString();
407
domain.Commit( domain.ChangeOwner( member, Simias.Storage.Access.Rights.ReadWrite ) );
409
catch( Exception vsa )
411
log.Error( vsa.Message );
412
log.Error( vsa.StackTrace );
416
// The SimiasAdmin is processed differently than normal simias users because
417
// the account is aleady created in the Simias store before LdapSync runs
418
// so the GUID has already been created. The SimiasAdmin must always exist in the
419
// store and the DN entry in the store must be correct with the Distinguished
420
// Name in the directory. LdapSync counts on the AdminDN entry in Simias.config
421
// to be updated if the admin is moved in the directory.
422
private void ProcessSimiasAdmin( LdapConnection conn )
424
// Since the first version of the iFolder 3.0 only
425
// exposes a username, firstname, lastname and full
426
// name, we'll limit the scope of the search
427
string[] searchAttributes = {
429
ldapSettings.NamingAttribute,
435
char[] dnDelimiters = {',', '='};
436
LdapEntry entry = null;
437
LdapAttribute timeStampAttr = null;
438
Member cMember = null;
440
string ldapGuid = null;
442
log.Debug( "ProcessSimiasAdmin( " + ldapSettings.AdminDN + ")" );
444
string namingContext = "";
445
entry = conn.Read( "", new string[] { "namingContexts" } );
448
namingContext = entry.getAttribute( "namingContexts" ).StringValue;
451
if ( domain == null )
453
store = Store.GetStore();
454
domain = store.GetDomain( store.DefaultDomain );
455
if ( domain == null )
457
throw new SimiasException( "Enterprise domain does not exist!" );
461
// If the DN property has never been set on the SimiasAdmin,
463
cMember = domain.Owner;
464
dn = cMember.Properties.GetSingleProperty( "DN" );
465
if ( dn == null || dn.Value.ToString() == "" )
467
if ( ldapSettings.AdminDN != null && ldapSettings.AdminDN != "" )
469
dn = new Property( "DN", ldapSettings.AdminDN );
470
cMember.Properties.ModifyProperty( dn );
474
// Check if the Simias Admin has changed in configuration
475
if ( ldapSettings.AdminDN != null && ldapSettings.AdminDN != "" &&
476
dn.Value.ToString() != ldapSettings.AdminDN )
478
ChangeSimiasAdmin( conn );
479
cMember = domain.Owner;
482
// The Simias admin is tracked in the directory by the directory
483
// guid. Make sure the guid is stored in the node
484
Property lguidProp = cMember.Properties.GetSingleProperty( "LdapGuid" );
485
if ( lguidProp == null )
487
// This must be the first time thru so let's get the directory
488
// entry based on the configured DN
491
entry = conn.Read( ldapSettings.AdminDN, searchAttributes );
493
catch( LdapException lEx )
495
log.Error( "The Simias Administrator does not exist in the Ldap directory as configured in Simias.config!" );
496
log.Error( lEx.Message );
498
catch( Exception e1 )
500
log.Error( "The Simias Administrator does not exist in the Ldap directory as configured in Simias.config!" );
501
log.Error( e1.Message );
506
ldapGuid = GetLdapGuid( entry );
507
lguidProp = new Property( "LdapGuid", ldapGuid );
508
lguidProp.LocalProperty = true;
509
cMember.Properties.ModifyProperty( lguidProp );
514
ldapGuid = lguidProp.Value.ToString();
517
if ( ldapGuid != null )
523
// Now go find the SimiasAdmin in the Ldap directory
524
string guidFilter = BuildGuidFilter( ldapGuid );
525
LdapSearchResults results =
528
LdapConnection.SCOPE_SUB,
529
"(&(objectclass=inetOrgPerson)" + guidFilter + ")",
532
if ( results.hasMore() == true )
534
entry = results.next();
537
catch ( LdapException e )
539
log.Error( e.LdapErrorMessage );
540
log.Error( e.StackTrace );
542
catch ( Exception e )
544
log.Error( e.Message );
545
log.Error( e.StackTrace );
551
// check if the ldap object's time stamp has changed
555
timeStampAttr = entry.getAttribute( "modifytimestamp" );
557
cMember.Properties.GetSingleProperty( "LdapTimeStamp" );
559
if ( ( pStamp == null ) ||
561
(string) pStamp.Value != timeStampAttr.StringValue ) )
563
// The time stamp changed let's look at first and
568
bool changed = false;
570
// If we're tracking by ldap see if the naming attribute
572
LdapAttribute namingAttr = entry.getAttribute( ldapSettings.NamingAttribute );
573
if ( namingAttr != null && namingAttr.StringValue.Length != 0 )
575
if ( namingAttr.StringValue != cMember.Name )
577
cMember.Name = namingAttr.StringValue;
581
LdapAttribute givenAttr = entry.getAttribute( "givenName" );
582
if ( givenAttr != null && givenAttr.StringValue.Length != 0 )
584
if ( givenAttr.StringValue != cMember.Given )
587
cMember.Given = givenAttr.StringValue;
591
LdapAttribute sirAttr = entry.getAttribute( "sn" );
592
if ( sirAttr != null && sirAttr.StringValue.Length != 0 )
594
if ( sirAttr.StringValue != cMember.Family )
596
cMember.Family = sirAttr.StringValue;
602
// If the entry has changed and we have a valid
604
if ( changed == true &&
605
cMember.Given != null &&
606
cMember.Given != "" &&
607
cMember.Family != null &&
608
cMember.Family != "" )
610
cMember.FN = cMember.Given + " " + cMember.Family;
613
// Did the distinguished name change?
614
Property dnProp = cMember.Properties.GetSingleProperty( "DN" );
615
if ( dnProp != null && ( dnProp.ToString() != entry.DN ) )
617
dnProp.Value = entry.DN;
618
cMember.Properties.ModifyProperty( "DN", dnProp );
623
pStamp = new Property( "LdapTimeStamp", timeStampAttr.StringValue );
624
pStamp.LocalProperty = true;
625
cMember.Properties.ModifyProperty( pStamp );
632
log.Error( "The Simias administrator could not be verified in the directory!" );
633
log.Error( "Please update Simias.config with a valid Ldap user" );
638
log.Error( "The Simias administrator could not be verified in the directory!" );
639
log.Error( "Please update Simias.config with a valid Ldap user" );
642
// Now matter what always update the sync guid so
643
// the SimiasAdmin won't be deleted from Simias
645
cMember.Properties.ModifyProperty( state.SyncGuid );
646
domain.Commit( cMember );
649
private void ProcessSearchGroup( LdapConnection conn, String searchGroup )
651
string[] searchAttributes = {
656
log.Debug( "ProcessSearchGroup(" + searchGroup + ")" );
662
LdapEntry ldapEntry = conn.Read( searchGroup, searchAttributes );
663
String[] members = ldapEntry.getAttribute("member").StringValueArray;
665
foreach( String member in members )
667
// Check if the sync engine wants us to abort
668
if ( this.abort == true )
673
log.Debug( " Processing member: " + member );
675
ProcessSearchUser( conn, member );
678
catch( SimiasShutdownException s )
682
catch( LdapException e )
684
log.Error( e.LdapErrorMessage );
685
log.Error( e.StackTrace );
689
log.Error( e.Message );
690
log.Error( e.StackTrace );
693
log.Debug( "Processed " + count.ToString() + " entries" );
696
private void ProcessSearchContainer(LdapConnection conn, String searchContainer)
698
String searchFilter = "(objectclass=inetOrgPerson)";
699
string[] searchAttributes = {
701
ldapSettings.NamingAttribute,
708
log.Debug( "ProcessSearchContainer(" + searchContainer + ")" );
711
LdapSearchConstraints searchConstraints = new LdapSearchConstraints();
712
searchConstraints.MaxResults = 0;
714
LdapSearchQueue queue =
717
LdapConnection.SCOPE_SUB,
721
(LdapSearchQueue) null,
724
LdapMessage ldapMessage;
725
while( ( ldapMessage = queue.getResponse() ) != null )
727
// Check if the sync engine wants us to abort
728
if ( this.abort == true )
733
if ( ldapMessage is LdapSearchResult )
735
LdapEntry cEntry = ((LdapSearchResult) ldapMessage).Entry;
743
ProcessUserEntry( cEntry );
746
catch( SimiasShutdownException s )
748
log.Error( s.Message );
751
catch( LdapException e )
753
log.Error( " Failed processing: " + cEntry.DN );
754
log.Error( e.LdapErrorMessage );
755
log.Error( e.StackTrace );
759
log.Error( " Failed processing: " + cEntry.DN );
760
log.Error( e.Message );
761
log.Error( e.StackTrace );
766
log.Debug( "Processed " + count.ToString() + " entries" );
769
private void ProcessUserEntry( LdapEntry entry )
771
log.Debug( "ProcessUserEntry(" + entry.DN + ")" );
773
string commonName = String.Empty;
774
string firstName = String.Empty;
775
string lastName = String.Empty;
776
string fullName = String.Empty;
777
string distinguishedName = String.Empty;
778
string ldapGuid = null;
780
char[] dnDelimiters = {',', '='};
781
LdapAttribute timeStampAttr = null;
783
bool attrError = false;
786
// get the last update time
787
timeStampAttr = entry.getAttribute( "modifytimestamp" );
789
ldapGuid = GetLdapGuid( entry );
790
distinguishedName = entry.DN;
792
// retrieve from configuration the directory attribute configured
793
// for naming in Simias.
794
LdapAttribute cAttr =
795
entry.getAttribute( ldapSettings.NamingAttribute );
796
if ( cAttr != null && cAttr.StringValue.Length != 0 )
798
commonName = cAttr.StringValue;
801
if ( ldapSettings.NamingAttribute.ToLower() == LdapSettings.DefaultNamingAttribute.ToLower() )
803
// If the naming attribute is default (cn) then we want to continue
804
// to work the way we previously did so we don't break any existing installs.
806
// If the distinguishing attribute did not exist,
807
// then make the Simias username the first component
809
string[] components = entry.DN.Split( dnDelimiters );
810
commonName = components[1];
813
LdapAttribute givenAttr = entry.getAttribute( "givenName" );
814
if ( givenAttr != null && givenAttr.StringValue.Length != 0 )
816
firstName = givenAttr.StringValue as string;
819
LdapAttribute sirAttr = entry.getAttribute( "sn" );
820
if ( sirAttr != null && sirAttr.StringValue.Length != 0 )
822
lastName = sirAttr.StringValue as string;
825
if ( firstName != null && lastName != null )
827
fullName = firstName + " " + lastName;
830
catch( Exception gEx )
832
log.Error( gEx.Message );
833
log.Error( gEx.StackTrace );
835
state.ReportError( gEx.Message );
839
// No exception were generated gathering member info
840
// so call the sync engine to process this member
841
if ( attrError == false )
843
if ( timeStampAttr != null && timeStampAttr.StringValue.Length != 0 )
845
Property ts = new Property( "LdapTimeStamp", timeStampAttr.StringValue );
846
ts.LocalProperty = true;
847
Property[] propertyList = { ts };
874
#region Public Methods
876
/// Call to abort an in process synchronization
878
/// <returns>N/A</returns>
885
/// Call to inform a provider to start a synchronization cycle
887
/// <returns> True - provider successfully finished a sync cycle,
888
/// False - provider failed the sync cycle
890
public bool Start( Simias.IdentitySync.State State )
892
log.Debug( "Start called" );
902
ldapSettings = LdapSettings.Get( Store.StorePath );
903
log.Debug( "new LdapConnection" );
904
conn = new LdapConnection();
906
log.Debug( "Connecting to: " + ldapSettings.Host + " on port: " + ldapSettings.Port.ToString() );
907
conn.SecureSocketLayer = ldapSettings.SSL;
908
conn.Connect( ldapSettings.Host, ldapSettings.Port );
910
ProxyUser proxy = new ProxyUser();
912
log.Debug( "Binding as: " + proxy.UserDN );
913
conn.Bind( proxy.UserDN, proxy.Password );
915
ProcessSimiasAdmin( conn );
916
ProcessSearchObjects( conn, ldapSettings );
918
catch( SimiasShutdownException s )
920
log.Error( s.Message );
922
syncStatus = Simias.LdapProvider.Status.SyncThreadDown;
924
catch( LdapException e )
926
log.Error( e.LdapErrorMessage );
927
log.Error( e.StackTrace );
931
? Simias.LdapProvider.Status.LdapConnectionFailure
932
: Simias.LdapProvider.Status.LdapAuthenticationFailure;
934
state.ReportError( e.LdapErrorMessage );
938
log.Error( e.Message );
939
log.Error( e.StackTrace );
941
syncStatus = Simias.LdapProvider.Status.InternalException;
943
state.ReportError( e.Message );
949
log.Debug( "Disconnecting Ldap connection" );
958
log.Error( e.Message );
959
log.Error( e.StackTrace );
960
State.ReportError( e.Message );