/ daemon / I2PControl.cpp
I2PControl.cpp
  1  /*
  2  * Copyright (c) 2013-2025, The PurpleI2P Project
  3  *
  4  * This file is part of Purple i2pd project and licensed under BSD3
  5  *
  6  * See full license text in LICENSE file at top of project tree
  7  */
  8  
  9  #include <stdio.h>
 10  #include <sstream>
 11  #include <iomanip>
 12  #include <openssl/x509.h>
 13  #include <openssl/pem.h>
 14  
 15  // Use global placeholders from boost introduced when local_time.hpp is loaded
 16  #define BOOST_BIND_GLOBAL_PLACEHOLDERS
 17  #include <boost/property_tree/json_parser.hpp>
 18  #include <boost/algorithm/string.hpp>
 19  
 20  #include "FS.h"
 21  #include "Log.h"
 22  #include "Config.h"
 23  #include "NetDb.hpp"
 24  #include "Tunnel.h"
 25  #include "Daemon.h"
 26  #include "I2PControl.h"
 27  
 28  namespace i2p
 29  {
 30  namespace client
 31  {
 32  	I2PControlService::I2PControlService (const std::string& address, int port):
 33  		m_IsRunning (false),	
 34  		m_SSLContext (boost::asio::ssl::context::sslv23),
 35  		m_ShutdownTimer (m_Service)
 36  	{
 37  		if (port)		
 38  			m_Acceptor = std::make_unique<boost::asio::ip::tcp::acceptor>(m_Service,
 39  				boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port));
 40  		else
 41  #if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
 42  		{		
 43  			std::remove (address.c_str ());	// just in case
 44  			m_LocalAcceptor = std::make_unique<boost::asio::local::stream_protocol::acceptor>(m_Service,
 45  				boost::asio::local::stream_protocol::endpoint(address));	
 46  		}	
 47  #else
 48  			LogPrint(eLogError, "I2PControl: Local sockets are not supported");
 49  #endif				
 50  				
 51  		i2p::config::GetOption("i2pcontrol.password", m_Password);
 52  
 53  		// certificate / keys
 54  		std::string i2pcp_crt; i2p::config::GetOption("i2pcontrol.cert", i2pcp_crt);
 55  		std::string i2pcp_key; i2p::config::GetOption("i2pcontrol.key",  i2pcp_key);
 56  
 57  		if (i2pcp_crt.at(0) != '/')
 58  			i2pcp_crt = i2p::fs::DataDirPath(i2pcp_crt);
 59  		if (i2pcp_key.at(0) != '/')
 60  			i2pcp_key = i2p::fs::DataDirPath(i2pcp_key);
 61  		if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key)) 
 62  		{
 63  			LogPrint (eLogInfo, "I2PControl: Creating new certificate for control connection");
 64  			CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str());
 65  		} 
 66  		else 
 67  			LogPrint(eLogDebug, "I2PControl: Using cert from ", i2pcp_crt);
 68  		m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use);
 69  		boost::system::error_code ec;
 70  		m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem, ec);
 71  		if (!ec)		
 72  			m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem, ec);
 73  		if (ec)
 74  		{
 75  			LogPrint (eLogInfo, "I2PControl: Failed to load ceritifcate: ", ec.message (), ". Recreating");
 76  			CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str());
 77  			m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem, ec);
 78  			if (!ec)		
 79  				m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem, ec);
 80  			if (ec) 
 81  				// give up
 82  				LogPrint (eLogError, "I2PControl: Can't load certificates");
 83  		}	
 84  
 85  		// handlers
 86  		m_MethodHandlers["Authenticate"]       = &I2PControlService::AuthenticateHandler;
 87  		m_MethodHandlers["Echo"]               = &I2PControlService::EchoHandler;
 88  		m_MethodHandlers["I2PControl"]         = &I2PControlService::I2PControlHandler;
 89  		m_MethodHandlers["RouterInfo"]         = &I2PControlHandlers::RouterInfoHandler;
 90  		m_MethodHandlers["RouterManager"]      = &I2PControlService::RouterManagerHandler;
 91  		m_MethodHandlers["NetworkSetting"]     = &I2PControlHandlers::NetworkSettingHandler;
 92  		m_MethodHandlers["ClientServicesInfo"] = &I2PControlHandlers::ClientServicesInfoHandler;
 93  
 94  		// I2PControl
 95  		m_I2PControlHandlers["i2pcontrol.password"] = &I2PControlService::PasswordHandler;
 96  
 97  		// RouterManager
 98  		m_RouterManagerHandlers["Reseed"]           = &I2PControlService::ReseedHandler;
 99  		m_RouterManagerHandlers["Shutdown"]         = &I2PControlService::ShutdownHandler;
