1 #include "ldap_config_mgr.hpp"
2 #include "ldap_config.hpp"
3 #include "ldap_config_serialize.hpp"
4 
5 #include "utils.hpp"
6 #include <filesystem>
7 #include <fstream>
8 #include <sstream>
9 
10 namespace phosphor
11 {
12 namespace ldap
13 {
14 
15 constexpr auto nscdService = "nscd.service";
16 constexpr auto LDAPscheme = "ldap";
17 constexpr auto LDAPSscheme = "ldaps";
18 
19 using namespace phosphor::logging;
20 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
21 namespace fs = std::filesystem;
22 using Argument = xyz::openbmc_project::Common::InvalidArgument;
23 
24 using Line = std::string;
25 using Key = std::string;
26 using Val = std::string;
27 using ConfigInfo = std::map<Key, Val>;
28 
29 void ConfigMgr::startOrStopService(const std::string& service, bool start)
30 {
31     if (start)
32     {
33         restartService(service);
34     }
35     else
36     {
37         stopService(service);
38     }
39 }
40 
41 void ConfigMgr::restartService(const std::string& service)
42 {
43     try
44     {
45         auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
46                                           SYSTEMD_INTERFACE, "RestartUnit");
47         method.append(service.c_str(), "replace");
48         bus.call_noreply(method);
49     }
50     catch (const sdbusplus::exception::SdBusError& ex)
51     {
52         log<level::ERR>("Failed to restart service",
53                         entry("SERVICE=%s", service.c_str()),
54                         entry("ERR=%s", ex.what()));
55         elog<InternalFailure>();
56     }
57 }
58 void ConfigMgr::stopService(const std::string& service)
59 {
60     try
61     {
62         auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
63                                           SYSTEMD_INTERFACE, "StopUnit");
64         method.append(service.c_str(), "replace");
65         bus.call_noreply(method);
66     }
67     catch (const sdbusplus::exception::SdBusError& ex)
68     {
69         log<level::ERR>("Failed to stop service",
70                         entry("SERVICE=%s", service.c_str()),
71                         entry("ERR=%s", ex.what()));
72         elog<InternalFailure>();
73     }
74 }
75 
76 void ConfigMgr::deleteObject()
77 {
78     configPtr.reset(nullptr);
79 }
80 
81 std::string ConfigMgr::createConfig(
82     std::string lDAPServerURI, std::string lDAPBindDN, std::string lDAPBaseDN,
83     std::string lDAPBindDNPassword, CreateIface::SearchScope lDAPSearchScope,
84     CreateIface::Create::Type lDAPType, std::string groupNameAttribute,
85     std::string userNameAttribute)
86 {
87     bool secureLDAP = false;
88 
89     if (isValidLDAPURI(lDAPServerURI, LDAPSscheme))
90     {
91         secureLDAP = true;
92     }
93     else if (isValidLDAPURI(lDAPServerURI, LDAPscheme))
94     {
95         secureLDAP = false;
96     }
97     else
98     {
99         log<level::ERR>("bad LDAP Server URI",
100                         entry("LDAPSERVERURI=%s", lDAPServerURI.c_str()));
101         elog<InvalidArgument>(Argument::ARGUMENT_NAME("lDAPServerURI"),
102                               Argument::ARGUMENT_VALUE(lDAPServerURI.c_str()));
103     }
104 
105     if (secureLDAP && !fs::exists(tlsCacertFile.c_str()))
106     {
107         log<level::ERR>("LDAP server's CA certificate not provided",
108                         entry("TLSCACERTFILE=%s", tlsCacertFile.c_str()));
109         elog<NoCACertificate>();
110     }
111 
112     if (lDAPBindDN.empty())
113     {
114         log<level::ERR>("Not a valid LDAP BINDDN",
115                         entry("LDAPBINDDN=%s", lDAPBindDN.c_str()));
116         elog<InvalidArgument>(Argument::ARGUMENT_NAME("LDAPBindDN"),
117                               Argument::ARGUMENT_VALUE(lDAPBindDN.c_str()));
118     }
119 
120     if (lDAPBaseDN.empty())
121     {
122         log<level::ERR>("Not a valid LDAP BASEDN",
123                         entry("LDAPBASEDN=%s", lDAPBaseDN.c_str()));
124         elog<InvalidArgument>(Argument::ARGUMENT_NAME("LDAPBaseDN"),
125                               Argument::ARGUMENT_VALUE(lDAPBaseDN.c_str()));
126     }
127 
128     // With current implementation we support only one LDAP server.
129     deleteObject();
130 
131     auto objPath = std::string(LDAP_CONFIG_DBUS_OBJ_PATH);
132     configPtr = std::make_unique<Config>(
133         bus, objPath.c_str(), configFilePath.c_str(), tlsCacertFile.c_str(),
134         secureLDAP, lDAPServerURI, lDAPBindDN, lDAPBaseDN,
135         std::move(lDAPBindDNPassword),
136         static_cast<ConfigIface::SearchScope>(lDAPSearchScope),
137         static_cast<ConfigIface::Type>(lDAPType), false, groupNameAttribute,
138         userNameAttribute, *this);
139 
140     restartService(nscdService);
141     return objPath;
142 }
143 
144 void ConfigMgr::restore(const char* filePath)
145 {
146     if (!fs::exists(filePath))
147     {
148         log<level::ERR>("Config file doesn't exists",
149                         entry("LDAP_CONFIG_FILE=%s", configFilePath.c_str()));
150         return;
151     }
152 
153     ConfigInfo configValues;
154     try
155     {
156         std::fstream stream(filePath, std::fstream::in);
157         Line line;
158         // read characters from stream and places them into line
159         while (std::getline(stream, line))
160         {
161             // remove leading and trailing extra spaces
162             auto firstScan = line.find_first_not_of(' ');
163             auto first =
164                 (firstScan == std::string::npos ? line.length() : firstScan);
165             auto last = line.find_last_not_of(' ');
166             line = line.substr(first, last - first + 1);
167             // reduce multiple spaces between two words to a single space
168             auto pred = [](char a, char b) {
169                 return (a == b && a == ' ') ? true : false;
170             };
171 
172             auto lastPos = std::unique(line.begin(), line.end(), pred);
173 
174             line.erase(lastPos, line.end());
175 
176             // Ignore if line is empty or starts with '#'
177             if (line.empty() || line.at(0) == '#')
178             {
179                 continue;
180             }
181 
182             Key key;
183             std::istringstream isLine(line);
184             // extract characters from isLine and stores them into
185             // key until the delimitation character ' ' is found.
186             // If the delimiter is found, it is extracted and discarded
187             // the next input operation will begin after it.
188             if (std::getline(isLine, key, ' '))
189             {
190                 Val value;
191                 // extract characters after delimitation character ' '
192                 if (std::getline(isLine, value, ' '))
193                 {
194                     // skip line if it starts with "base shadow" or
195                     // "base passwd" because we would have 3 entries
196                     // ("base lDAPBaseDN" , "base passwd lDAPBaseDN" and
197                     // "base shadow lDAPBaseDN") for the property "lDAPBaseDN",
198                     // one is enough to restore it.
199 
200                     if ((key == "base") &&
201                         (value == "passwd" || value == "shadow"))
202                     {
203                         continue;
204                     }
205 
206                     // if config type is AD "map group" entry would be add to
207                     // the map configValues. For OpenLdap config file no map
208                     // entry would be there.
209                     if ((key == "map") && (value == "passwd"))
210                     {
211                         key = key + "_" + value;
212                         if (std::getline(isLine, value, ' '))
213                         {
214                             key += "_" + value;
215                         }
216                         std::getline(isLine, value, ' ');
217                     }
218                     configValues[key] = value;
219                 }
220             }
221         }
222 
223         CreateIface::SearchScope lDAPSearchScope;
224         if (configValues["scope"] == "sub")
225         {
226             lDAPSearchScope = CreateIface::SearchScope::sub;
227         }
228         else if (configValues["scope"] == "one")
229         {
230             lDAPSearchScope = CreateIface::SearchScope::one;
231         }
232         else
233         {
234             lDAPSearchScope = CreateIface::SearchScope::base;
235         }
236 
237         CreateIface::Type lDAPType;
238         // If the file is having a line which starts with "map group"
239         if (configValues["map"] == "group")
240         {
241             lDAPType = CreateIface::Type::ActiveDirectory;
242         }
243         else
244         {
245             lDAPType = CreateIface::Type::OpenLdap;
246         }
247 
248         // Don't create the config object if either of the field is empty.
249         if (configValues["uri"] == "" || configValues["binddn"] == "" ||
250             configValues["base"] == "")
251         {
252             log<level::INFO>(
253                 "LDAP config parameter value missing",
254                 entry("URI=%s", configValues["uri"].c_str()),
255                 entry("BASEDN=%s", configValues["base"].c_str()),
256                 entry("BINDDN=%s", configValues["binddn"].c_str()));
257             return;
258         }
259 
260         createConfig(std::move(configValues["uri"]),
261                      std::move(configValues["binddn"]),
262                      std::move(configValues["base"]),
263                      std::move(configValues["bindpw"]), lDAPSearchScope,
264                      lDAPType, std::move(configValues["map_passwd_uid"]),
265                      std::move(configValues["map_passwd_gidNumber"]));
266 
267         // Get the enabled property value from the persistent location
268         if (!deserialize(dbusPersistentPath, *configPtr))
269         {
270             log<level::INFO>(
271                 "Deserialization Failed, continue with service disable");
272         }
273     }
274     catch (const InvalidArgument& e)
275     {
276         // Don't throw - we don't want to create a D-Bus
277         // object upon finding empty values in config, as
278         // this can be a default config.
279     }
280     catch (const NoCACertificate& e)
281     {
282         // Don't throw - we don't want to create a D-Bus
283         // object upon finding "ssl on" without having tls_cacertFile in place,
284         // as this can be a default config.
285     }
286     catch (const InternalFailure& e)
287     {
288         throw;
289     }
290     catch (const std::exception& e)
291     {
292         log<level::ERR>(e.what());
293         elog<InternalFailure>();
294     }
295 }
296 } // namespace ldap
297 } // namespace phosphor
298