1 // =================================
  2 // Copyright (c) 2021 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 #include <cmajor/rt/Debug.hpp>
  7 #include <sngxml/dom/Element.hpp>
  8 #include <sngxml/dom/Parser.hpp>
  9 #include <sngxml/xpath/XPathEvaluate.hpp>
 10 #include <soulng/util/Path.hpp>
 11 #include <soulng/util/TextUtils.hpp>
 12 #include <soulng/util/Sha1.hpp>
 13 #include <soulng/util/Socket.hpp>
 14 #include <soulng/util/System.hpp>
 15 #include <soulng/util/Unicode.hpp>
 16 #include <boost/lexical_cast.hpp>
 17 #include <iostream>
 18 #include <thread>
 19 #include <time.h>
 20 
 21 namespace cmajor { namespace rt {
 22 
 23 using namespace soulng::util;
 24 using namespace soulng::unicode;
 25 
 26 const std::int64_t sessionTimeoutSecs = 5 * 60;
 27 
 28 class CmdbSessionServer 
 29 {
 30 public:
 31     static void Init();
 32     static void Done();
 33     static CmdbSessionServer& Instance() { return *instance; }
 34     void Start(const std::string& skey_const std::string& rkey_int port_);
 35     void OpenSession(TcpSocket& socket);
 36     void CloseSession(TcpSocket& socket);
 37     bool IsSessionOpen() const { return sessionOpen; }
 38     void SendOutput(int fileHandleconst std::string& bytes);
 39     int64_t ReceiveInput(uint8_t* bufferint64_t bufferSize);
 40 private:
 41     static std::unique_ptr<CmdbSessionServer> instance;
 42     CmdbSessionServer();
 43     std::string skey;
 44     std::string rkey;
 45     int port;
 46     bool sessionOpen;
 47     std::string inputHexByteBuffer;
 48 };
 49 
 50 std::unique_ptr<CmdbSessionServer> CmdbSessionServer::instance;
 51 
 52 CmdbSessionServer::CmdbSessionServer() : skey()rkey()port()sessionOpen(false)
 53 {
 54 }
 55 
 56 void CmdbSessionServer::Init()
 57 {
 58     instance.reset(new CmdbSessionServer());
 59 }
 60 
 61 void CmdbSessionServer::Done()
 62 {
 63     instance.reset();
 64 }
 65 
 66 void CmdbSessionServer::Start(const std::string& skey_const std::string& rkey_int port_)
 67 {
 68     skey = skey_;
 69     rkey = rkey_;
 70     port = port_;
 71     sessionOpen = true;
 72 }
 73 
 74 void CmdbSessionServer::OpenSession(TcpSocket& socket)
 75 {
 76     sngxml::dom::Document openSessionRequest;
 77     sngxml::dom::Element* cmdbOpenMessage = new sngxml::dom::Element(U"cmdbMessage");
 78     openSessionRequest.AppendChild(std::unique_ptr<sngxml::dom::Node>(cmdbOpenMessage));
 79     cmdbOpenMessage->SetAttribute(U"kind"U"openSessionRequest");
 80     cmdbOpenMessage->SetAttribute(U"key"ToUtf32(GetSha1MessageDigest(this->skey)));
 81     sngxml::dom::SendDocument(socketopenSessionRequest);
 82     bool validOpenResponseReceived = false;
 83     std::unique_ptr<sngxml::dom::Document> openSessionResponse = sngxml::dom::ReceiveDocument(socket);
 84     if (openSessionResponse)
 85     {
 86         std::unique_ptr<sngxml::xpath::XPathObject> responseObject(sngxml::xpath::Evaluate(U"/cmdbMessage"openSessionResponse.get()));
 87         if (responseObject)
 88         {
 89             if (responseObject->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 90             {
 91                 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(responseObject.get());
 92                 if (nodeSet->Length() == 1)
 93                 {
 94                     sngxml::dom::Node* node = (*nodeSet)[0];
 95                     if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
 96                     {
 97                         sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
 98                         std::u32string kind = element->GetAttribute(U"kind");
 99                         if (kind == U"openSessionResponse")
100                         {
101                             std::string key = ToUtf8(element->GetAttribute(U"key"));
102                             if (key == GetSha1MessageDigest(this->rkey))
103                             {
104                                 validOpenResponseReceived = true;
105                             }
106                             else
107                             {
108                                 throw std::runtime_error("invalid CMDB session key");
109                             }
110                         }
111                     }
112                 }
113             }
114         }
115     }
116     if (!validOpenResponseReceived)
117     {
118         throw std::runtime_error("no valid open session response received");
119     }
120 }
121 
122 void CmdbSessionServer::CloseSession(TcpSocket& socket)
123 {
124     sngxml::dom::Document closeSessionRequest;
125     sngxml::dom::Element* cmdbCloseMessage = new sngxml::dom::Element(U"cmdbMessage");
126     closeSessionRequest.AppendChild(std::unique_ptr<sngxml::dom::Node>(cmdbCloseMessage));
127     cmdbCloseMessage->SetAttribute(U"kind"U"closeSessionRequest");
128     sngxml::dom::SendDocument(socketcloseSessionRequest);
129 }
130 
131 void CmdbSessionServer::SendOutput(int fileHandleconst std::string& bytes)
132 {
133     try
134     {
135         if (!sessionOpen)
136         {
137             throw std::runtime_error("no CMDB session open");
138         }
139         TcpSocket socket("localhost"std::to_string(port));
140         OpenSession(socket);
141         sngxml::dom::Document outputRequest;
142         sngxml::dom::Element* cmdbOutputMessage = new sngxml::dom::Element(U"cmdbMessage");
143         outputRequest.AppendChild(std::unique_ptr<sngxml::dom::Node>(cmdbOutputMessage));
144         cmdbOutputMessage->SetAttribute(U"kind"U"outputRequest");
145         cmdbOutputMessage->SetAttribute(U"handle"ToUtf32(std::to_string(fileHandle)));
146         cmdbOutputMessage->SetAttribute(U"bytes"ToUtf32(bytes));
147         sngxml::dom::SendDocument(socketoutputRequest);
148         bool outputResponseReceived = false;
149         std::unique_ptr<sngxml::dom::Document> outputResponse = sngxml::dom::ReceiveDocument(socket);
150         if (outputResponse)
151         {
152             std::unique_ptr<sngxml::xpath::XPathObject> responseObject(sngxml::xpath::Evaluate(U"/cmdbMessage"outputResponse.get()));
153             if (responseObject)
154             {
155                 if (responseObject->Type() == sngxml::xpath::XPathObjectType::nodeSet)
156                 {
157                     sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(responseObject.get());
158                     if (nodeSet->Length() == 1)
159                     {
160                         sngxml::dom::Node* node = (*nodeSet)[0];
161                         if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
162                         {
163                             sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
164                             std::u32string kind = element->GetAttribute(U"kind");
165                             if (kind == U"outputResponse")
166                             {
167                                 outputResponseReceived = true;
168                             }
169                         }
170                     }
171                 }
172             }
173             CloseSession(socket);
174             socket.Shutdown(ShutdownMode::both);
175             socket.Close();
176         }
177         if (!outputResponseReceived)
178         {
179             throw std::runtime_error("no valid output response received");
180         }
181     }
182     catch (const std::exception& ex;)
183     {
184         std::cerr << "CMDB session output request failed: " << ex.what() << std::endl;
185     }
186 }
187 
188 int64_t CmdbSessionServer::ReceiveInput(uint8_t* bufferint64_t bufferSize)
189 {
190     try
191     {
192         if (inputHexByteBuffer.empty())
193         {
194             if (!sessionOpen)
195             {
196                 throw std::runtime_error("no CMDB session open");
197             }
198             TcpSocket socket("localhost"std::to_string(port));
199             OpenSession(socket);
200             sngxml::dom::Document inputRequest;
201             sngxml::dom::Element* cmdbInputMessage = new sngxml::dom::Element(U"cmdbMessage");
202             inputRequest.AppendChild(std::unique_ptr<sngxml::dom::Node>(cmdbInputMessage));
203             cmdbInputMessage->SetAttribute(U"kind"U"inputRequest");
204             sngxml::dom::SendDocument(socketinputRequest);
205             bool inputResponseReceived = false;
206             std::unique_ptr<sngxml::dom::Document> inputResponse = sngxml::dom::ReceiveDocument(socket);
207             if (inputResponse)
208             {
209                 std::unique_ptr<sngxml::xpath::XPathObject> responseObject(sngxml::xpath::Evaluate(U"/cmdbMessage"inputResponse.get()));
210                 if (responseObject)
211                 {
212                     if (responseObject->Type() == sngxml::xpath::XPathObjectType::nodeSet)
213                     {
214                         sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(responseObject.get());
215                         if (nodeSet->Length() == 1)
216                         {
217                             sngxml::dom::Node* node = (*nodeSet)[0];
218                             if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
219                             {
220                                 sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
221                                 std::u32string kind = element->GetAttribute(U"kind");
222                                 if (kind == U"inputResponse")
223                                 {
224                                     std::u32string value = element->GetAttribute(U"bytes");
225                                     inputHexByteBuffer = ToUtf8(value);
226                                     inputResponseReceived = true;
227                                 }
228                             }
229                         }
230                     }
231                 }
232             }
233             CloseSession(socket);
234             socket.Shutdown(ShutdownMode::both);
235             socket.Close();
236             if (!inputResponseReceived)
237             {
238                 throw std::runtime_error("no valid input response received");
239             }
240         }
241         int64_t bytesReceived = 0;
242         uint8_t* p = buffer;
243         int64_t bytesToReceive = bufferSize;
244         while (inputHexByteBuffer.size() >= 2 && bytesToReceive > 0)
245         {
246             std::string hex;
247             hex.append(1inputHexByteBuffer[0]);
248             hex.append(1inputHexByteBuffer[1]);
249             inputHexByteBuffer.erase(inputHexByteBuffer.begin());
250             inputHexByteBuffer.erase(inputHexByteBuffer.begin());
251             uint8_t byte = ParseHexByte(hex);
252             *p++ = byte;
253             --bytesToReceive;
254             ++bytesReceived;
255         }
256         return bytesReceived;
257     }
258     catch (const std::exception& ex;)
259     {
260         std::cerr << "CMDB session input request failed: " << ex.what() << std::endl;
261     }
262     return 0;
263 }
264 
265 void StartCmdbSession()
266 {
267     try
268     {
269         std::string cmdbSessionFilePath;
270         std::string exePath = GetFullPath(GetPathToExecutable());
271         if (EndsWith(exePath".exe"))
272         {
273             cmdbSessionFilePath = Path::ChangeExtension(exePath".cmdbs");
274         }
275         else
276         {
277             cmdbSessionFilePath = exePath + ".cmdbs";
278         }
279         if (FileExists(cmdbSessionFilePath))
280         {
281             std::unique_ptr<sngxml::dom::Document> sessionDoc = sngxml::dom::ReadDocument(cmdbSessionFilePath);
282             std::unique_ptr<sngxml::xpath::XPathObject> timestampObject = sngxml::xpath::Evaluate(U"/cmdbSession/timestamp"sessionDoc.get());
283             if (timestampObject)
284             {
285                 if (timestampObject->Type() == sngxml::xpath::XPathObjectType::nodeSet)
286                 {
287                     sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(timestampObject.get());
288                     if (nodeSet->Length() == 1)
289                     {
290                         sngxml::dom::Node* timestampNode = (*nodeSet)[0];
291                         if (timestampNode->GetNodeType() == sngxml::dom::NodeType::elementNode)
292                         {
293                             sngxml::dom::Element* timestampElement = static_cast<sngxml::dom::Element*>(timestampNode);
294                             std::string timestampStr = ToUtf8(timestampElement->GetAttribute(U"value"));
295                             if (!timestampStr.empty())
296                             {
297                                 time_t timestamp = boost::lexical_cast<time_t>(timestampStr);
298                                 time_t now;
299                                 time(&now);
300                                 if (now - timestamp >= 0 && now - timestamp < sessionTimeoutSecs)
301                                 {
302                                     std::unique_ptr<sngxml::xpath::XPathObject> skeyObject = sngxml::xpath::Evaluate(U"/cmdbSession/skey"sessionDoc.get());
303                                     if (skeyObject)
304                                     {
305                                         if (skeyObject->Type() == sngxml::xpath::XPathObjectType::nodeSet)
306                                         {
307                                             sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(skeyObject.get());
308                                             if (nodeSet->Length() == 1)
309                                             {
310                                                 sngxml::dom::Node* keyNode = (*nodeSet)[0];
311                                                 if (keyNode->GetNodeType() == sngxml::dom::NodeType::elementNode)
312                                                 {
313                                                     sngxml::dom::Element* keyElement = static_cast<sngxml::dom::Element*>(keyNode);
314                                                     std::string skeyStr = ToUtf8(keyElement->GetAttribute(U"value"));
315                                                     if (!skeyStr.empty())
316                                                     {
317                                                         std::unique_ptr<sngxml::xpath::XPathObject> rkeyObject = sngxml::xpath::Evaluate(U"/cmdbSession/rkey"sessionDoc.get());
318                                                         if (rkeyObject)
319                                                         {
320                                                             sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(rkeyObject.get());
321                                                             if (nodeSet->Length() == 1)
322                                                             {
323                                                                 sngxml::dom::Node* keyNode = (*nodeSet)[0];
324                                                                 if (keyNode->GetNodeType() == sngxml::dom::NodeType::elementNode)
325                                                                 {
326                                                                     sngxml::dom::Element* keyElement = static_cast<sngxml::dom::Element*>(keyNode);
327                                                                     std::string rkeyStr = ToUtf8(keyElement->GetAttribute(U"value"));
328                                                                     if (!rkeyStr.empty())
329                                                                     {
330                                                                         std::unique_ptr<sngxml::xpath::XPathObject> portObject = sngxml::xpath::Evaluate(U"/cmdbSession/port"sessionDoc.get());
331                                                                         if (portObject)
332                                                                         {
333                                                                             if (portObject->Type() == sngxml::xpath::XPathObjectType::nodeSet)
334                                                                             {
335                                                                                 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(portObject.get());
336                                                                                 if (nodeSet->Length() == 1)
337                                                                                 {
338                                                                                     sngxml::dom::Node* portNode = (*nodeSet)[0];
339                                                                                     if (portNode->GetNodeType() == sngxml::dom::NodeType::elementNode)
340                                                                                     {
341                                                                                         sngxml::dom::Element* portElement = static_cast<sngxml::dom::Element*>(portNode);
342                                                                                         std::string portStr = ToUtf8(portElement->GetAttribute(U"value"));
343                                                                                         if (!portStr.empty())
344                                                                                         {
345                                                                                             int port = boost::lexical_cast<int>(portStr);
346                                                                                             CmdbSessionServer::Instance().Start(skeyStrrkeyStrport);
347                                                                                         }
348                                                                                         else
349                                                                                         {
350                                                                                             throw std::runtime_error("port is empty");
351                                                                                         }
352                                                                                     }
353                                                                                 }
354                                                                             }
355                                                                         }
356                                                                     }
357                                                                     else
358                                                                     {
359                                                                         throw std::runtime_error("key is empty");
360                                                                     }
361                                                                 }
362                                                             }
363                                                         }
364                                                     }
365                                                     else
366                                                     {
367                                                         throw std::runtime_error("key is empty");
368                                                     }
369                                                 }
370                                             }
371                                         }
372                                     }
373                                 }
374                             }
375                             else
376                             {
377                                 throw std::runtime_error("timestamp is empty");
378                             }
379                         }
380                     }
381                 }
382             }
383         }
384     }
385     catch (const std::exception& ex;)
386     {
387         std::cerr << "unable to start CMDB session: " << ex.what() << std::endl;
388     }
389 }
390 
391 bool IsCmdbSessionOpen()
392 {
393     return CmdbSessionServer::Instance().IsSessionOpen();
394 }
395 
396 void WriteBytesToCmdbSession(int fileHandleconst uint8_t* bufferint64_t count)
397 {
398     std::string output;
399     for (int64_t i = 0; i < count; ++i)
400     {
401         output.append(ToHexString(buffer[i]));
402     }
403     CmdbSessionServer::Instance().SendOutput(fileHandleoutput);
404 }
405 
406 int64_t ReadBytesFromCmdbSession(uint8_t* bufferint64_t bufferSize)
407 {
408     return CmdbSessionServer::Instance().ReceiveInput(bufferbufferSize);
409 }
410 
411 void InitCmdbSession()
412 {
413     CmdbSessionServer::Init();
414 }
415 
416 void DoneCmdbSession()
417 {
418     CmdbSessionServer::Done();
419 }
420 
421 } } // namespace cmajor::rt