100  		m_RouterManagerHandlers["ShutdownGraceful"] = &I2PControlService::ShutdownGracefulHandler;
101  	}
102  
103  	I2PControlService::~I2PControlService ()
104  	{
105  		Stop ();
106  	}
107  
108  	void I2PControlService::Start ()
109  	{
110  		if (!m_IsRunning)
111  		{
112  			Accept ();
113  			m_IsRunning = true;
114  			m_Thread = std::make_unique<std::thread>(std::bind (&I2PControlService::Run, this));
115  		}
116  	}
117  
118  	void I2PControlService::Stop ()
119  	{
120  		if (m_IsRunning)
121  		{
122  			m_IsRunning = false;
123  			if (m_Acceptor) m_Acceptor->cancel ();
124  #if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
125  			if (m_LocalAcceptor) 
126  			{
127  				auto path = m_LocalAcceptor->local_endpoint().path();
128  				m_LocalAcceptor->cancel ();
129  				std::remove (path.c_str ());
130  			}	
131  #endif			
132  			m_Service.stop ();
133  			if (m_Thread)
134  			{
135  				m_Thread->join ();
136  				m_Thread = nullptr;
137  			}
138  		}
139  	}
140  
141  	void I2PControlService::Run ()
142  	{
143  		i2p::util::SetThreadName("I2PC");
144  
145  		while (m_IsRunning)
146  		{
147  			try {
148  				m_Service.run ();
149  			} catch (std::exception& ex) {
150  				LogPrint (eLogError, "I2PControl: Runtime exception: ", ex.what ());
151  			}
152  		}
153  	}
154  
155  	void I2PControlService::Accept ()
156  	{
157  		if (m_Acceptor)
158  		{	
159  			auto newSocket = std::make_shared<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> > (m_Service, m_SSLContext);
160  			m_Acceptor->async_accept (newSocket->lowest_layer(),
161  				[this, newSocket](const boost::system::error_code& ecode)
162  				{
163  					HandleAccepted (ecode, newSocket);
164  				});		
165  		}	
166  #if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
167  		else if (m_LocalAcceptor)
168  		{
169  			auto newSocket = std::make_shared<boost::asio::ssl::stream<boost::asio::local::stream_protocol::socket> > (m_Service, m_SSLContext);
170  			m_LocalAcceptor->async_accept (newSocket->lowest_layer(),
171  				[this, newSocket](const boost::system::error_code& ecode)
172  				{
173  					HandleAccepted (ecode, newSocket);
174  				});		
175  		}	
176  #endif		
177  	}
178  
179  	template<typename ssl_socket> 
180  	void I2PControlService::HandleAccepted (const boost::system::error_code& ecode,
181  		std::shared_ptr<ssl_socket> newSocket)
182  	{
183  		if (ecode != boost::asio::error::operation_aborted)
184  			Accept ();
185  
186  		if (ecode) 
187  		{
188  			LogPrint (eLogError, "I2PControl: Accept error: ", ecode.message ());
189  			return;
190  		}
191  		LogPrint (eLogDebug, "I2PControl: New request from ", newSocket->lowest_layer ().remote_endpoint ());
192  		Handshake (newSocket);
193  	}	
194  		
195  	template<typename ssl_socket>
196  	void I2PControlService::Handshake (std::shared_ptr<ssl_socket> socket)
197  	{
198  		socket->async_handshake(boost::asio::ssl::stream_base::server,
199  			[this, socket](const boost::system::error_code& ecode)
200  		    {
201  				if (ecode) 
202  				{
203  					LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ());
204  					return;
205  				}
206  				ReadRequest (socket);
207  			});			                        
208  	}
209  
210  	template<typename ssl_socket>
211  	void I2PControlService::ReadRequest (std::shared_ptr<ssl_socket> socket)
212  	{
213  		auto request = std::make_shared<I2PControlBuffer>();
214  		socket->async_read_some (
215  #if defined(BOOST_ASIO_HAS_STD_ARRAY)
216  			boost::asio::buffer (*request),
217  #else
218  			boost::asio::buffer (request->data (), request->size ()),
219  #endif
220  		    [this, socket, request](const boost::system::error_code& ecode, size_t bytes_transferred)
221  		    {
222  				HandleRequestReceived (ecode, bytes_transferred, socket, request);
223  			});
224  	}
225  
226  	template<typename ssl_socket>
227  	void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode,
228  		size_t bytes_transferred, std::shared_ptr<ssl_socket> socket,
229  		std::shared_ptr<I2PControlBuffer> buf)
230  	{
231  		if (ecode)
232  		{
233  			LogPrint (eLogError, "I2PControl: Read error: ", ecode.message ());
234  			return;
235  		}
236  		else
237  		{
238  			bool isHtml = !memcmp (buf->data (), "POST", 4);
239  			try
240  			{
241  				std::stringstream ss;
242  				ss.write (buf->data (), bytes_transferred);
243  				if (isHtml)
244  				{
245  					std::string header;
246  					size_t contentLength = 0;
247  					while (!ss.eof () && header != "\r")
248  					{
249  						std::getline(ss, header);
250  						auto colon = header.find (':');
251  						if (colon != std::string::npos && boost::iequals (header.substr (0, colon), "Content-Length"))
252  							contentLength = std::stoi (header.substr (colon + 1));
253  					}
254  					if (ss.eof ())
255  					{
256  						LogPrint (eLogError, "I2PControl: Malformed request, HTTP header expected");
257  						return; // TODO:
258  					}
259  					std::streamoff rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read
260  					if (rem > 0)
261  					{
262  						bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), rem));
263  						ss.write (buf->data (), bytes_transferred);
264  					}
265  				}
266  				std::ostringstream response;
267  				boost::property_tree::ptree pt;
268  				boost::property_tree::read_json (ss, pt);
269  
270  				std::string id     = pt.get<std::string>("id");
271  				std::string method = pt.get<std::string>("method");
272  				auto it = m_MethodHandlers.find (method);
273  				if (it != m_MethodHandlers.end ())
274  				{
275  					response << "{\"id\":" << id << ",\"result\":{";
276  					(this->*(it->second))(pt.get_child ("params"), response);
277  					response << "},\"jsonrpc\":\"2.0\"}\n";
278  				}
279  				else
280  				{
281  					LogPrint (eLogWarning, "I2PControl: Unknown method ", method);
282  					response << "{\"id\":null,\"error\":";
283  					response << "{\"code\":-32601,\"message\":\"Method not found\"},";
284  					response << "\"jsonrpc\":\"2.0\"}\n";
285  				}
286  				SendResponse (socket, buf, response, isHtml);
287  			}
288  			catch (std::exception& ex)
289  			{
290  				LogPrint (eLogError, "I2PControl: Exception when handle request: ", ex.what ());
291  				std::ostringstream response;
292  				response << "{\"id\":null,\"error\":";
293  				response << "{\"code\":-32700,\"message\":\"" << ex.what () << "\"},";
294  				response << "\"jsonrpc\":\"2.0\"}\n";
295  				SendResponse (socket, buf, response, isHtml);
296  			}
297  			catch (...)
298  			{
299  				LogPrint (eLogError, "I2PControl: Handle request unknown exception");
300  			}
301  		}
302  	}
303  
304  	template<typename ssl_socket>
305  	void I2PControlService::SendResponse (std::shared_ptr<ssl_socket> socket,
306  		std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml)
307  	{
308  		size_t len = response.str ().length (), offset = 0;
309  		if (isHtml)
310  		{
311  			std::ostringstream header;
312  			header << "HTTP/1.1 200 OK\r\n";
313  			header << "Connection: close\r\n";
314  			header << "Content-Length: " << std::to_string(len) << "\r\n";
315  			header << "Content-Type: application/json\r\n";
316  			header << "Date: ";
317  			std::time_t t = std::time (nullptr);
318      		std::tm tm = *std::gmtime (&t);
319  			header << std::put_time(&tm, "%a, %d %b %Y %T GMT") << "\r\n";
320  			header << "\r\n";
321  			offset = header.str ().size ();
322  			memcpy (buf->data (), header.str ().c_str (), offset);
323  		}
324  		memcpy (buf->data () + offset, response.str ().c_str (), len);
325  		boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len),
326  			boost::asio::transfer_all (),
327  		    [socket, buf](const boost::system::error_code& ecode, std::size_t bytes_transferred)
328  		    {
329  				if (ecode)
330  					LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ());
331  			});	
332  	}
333  
334  // handlers
335  
336  	void I2PControlService::AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
337  	{
338  		int api       = params.get<int> ("API");
339  		auto password = params.get<std::string> ("Password");
340  		LogPrint (eLogDebug, "I2PControl: Authenticate API=", api, " Password=", password);
341  		if (password != m_Password) {
342  			LogPrint (eLogError, "I2PControl: Authenticate - Invalid password: ", password);
343  			return;
344  		}
345  		InsertParam (results, "API", api);
346  		results << ",";
347  		std::string token = boost::lexical_cast<std::string>(i2p::util::GetSecondsSinceEpoch ());
348  		m_Tokens.insert (token);
349  		InsertParam (results, "Token", token);
350  	}
351  
352  	void I2PControlService::EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
353  	{
354  		auto echo = params.get<std::string> ("Echo");
355  		LogPrint (eLogDebug, "I2PControl Echo Echo=", echo);
356  		InsertParam (results, "Result", echo);
357  	}
358  
359  
360  // I2PControl
361  
362  	void I2PControlService::I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
363  	{
364  		for (auto& it: params)
365  		{
366  			LogPrint (eLogDebug, "I2PControl: I2PControl request: ", it.first);
367  			auto it1 = m_I2PControlHandlers.find (it.first);
368  			if (it1 != m_I2PControlHandlers.end ())
369  			{
370  				(this->*(it1->second))(it.second.data ());
371  				InsertParam (results, it.first, "");
372  			}
373  			else
374  				LogPrint (eLogError, "I2PControl: I2PControl unknown request: ", it.first);
375  		}
376  	}
377  
378  	void I2PControlService::PasswordHandler (const std::string& value)
379  	{
380  		LogPrint (eLogWarning, "I2PControl: New password=", value, ", to make it persistent you should update your config!");
381  		m_Password = value;
382  		m_Tokens.clear ();
383  	}
384  
385  
386  // RouterManager
387  
388  	void I2PControlService::RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
389  	{
390  		for (auto it = params.begin (); it != params.end (); it++)
391  		{
392  			LogPrint (eLogDebug, "I2PControl: RouterManager request: ", it->first);
393  			auto it1 = m_RouterManagerHandlers.find (it->first);
394  			if (it1 != m_RouterManagerHandlers.end ()) 
395  			{
396  				if (it != params.begin ()) results << ",";
397  				(this->*(it1->second))(results);
398  			} else
399  				LogPrint (eLogError, "I2PControl: RouterManager unknown request: ", it->first);
400  		}
401  	}
402  
403  
404  	void I2PControlService::ShutdownHandler (std::ostringstream& results)
405  	{
406  		LogPrint (eLogInfo, "I2PControl: Shutdown requested");
407  		InsertParam (results, "Shutdown", "");
408  		m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent
409  		m_ShutdownTimer.async_wait (
410  			[](const boost::system::error_code& ecode)
411  			{
412  				Daemon.running = 0;
413  			});
414  	}
415  
416  	void I2PControlService::ShutdownGracefulHandler (std::ostringstream& results)
417  	{
418  		i2p::context.SetAcceptsTunnels (false);
419  		int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout ();
420  		LogPrint (eLogInfo, "I2PControl: Graceful shutdown requested, ", timeout, " seconds remains");
421  		InsertParam (results, "ShutdownGraceful", "");
422  		m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second
423  		m_ShutdownTimer.async_wait (
424  			[](const boost::system::error_code& ecode)
425  			{
426  				Daemon.running = 0;
427  			});
428  	}
429  
430  	void I2PControlService::ReseedHandler (std::ostringstream& results)
431  	{
432  		LogPrint (eLogInfo, "I2PControl: Reseed requested");
433  		InsertParam (results, "Reseed", "");
434  		i2p::data::netdb.Reseed ();
435  	}
436  
437  	// certificate
438  	void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path)
439  	{
440  		FILE *f = NULL;
441  #if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0
442  		EVP_PKEY *  pkey = EVP_RSA_gen(4096); // e = 65537
443  #else		
444  		EVP_PKEY * pkey = EVP_PKEY_new ();
445  		RSA * rsa = RSA_new ();
446  		BIGNUM * e = BN_dup (i2p::crypto::GetRSAE ());
447  		RSA_generate_key_ex (rsa, 4096, e, NULL);
448  		BN_free (e);	
449  		if (rsa) EVP_PKEY_assign_RSA (pkey, rsa);
450  		else
451  		{	
452  			LogPrint (eLogError, "I2PControl: Can't create RSA key for certificate");
453  			EVP_PKEY_free (pkey);
454  			return;
455  		}	
456  #endif			
457  		X509 * x509 = X509_new ();
458  		ASN1_INTEGER_set (X509_get_serialNumber (x509), 1);
459  		X509_gmtime_adj (X509_getm_notBefore (x509), 0);
460  		X509_gmtime_adj (X509_getm_notAfter (x509), I2P_CONTROL_CERTIFICATE_VALIDITY*24*60*60); // expiration
461  		X509_set_pubkey (x509, pkey); // public key
462  		X509_NAME * name = X509_get_subject_name (x509);
463  		X509_NAME_add_entry_by_txt (name, "C",  MBSTRING_ASC, (unsigned char *)"A1", -1, -1, 0); // country (Anonymous proxy)
464  		X509_NAME_add_entry_by_txt (name, "O",  MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization
465  		X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name
466  		X509_set_issuer_name (x509, name); // set issuer to ourselves
467  		X509_sign (x509, pkey, EVP_sha1 ()); // sign, last param must be NULL for EdDSA
468  
469  		// save cert
470  		if ((f = fopen (crt_path, "wb")) != NULL) 
471  		{
472  			LogPrint (eLogInfo, "I2PControl: Saving new cert to ", crt_path);
473  			PEM_write_X509 (f, x509);
474  			fclose (f);
475  		} 
476  		else
477  			LogPrint (eLogError, "I2PControl: Can't write cert: ", strerror(errno));
478  		X509_free (x509);
479  		
480  		// save key
481  		if ((f = fopen (key_path, "wb")) != NULL) 
482  		{
483  			LogPrint (eLogInfo, "I2PControl: saving cert key to ", key_path);
484  			PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL);
485  			fclose (f);
486  		}
487  		else
488  			LogPrint (eLogError, "I2PControl: Can't write key: ", strerror(errno));
489  		EVP_PKEY_free (pkey);
490  	}
491  }
492  }