@@ -2368,45 +2368,95 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
23682368 }
23692369#else
23702370#ifdef HAVE_LDAP_INITIALIZE
2371+
2372+ /*
2373+ * OpenLDAP provides a non-standard extension ldap_initialize() that takes
2374+ * a list of URIs, allowing us to request "ldaps" instead of "ldap". It
2375+ * also provides ldap_domain2hostlist() to find LDAP servers automatically
2376+ * using DNS SRV. They were introduced in the same version, so for now we
2377+ * don't have an extra configure check for the latter.
2378+ */
23712379 {
2372- const char * hostnames = port -> hba -> ldapserver ;
2373- char * uris = NULL ;
2380+ StringInfoData uris ;
2381+ char * hostlist = NULL ;
2382+ char * p ;
2383+ bool append_port ;
2384+
2385+ /* We'll build a space-separated scheme://hostname:port list here */
2386+ initStringInfo (& uris );
23742387
23752388 /*
2376- * We have a space-separated list of hostnames. Convert it
2377- * to a space-separated list of URIs.
2389+ * If pg_hba.conf provided no hostnames, we can ask OpenLDAP to try to
2390+ * find some by extracting a domain name from the base DN and looking
2391+ * up DSN SRV records for _ldap._tcp.<domain>.
23782392 */
2393+ if (!port -> hba -> ldapserver || port -> hba -> ldapserver [0 ] == '\0' )
2394+ {
2395+ char * domain ;
2396+
2397+ /* ou=blah,dc=foo,dc=bar -> foo.bar */
2398+ if (ldap_dn2domain (port -> hba -> ldapbasedn , & domain ))
2399+ {
2400+ ereport (LOG ,
2401+ (errmsg ("could not extract domain name from ldapbasedn" )));
2402+ return STATUS_ERROR ;
2403+ }
2404+
2405+ /* Look up a list of LDAP server hosts and port numbers */
2406+ if (ldap_domain2hostlist (domain , & hostlist ))
2407+ {
2408+ ereport (LOG ,
2409+ (errmsg ("LDAP authentication could not find DNS SRV records for \"%s\"" ,
2410+ domain ),
2411+ (errhint ("Set an LDAP server name explicitly." ))));
2412+ ldap_memfree (domain );
2413+ return STATUS_ERROR ;
2414+ }
2415+ ldap_memfree (domain );
2416+
2417+ /* We have a space-separated list of host:port entries */
2418+ p = hostlist ;
2419+ append_port = false;
2420+ }
2421+ else
2422+ {
2423+ /* We have a space-separated list of hosts from pg_hba.conf */
2424+ p = port -> hba -> ldapserver ;
2425+ append_port = true;
2426+ }
2427+
2428+ /* Convert the list of host[:port] entries to full URIs */
23792429 do
23802430 {
2381- char * hostname ;
2382- size_t hostname_size ;
2383- char * new_uris ;
2384-
2385- /* Find the leading hostname. */
2386- hostname_size = strcspn ( hostnames , " " );
2387- hostname = pnstrdup ( hostnames , hostname_size );
2388-
2389- /* Append a URI for this hostname. */
2390- new_uris = psprintf ( "%s%s%s ://%s:%d" ,
2391- uris ? uris : "" ,
2392- uris ? " " : "" ,
2393- scheme ,
2394- hostname ,
2395- port -> hba -> ldapport );
2396-
2397- pfree ( hostname );
2398- if ( uris )
2399- pfree ( uris );
2400- uris = new_uris ;
2401-
2402- /* Step over this hostname and any spaces. */
2403- hostnames += hostname_size ;
2404- while ( * hostnames == ' ' )
2405- ++ hostnames ;
2406- } while ( * hostnames );
2407-
2408- r = ldap_initialize (ldap , uris );
2409- pfree (uris );
2431+ size_t size ;
2432+
2433+ /* Find the span of the next entry */
2434+ size = strcspn ( p , " " );
2435+
2436+ /* Append a space separator if this isn't the first URI */
2437+ if ( uris . len > 0 )
2438+ appendStringInfoChar ( & uris , ' ' );
2439+
2440+ /* Append scheme ://host:port */
2441+ appendStringInfoString ( & uris , scheme );
2442+ appendStringInfoString ( & uris , "://" );
2443+ appendBinaryStringInfo ( & uris , p , size );
2444+ if ( append_port )
2445+ appendStringInfo ( & uris , ":%d" , port -> hba -> ldapport );
2446+
2447+ /* Step over this entry and any number of trailing spaces */
2448+ p += size ;
2449+ while ( * p == ' ' )
2450+ ++ p ;
2451+ } while ( * p );
2452+
2453+ /* Free memory from OpenLDAP if we looked up SRV records */
2454+ if ( hostlist )
2455+ ldap_memfree ( hostlist ) ;
2456+
2457+ /* Finally, try to connect using the URI list */
2458+ r = ldap_initialize (ldap , uris . data );
2459+ pfree (uris . data );
24102460 if (r != LDAP_SUCCESS )
24112461 {
24122462 ereport (LOG ,
@@ -2552,13 +2602,35 @@ CheckLDAPAuth(Port *port)
25522602 LDAP * ldap ;
25532603 int r ;
25542604 char * fulluser ;
2605+ const char * server_name ;
25552606
2607+ #ifdef HAVE_LDAP_INITIALIZE
2608+
2609+ /*
2610+ * For OpenLDAP, allow empty hostname if we have a basedn. We'll look for
2611+ * servers with DNS SRV records via OpenLDAP library facilities.
2612+ */
2613+ if ((!port -> hba -> ldapserver || port -> hba -> ldapserver [0 ] == '\0' ) &&
2614+ (!port -> hba -> ldapbasedn || port -> hba -> ldapbasedn [0 ] == '\0' ))
2615+ {
2616+ ereport (LOG ,
2617+ (errmsg ("LDAP server not specified, and no ldapbasedn" )));
2618+ return STATUS_ERROR ;
2619+ }
2620+ #else
25562621 if (!port -> hba -> ldapserver || port -> hba -> ldapserver [0 ] == '\0' )
25572622 {
25582623 ereport (LOG ,
25592624 (errmsg ("LDAP server not specified" )));
25602625 return STATUS_ERROR ;
25612626 }
2627+ #endif
2628+
2629+ /*
2630+ * If we're using SRV records, we don't have a server name so we'll just
2631+ * show an empty string in error messages.
2632+ */
2633+ server_name = port -> hba -> ldapserver ? port -> hba -> ldapserver : "" ;
25622634
25632635 if (port -> hba -> ldapport == 0 )
25642636 {
@@ -2630,7 +2702,7 @@ CheckLDAPAuth(Port *port)
26302702 ereport (LOG ,
26312703 (errmsg ("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s" ,
26322704 port -> hba -> ldapbinddn ? port -> hba -> ldapbinddn : "" ,
2633- port -> hba -> ldapserver ,
2705+ server_name ,
26342706 ldap_err2string (r )),
26352707 errdetail_for_ldap (ldap )));
26362708 ldap_unbind (ldap );
@@ -2658,7 +2730,7 @@ CheckLDAPAuth(Port *port)
26582730 {
26592731 ereport (LOG ,
26602732 (errmsg ("could not search LDAP for filter \"%s\" on server \"%s\": %s" ,
2661- filter , port -> hba -> ldapserver , ldap_err2string (r )),
2733+ filter , server_name , ldap_err2string (r )),
26622734 errdetail_for_ldap (ldap )));
26632735 ldap_unbind (ldap );
26642736 pfree (passwd );
@@ -2673,14 +2745,14 @@ CheckLDAPAuth(Port *port)
26732745 ereport (LOG ,
26742746 (errmsg ("LDAP user \"%s\" does not exist" , port -> user_name ),
26752747 errdetail ("LDAP search for filter \"%s\" on server \"%s\" returned no entries." ,
2676- filter , port -> hba -> ldapserver )));
2748+ filter , server_name )));
26772749 else
26782750 ereport (LOG ,
26792751 (errmsg ("LDAP user \"%s\" is not unique" , port -> user_name ),
26802752 errdetail_plural ("LDAP search for filter \"%s\" on server \"%s\" returned %d entry." ,
26812753 "LDAP search for filter \"%s\" on server \"%s\" returned %d entries." ,
26822754 count ,
2683- filter , port -> hba -> ldapserver , count )));
2755+ filter , server_name , count )));
26842756
26852757 ldap_unbind (ldap );
26862758 pfree (passwd );
@@ -2698,7 +2770,7 @@ CheckLDAPAuth(Port *port)
26982770 (void ) ldap_get_option (ldap , LDAP_OPT_ERROR_NUMBER , & error );
26992771 ereport (LOG ,
27002772 (errmsg ("could not get dn for the first entry matching \"%s\" on server \"%s\": %s" ,
2701- filter , port -> hba -> ldapserver ,
2773+ filter , server_name ,
27022774 ldap_err2string (error )),
27032775 errdetail_for_ldap (ldap )));
27042776 ldap_unbind (ldap );
@@ -2719,7 +2791,7 @@ CheckLDAPAuth(Port *port)
27192791 {
27202792 ereport (LOG ,
27212793 (errmsg ("could not unbind after searching for user \"%s\" on server \"%s\"" ,
2722- fulluser , port -> hba -> ldapserver )));
2794+ fulluser , server_name )));
27232795 pfree (passwd );
27242796 pfree (fulluser );
27252797 return STATUS_ERROR ;
@@ -2750,7 +2822,7 @@ CheckLDAPAuth(Port *port)
27502822 {
27512823 ereport (LOG ,
27522824 (errmsg ("LDAP login failed for user \"%s\" on server \"%s\": %s" ,
2753- fulluser , port -> hba -> ldapserver , ldap_err2string (r )),
2825+ fulluser , server_name , ldap_err2string (r )),
27542826 errdetail_for_ldap (ldap )));
27552827 ldap_unbind (ldap );
27562828 pfree (passwd );
0 commit comments