1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
|
/*
Copyright (©) 2003-2025 Teus Benschop.
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; either version 3 of the License, or
(at your option) any later version.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <ldap/logic.h>
#include <filter/string.h>
#include <filter/url.h>
#include <filter/shell.h>
#include <filter/roles.h>
#include <database/logs.h>
/*
Public LDAP test server:
https://www.forumsys.com/en/tutorials/integration-how-to/ldap/online-ldap-test-server/
Examples of search queries against the test server:
ldapsearch -D "cn=read-only-admin,dc=example,dc=com" -w password -h ldap.forumsys.com -p 389 -b "dc=example,dc=com" -s sub "(objectclass=*)"
ldapsearch -D "uid=boyle,dc=example,dc=com" -w password -h ldap.forumsys.com -p 389
ldapsearch -h ldap.forumsys.com -p 389 -w password -D "cn=read-only-admin,dc=example,dc=com" -b "dc=example,dc=com" -s sub "(objectclass=*)"
ldapsearch -H ldap://ldap.forumsys.com -D "cn=read-only-admin,dc=example,dc=com" -w password -b "dc=example,dc=com" -s sub "(objectclass=*)"
ldapsearch -H ldap://ldap.forumsys.com -D "uid=boyle,dc=example,dc=com" -w password -b "dc=example,dc=com" -s sub "(uid=boyle)"
ldapsearch -H ldap://192.168.2.12 -D "cn=admin,dc=test,dc=com" -w openldap -b "dc=test,dc=com" -s sub "(objectClass=*)"
ldapsearch -H ldap://192.168.2.12 -D "cn=user,dc=test,dc=com" -w benschop -b "dc=test,dc=com" -s sub "(cn=user)"
*/
// These global variables contain the settings for the OpenLDAP server
// to query for user credentials.
std::string ldap_logic_uri {};
std::string ldap_logic_binddn {};
std::string ldap_logic_basedn {};
std::string ldap_logic_scope {};
std::string ldap_logic_filter {};
std::string ldap_logic_role {};
// Initialize the configuration for accessing an OpenLDAP server.
void ldap_logic_initialize ()
{
// Check if the OpenLDAP configuration file exists.
const std::string path = filter_url_create_root_path ({config::logic::config_folder (), "ldap.conf"});
if (file_or_dir_exists (path)) {
// Parse the configuration file.
const std::string contents = filter_url_file_get_contents (path);
const std::vector <std::string> lines = filter::strings::explode (contents, '\n');
for (auto line : lines) {
line = filter::strings::trim (line);
if (line.empty ()) continue;
if (line.substr (0, 1) == "#") continue;
size_t pos = line.find ("=");
const std::string key = filter::strings::trim (line.substr (0, pos));
line.erase (0, ++pos);
line = filter::strings::trim (line);
if (key == "uri" ) ldap_logic_uri = line;
if (key == "binddn") ldap_logic_binddn = line;
if (key == "basedn") ldap_logic_basedn = line;
if (key == "scope" ) ldap_logic_scope = line;
if (key == "filter") ldap_logic_filter = line;
if (key == "role" ) ldap_logic_role = line;
}
// Log the results.
if (ldap_logic_is_on (true)) {
Database_Logs::log ("Using LDAP for authentication");
}
}
}
// Clear LDAP configuration.
void ldap_logic_clear ()
{
ldap_logic_uri.clear ();
ldap_logic_binddn.clear ();
ldap_logic_basedn.clear ();
ldap_logic_scope.clear ();
ldap_logic_filter.clear ();
ldap_logic_role.clear ();
}
// Returns true if authentication through OpenLDAP is on.
bool ldap_logic_is_on (bool log)
{
if (ldap_logic_uri.empty ()) {
if (log) Database_Logs::log ("LDAP server configuration lacks the URI");
return false;
}
if (ldap_logic_binddn.empty ()) {
if (log) Database_Logs::log ("LDAP server configuration lacks the bind dn");
return false;
}
if (ldap_logic_basedn.empty ()) {
if (log) Database_Logs::log ("LDAP server configuration lacks the base dn");
return false;
}
if (ldap_logic_scope.empty ()) {
if (log) Database_Logs::log ("LDAP server configuration lacks the scope");
return false;
}
if (ldap_logic_filter.empty ()) {
if (log) Database_Logs::log ("LDAP server configuration lacks the search filter");
return false;
}
if (ldap_logic_role.empty ()) {
if (log) Database_Logs::log ("LDAP server configuration lacks the role field");
return false;
}
return true;
}
// Queries the LDAP server with credentials $user and $password.
// Parameter $access indicates whether the credentials have access to the server.
// Parameter $mail returns the email address.
// Parameter $role returns the user's role.
// If the query was done successfully, the function returns true.
bool ldap_logic_fetch (const std::string& user, const std::string& password, bool& access, std::string& email, int& role, bool log)
{
// Initialize result values for the caller.
access = false;
email.clear ();
role = roles::guest;
// Insert the user name where appropriate.
const std::string binddn = filter::strings::replace ("[user]", user, ldap_logic_binddn);
const std::string filter = filter::strings::replace ("[user]", user, ldap_logic_filter);
// Query the LDAP server.
std::string output {};
const int result = filter::shell::vfork (output, "",
filter::shell::get_executable(filter::shell::Executable::ldapsearch),
"-H", ldap_logic_uri.c_str (),
"-D", binddn.c_str (),
"-w", password.c_str (),
"-b", ldap_logic_basedn.c_str (),
"-s", ldap_logic_scope.c_str(),
filter.c_str());
// Logging.
if (log) {
const std::string command = std::string(filter::shell::get_executable(filter::shell::Executable::ldapsearch)) + " -H " + ldap_logic_uri + " -D " + binddn + " -w " + password + " -b " + ldap_logic_basedn + " -s " + ldap_logic_scope + " " + filter;
Database_Logs::log ("LDAP query\n" + command + "\n" + output, roles::admin);
}
// Check on invalid credentials.
if (result == 12544) {
return true;
}
// Parse server response.
if (result == 0) {
access = true;
const std::vector <std::string> lines = filter::strings::explode (output, '\n');
for (const auto& line : lines) {
if (line.find ("mail:") == 0) {
email = filter::strings::trim (line.substr (5));
}
if (line.find (ldap_logic_role + ":") == 0) {
const std::string fragment = filter::strings::unicode_string_casefold (filter::strings::trim (line.substr (3)));
for (int r = roles::lowest; r <= roles::highest; r++) {
if (fragment.find (filter::strings::unicode_string_casefold (roles::english (r))) != std::string::npos) {
role = r;
}
}
}
}
return true;
}
// Communication failure or another error.
return false;
}
|