irc.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. #include "irc.h"
  2. #include <boost/algorithm/string.hpp>
  3. #include <iostream>
  4. void string_toupper(std::string &str) {
  5. std::transform(str.begin(), str.end(), str.begin(), ::toupper);
  6. }
  7. /**
  8. * @brief remove channel modes (op,voice,hop,...)
  9. *
  10. * @param nick
  11. */
  12. void remove_channel_modes(std::string &nick) {
  13. // ~&@%+
  14. std::string remove("~&@%+");
  15. std::string::size_type pos;
  16. do {
  17. pos = remove.find(nick[0]);
  18. if (pos != std::string::npos)
  19. nick.erase(0, 1);
  20. } while (pos != std::string::npos);
  21. }
  22. /**
  23. * @brief split on spaces, with limit
  24. *
  25. * max is the maximum number of splits we will do.
  26. * default -1 is split all.
  27. *
  28. * "this is a test", 3 => [this][is][a test]
  29. *
  30. * @param text
  31. * @param max
  32. * @return std::vector<std::string>
  33. */
  34. std::vector<std::string> split_limit(std::string &text, int max) {
  35. std::vector<std::string> ret;
  36. int t = 0;
  37. boost::split(ret, text, [&t, max](char c) {
  38. if (c == ' ') {
  39. ++t;
  40. return ((max == -1) or (t < max));
  41. };
  42. return false;
  43. });
  44. return ret;
  45. }
  46. /**
  47. * @brief irc split
  48. *
  49. * If it doesn't start with a ':', split into two parts.
  50. * Otherwise 4 parts
  51. * [from] [command] [to] [message]
  52. *
  53. * @param text
  54. * @return std::vector<std::string>
  55. */
  56. std::vector<std::string> irc_split(std::string &text) {
  57. size_t msgpos = text.find(" :");
  58. std::string message;
  59. if (msgpos != std::string::npos) {
  60. message = text.substr(msgpos + 2);
  61. text.erase(msgpos);
  62. }
  63. std::vector<std::string> results;
  64. /*
  65. if (text[0] != ':')
  66. results = split_limit(text, 2);
  67. results = split_limit(text, 4);
  68. */
  69. results = split_limit(text, -1);
  70. if (!message.empty())
  71. results.push_back(message);
  72. return results;
  73. }
  74. /**
  75. * @brief parse_nick
  76. *
  77. * Parse out the nick from nick!username@host
  78. *
  79. * @param name
  80. * @return std::string
  81. */
  82. std::string parse_nick(std::string &name) {
  83. std::string to = name;
  84. if (to[0] == ':')
  85. to.erase(0, 1);
  86. size_t pos = to.find('!');
  87. if (pos != std::string::npos) {
  88. to.erase(pos);
  89. }
  90. return to;
  91. }
  92. // namespace io = boost::asio;
  93. // namespace ip = io::ip;
  94. // using tcp = boost::asio::ip; // ip::tcp;
  95. using error_code = boost::system::error_code;
  96. using namespace std::placeholders;
  97. // #define DEBUG_OUTPUT
  98. typedef std::function<void(std::string &)> receiveFunction;
  99. #ifdef SENDQ
  100. ircClient::ircClient(boost::asio::io_context &io_context)
  101. : resolver{io_context}, ssl_context{boost::asio::ssl::context::tls},
  102. socket{io_context, ssl_context},
  103. sendq_timer{io_context}, context{io_context} {
  104. #else
  105. ircClient::ircClient(boost::asio::io_context &io_context)
  106. : resolver{io_context}, ssl_context{boost::asio::ssl::context::tls},
  107. socket{io_context, ssl_context}, context{io_context} {
  108. #endif
  109. registered = false;
  110. nick_retry = 1;
  111. shutdown = false;
  112. logging = false;
  113. channels_updated = false;
  114. version = "Bugz IRC thing V0.1";
  115. #ifdef SENDQ
  116. sendq_current = 0;
  117. sendq_active = false;
  118. sendq_ms = 500;
  119. sendq_current = 0;
  120. #endif
  121. }
  122. std::ofstream &ircClient::log(void) {
  123. std::time_t t = std::time(nullptr);
  124. std::tm tm = *std::localtime(&t);
  125. debug_file << std::put_time(&tm, "%c ");
  126. return debug_file;
  127. }
  128. void ircClient::begin(void) {
  129. original_nick = nick;
  130. resolver.async_resolve(hostname, port,
  131. std::bind(&ircClient::on_resolve, this, _1, _2));
  132. if (!debug_output.empty()) {
  133. debug_file.open(debug_output.c_str(),
  134. std::ofstream::out | std::ofstream::app);
  135. logging = true;
  136. }
  137. }
  138. void ircClient::write(std::string output) {
  139. if (logging) {
  140. log() << "<< " << output << std::endl;
  141. }
  142. error_code error;
  143. socket.write_some(boost::asio::buffer(output + "\r\n"), error);
  144. if (error) {
  145. if (logging) {
  146. log() << "Write: " << error.message() << std::endl;
  147. }
  148. }
  149. }
  150. #ifdef SENDQ
  151. /**
  152. * @brief Add to the sendq for async slow message sending.
  153. *
  154. * target is used (we cycle through sendq_targets) so no one person can clog up
  155. * the queue.
  156. *
  157. * @param target
  158. * @param output
  159. */
  160. void ircClient::write_queue(std::string target, std::string output) {
  161. // is target in sendq_targets
  162. bool found = false;
  163. for (auto &t : sendq_targets) {
  164. if (t == target) {
  165. found = true;
  166. break;
  167. }
  168. }
  169. if (!found) {
  170. sendq_targets.push_back(target);
  171. };
  172. sendq[target].push_back(output);
  173. if (!sendq_active) {
  174. // async send is not active -- start the timer event
  175. sendq_active = true;
  176. sendq_current = 0;
  177. sendq_timer.expires_after(std::chrono::milliseconds(sendq_ms));
  178. sendq_timer.async_wait(std::bind(&ircClient::on_sendq, this, _1));
  179. }
  180. }
  181. /**
  182. * @brief async send buffer
  183. *
  184. * This sends lines out slowly to the ircd. It uses the sendq_targets to
  185. * alternate between so one person can't clog up the buffer.
  186. *
  187. * TODO: Add something that checks to see how many lines we have sent.
  188. * After 20 lines, increase the sendq_ms (maybe 2*sendq_ms?) After 50, maybe
  189. * 3*sendq_ms. We want to throttle ourselves and not have the ircd doing it.
  190. *
  191. * Currently, this is not thread-safe. It's being used by code that isn't
  192. * running the io_context in another thread.
  193. * @param error
  194. */
  195. void ircClient::on_sendq(error_code error) {
  196. std::string &target = sendq_targets[sendq_current];
  197. // calculate the next target
  198. ++sendq_current;
  199. if (sendq_current >= (int)sendq_targets.size())
  200. sendq_current = 0;
  201. std::string output = sendq[target].front();
  202. sendq[target].erase(sendq[target].begin());
  203. write(output);
  204. if (sendq[target].size() == 0) {
  205. // target queue is empty
  206. sendq.erase(target);
  207. // remove target from sendq_targets
  208. for (auto pos = sendq_targets.begin(); pos != sendq_targets.end(); ++pos) {
  209. // for (int x = 0; x < (int)sendq_targets.size(); ++x) {
  210. if (*pos == target) {
  211. sendq_targets.erase(pos);
  212. break;
  213. }
  214. }
  215. // verify the sendq_current is still valid
  216. if (sendq_current >= (int)sendq_targets.size())
  217. sendq_current = 0;
  218. }
  219. if (!sendq_targets.empty()) {
  220. // more to do, let's do it again!
  221. sendq_timer.expires_after(std::chrono::milliseconds(sendq_ms));
  222. sendq_timer.async_wait(std::bind(&ircClient::on_sendq, this, _1));
  223. } else {
  224. // let write_queue know we aren't running anymore.
  225. sendq_active = false;
  226. }
  227. }
  228. #endif
  229. /**
  230. * @brief thread safe messages.push_back
  231. *
  232. * @param msg
  233. */
  234. void ircClient::message_append(message_stamp &msg) {
  235. lock.lock();
  236. messages.push_back(msg);
  237. channels_updated = true;
  238. lock.unlock();
  239. }
  240. /**
  241. * @brief thread safe message_stamp pop
  242. *
  243. * @return boost::optional<message_stamp>
  244. */
  245. boost::optional<message_stamp> ircClient::message_pop(void) {
  246. lock.lock();
  247. message_stamp msg;
  248. if (messages.empty()) {
  249. channels_updated = false;
  250. lock.unlock();
  251. return boost::optional<message_stamp>{};
  252. }
  253. msg = messages.front();
  254. messages.erase(messages.begin());
  255. lock.unlock();
  256. return msg;
  257. }
  258. void ircClient::on_resolve(
  259. error_code error, boost::asio::ip::tcp::resolver::results_type results) {
  260. if (logging) {
  261. log() << "Resolve: " << error.message() << std::endl;
  262. }
  263. if (error) {
  264. std::string output = "Unable to resolve (DNS Issue?): " + error.message();
  265. errors.push_back(output);
  266. message(output);
  267. socket.async_shutdown(std::bind(&ircClient::on_shutdown, this, _1));
  268. }
  269. boost::asio::async_connect(socket.next_layer(), results,
  270. std::bind(&ircClient::on_connect, this, _1, _2));
  271. }
  272. void ircClient::on_connect(error_code error,
  273. boost::asio::ip::tcp::endpoint const &endpoint) {
  274. if (logging) {
  275. log() << "Connect: " << error.message() << ", endpoint: " << endpoint
  276. << std::endl;
  277. }
  278. if (error) {
  279. std::string output = "Unable to connect: " + error.message();
  280. message(output);
  281. errors.push_back(output);
  282. socket.async_shutdown(std::bind(&ircClient::on_shutdown, this, _1));
  283. }
  284. socket.async_handshake(boost::asio::ssl::stream_base::client,
  285. std::bind(&ircClient::on_handshake, this, _1));
  286. }
  287. void ircClient::on_handshake(error_code error) {
  288. if (logging) {
  289. log() << "Handshake: " << error.message() << std::endl;
  290. }
  291. if (error) {
  292. std::string output = "Handshake Failure: " + error.message();
  293. message(output);
  294. errors.push_back(output);
  295. socket.async_shutdown(std::bind(&ircClient::on_shutdown, this, _1));
  296. }
  297. write(registration());
  298. /*
  299. std::string request = registration();
  300. boost::asio::async_write(socket, boost::asio::buffer(request),
  301. std::bind(&ircClient::on_write, this, _1, _2));
  302. // socket.async_shutdown(std::bind(&ircClient::on_shutdown, this, _1));
  303. }
  304. void ircClient::on_write(error_code error, std::size_t bytes_transferred) {
  305. if ((error) and (logging)) {
  306. log() << "Write: " << error.message() << std::endl;
  307. }
  308. */
  309. // << ", bytes transferred: " << bytes_transferred << "\n";
  310. boost::asio::async_read_until(
  311. socket, response, '\n', std::bind(&ircClient::read_until, this, _1, _2));
  312. }
  313. void ircClient::on_shutdown(error_code error) {
  314. if (logging) {
  315. log() << "SHUTDOWN: " << error.message() << std::endl;
  316. }
  317. shutdown = true;
  318. context.stop();
  319. }
  320. void ircClient::read_until(error_code error, std::size_t bytes) {
  321. // std::cout << "Read: " << bytes << ", " << error << "\n";
  322. // auto data = response.data();
  323. if (bytes == 0) {
  324. if (logging) {
  325. log() << "Read 0 bytes, shutdown..." << std::endl;
  326. }
  327. socket.async_shutdown(std::bind(&ircClient::on_shutdown, this, _1));
  328. return;
  329. };
  330. // Only try to get the data -- if we're read some bytes.
  331. auto data = response.data();
  332. response.consume(bytes);
  333. std::string text{(const char *)data.data(), bytes};
  334. while ((text[text.size() - 1] == '\r') or (text[text.size() - 1] == '\n'))
  335. text.erase(text.size() - 1);
  336. receive(text);
  337. // repeat until closed
  338. boost::asio::async_read_until(
  339. socket, response, '\n', std::bind(&ircClient::read_until, this, _1, _2));
  340. }
  341. /**
  342. * @brief Append a system message to the messages.
  343. *
  344. * @param msg
  345. */
  346. void ircClient::message(std::string msg) {
  347. message_stamp ms;
  348. ms.buffer.push_back(msg);
  349. message_append(ms);
  350. }
  351. void ircClient::receive(std::string &text) {
  352. message_stamp ms;
  353. ms.buffer = irc_split(text);
  354. std::vector<std::string> &parts = ms.buffer;
  355. if (logging) {
  356. // this also shows our parser working
  357. std::ofstream &l = log();
  358. l << ">> ";
  359. for (auto &s : parts) {
  360. l << "[" << s << "] ";
  361. }
  362. l << std::endl;
  363. }
  364. // INTERNAL IRC PARSING/TRACKING
  365. if (parts.size() == 2) {
  366. // hide PING / PONG messages
  367. if (parts[0] == "PING") {
  368. std::string output = "PONG " + parts[1];
  369. write(output);
  370. return;
  371. }
  372. }
  373. if (parts.size() >= 3) {
  374. std::string source = parse_nick(parts[0]);
  375. std::string &cmd = parts[1];
  376. std::string &msg_to = parts[2];
  377. std::string msg;
  378. if (parts.size() >= 4) {
  379. msg = parts[parts.size() - 1];
  380. }
  381. if (logging) {
  382. // this also shows our parser working
  383. std::ofstream &l = log();
  384. l << "IRC: [SRC:" << source << "] [CMD:" << cmd << "] [TO:" << msg_to
  385. << "] [MSG:" << msg << "]" << std::endl;
  386. }
  387. if (cmd == "JOIN") {
  388. channels_lock.lock();
  389. if (nick == source) {
  390. // yes, we are joining
  391. std::string output = "You have joined " + msg_to;
  392. message(output);
  393. talkto(msg_to);
  394. // insert empty set here.
  395. std::set<std::string> empty;
  396. channels[msg_to] = empty;
  397. } else {
  398. // Someone else is joining
  399. std::string output = source + " has joined " += msg_to;
  400. message(output);
  401. channels[msg_to].insert(source);
  402. if ((int)source.size() > max_nick_length)
  403. max_nick_length = (int)source.size();
  404. }
  405. channels_lock.unlock();
  406. }
  407. if (cmd == "PART") {
  408. channels_lock.lock();
  409. if (nick == source) {
  410. std::string output = "You left " + msg_to;
  411. auto ch = channels.find(msg_to);
  412. if (ch != channels.end())
  413. channels.erase(ch);
  414. if (!channels.empty()) {
  415. talkto(channels.begin()->first);
  416. // output += " [talkto = " + talkto() + "]";
  417. } else {
  418. talkto("");
  419. }
  420. // message(output);
  421. } else {
  422. std::string output = source + " has left " + msg_to;
  423. if (!msg.empty()) {
  424. output += " " + msg;
  425. }
  426. message(output);
  427. channels[msg_to].erase(source);
  428. }
  429. find_max_nick_length();
  430. channels_lock.unlock();
  431. }
  432. if (cmd == "KICK") {
  433. std::string output =
  434. source + " has kicked " + parts[3] + " from " + msg_to;
  435. channels_lock.lock();
  436. if (parts[3] == nick) {
  437. channels.erase(msg_to);
  438. if (!channels.empty()) {
  439. talkto(channels.begin()->first);
  440. output += " [talkto = " + talkto() + "]";
  441. } else {
  442. talkto("");
  443. }
  444. } else {
  445. channels[msg_to].erase(parts[3]);
  446. }
  447. find_max_nick_length();
  448. channels_lock.unlock();
  449. message(output);
  450. }
  451. if (cmd == "QUIT") {
  452. std::string output = "* " + source + " has quit ";
  453. message(output);
  454. channels_lock.lock();
  455. if (source == nick) {
  456. // We've quit?
  457. channels.erase(channels.begin(), channels.end());
  458. } else {
  459. for (auto &c : channels) {
  460. c.second.erase(source);
  461. // would it be possible that channel is empty now?
  462. // no, because we're still in it.
  463. }
  464. find_max_nick_length();
  465. }
  466. channels_lock.unlock();
  467. }
  468. if (cmd == "353") {
  469. // NAMES list for channel
  470. std::vector<std::string> names_list = split_limit(msg);
  471. std::string channel = parts[4];
  472. channels_lock.lock();
  473. if (channels.find(channel) == channels.end()) {
  474. // does not exist
  475. channels.insert({channel, std::set<std::string>{}});
  476. }
  477. for (auto name : names_list) {
  478. remove_channel_modes(name);
  479. channels[channel].insert(name);
  480. }
  481. find_max_nick_length();
  482. channels_lock.unlock();
  483. }
  484. if (cmd == "NICK") {
  485. // msg_to.erase(0, 1);
  486. channels_lock.lock();
  487. for (auto &ch : channels) {
  488. if (ch.second.erase(source) == 1) {
  489. ch.second.insert(msg_to);
  490. }
  491. }
  492. // Is this us? If so, change our nick.
  493. if (source == nick)
  494. nick = msg_to;
  495. find_max_nick_length();
  496. channels_lock.unlock();
  497. }
  498. if (cmd == "PRIVMSG") {
  499. // Possibly a CTCP request. Let's see
  500. std::string message = msg;
  501. if ((message[0] == '\x01') and (message[message.size() - 1] == '\x01')) {
  502. // CTCP handler
  503. // NOTE: When sent to a channel, the response is sent to the sender.
  504. // CTCP MESSAGE FOUND strip \x01's
  505. message.erase(0, 1);
  506. message.erase(message.size() - 1);
  507. std::vector<std::string> ctcp_cmd = split_limit(message, 2);
  508. if (ctcp_cmd[0] != "ACTION") {
  509. std::string msg =
  510. "Received CTCP " + ctcp_cmd[0] + " from " + parse_nick(source);
  511. this->message(msg);
  512. if (logging) {
  513. log() << "CTCP : [" << message << "] from " + parse_nick(source)
  514. << std::endl;
  515. }
  516. }
  517. if (message == "VERSION") {
  518. std::string reply_to = parse_nick(source);
  519. boost::format fmt = boost::format("NOTICE %1% :\x01VERSION %2%\x01") %
  520. reply_to % version;
  521. std::string response = fmt.str();
  522. write(response);
  523. return;
  524. }
  525. if (message.substr(0, 5) == "PING ") {
  526. message.erase(0, 5);
  527. boost::format fmt = boost::format("NOTICE %1% :\x01PING %2%\x01") %
  528. parse_nick(source) % message;
  529. std::string response = fmt.str();
  530. write(response);
  531. return;
  532. }
  533. if (message == "TIME") {
  534. auto now = std::chrono::system_clock::now();
  535. auto in_time_t = std::chrono::system_clock::to_time_t(now);
  536. std::string datetime = boost::lexical_cast<std::string>(
  537. std::put_time(std::localtime(&in_time_t), "%c"));
  538. boost::format fmt = boost::format("NOTICE %1% :\x01TIME %2%\x01") %
  539. parse_nick(source) % datetime;
  540. std::string response = fmt.str();
  541. write(response);
  542. return;
  543. }
  544. if (message.substr(0, 7) == "ACTION ") {
  545. message.erase(0, 7);
  546. parts[1] = "ACTION"; // change PRIVMSG to ACTION
  547. parts[3] = message;
  548. } else {
  549. // What should I do with unknown CTCP commands?
  550. // Unknown CTCP command. Eat it.
  551. return;
  552. }
  553. }
  554. }
  555. }
  556. if (!registered) {
  557. // We're not registered yet
  558. if (parts[1] == "433") {
  559. // nick collision! Nick already in use
  560. if (nick == original_nick) {
  561. // try something basic
  562. nick += "_";
  563. std::string output = "NICK " + nick;
  564. write(output);
  565. return;
  566. } else {
  567. // Ok, go advanced
  568. nick = original_nick + "_" + std::to_string(nick_retry);
  569. ++nick_retry;
  570. std::string output = "NICK " + nick;
  571. write(output);
  572. return;
  573. }
  574. }
  575. // SASL Authentication
  576. if ((parts[1] == "CAP") and (parts[3] == "ACK")) {
  577. write("AUTHENTICATE PLAIN");
  578. }
  579. if ((parts[0] == "AUTHENTICATE") and (parts[1] == "+")) {
  580. std::string userpass;
  581. userpass.append(1, 0);
  582. userpass.append(nick);
  583. userpass.append(1, 0);
  584. userpass.append(sasl_plain_password);
  585. std::string asbase64 = base64encode(userpass);
  586. std::string auth = "AUTHENTICATE " + asbase64;
  587. write(auth);
  588. }
  589. if (parts[1] == "903") {
  590. // success SASL
  591. write("CAP END");
  592. }
  593. if (parts[1] == "904") {
  594. // SASL failed
  595. write("CAP END");
  596. // Should we close the connection if we can't authenticate?
  597. }
  598. if ((parts[1] == "376") or (parts[1] == "422")) {
  599. // END MOTD, or MOTD MISSING
  600. find_max_nick_length(); // start with ourself.
  601. registered = true;
  602. if (!autojoin.empty()) {
  603. std::string msg = "JOIN " + autojoin;
  604. write(msg);
  605. }
  606. }
  607. }
  608. message_append(ms);
  609. }
  610. /**
  611. * @brief find max nick length
  612. *
  613. * This is for formatting the messages.
  614. * We run through the channels checking all the users,
  615. * and also checking our own nick.
  616. *
  617. * This updates \ref max_nick_length
  618. */
  619. void ircClient::find_max_nick_length(void) {
  620. int max = 0;
  621. for (auto const &ch : channels) {
  622. for (auto const &nick : ch.second) {
  623. if ((int)nick.size() > max)
  624. max = (int)nick.size();
  625. }
  626. }
  627. // check our nick against this too.
  628. if ((int)nick.size() > max)
  629. max = (int)nick.size();
  630. max_nick_length = max;
  631. }
  632. std::string ircClient::registration(void) {
  633. std::string text;
  634. // Initiate SASL authentication
  635. if (!sasl_plain_password.empty()) {
  636. text = "CAP REQ :sasl\r\n";
  637. }
  638. if (!server_password.empty()) {
  639. text += "PASS " + server_password + "\r\n";
  640. }
  641. text += "NICK " + nick + "\r\n" + "USER " + username + " 0 * :" + realname +
  642. "\r\n";
  643. return text;
  644. }
  645. #include <boost/archive/iterators/base64_from_binary.hpp>
  646. #include <boost/archive/iterators/transform_width.hpp>
  647. typedef boost::archive::iterators::base64_from_binary<
  648. boost::archive::iterators::transform_width<std::string::const_iterator, 6,
  649. 8>>
  650. it_base64_t;
  651. std::string base64encode(const std::string &str) {
  652. unsigned int writePaddChars = (3 - str.length() % 3) % 3;
  653. std::string base64(it_base64_t(str.begin()), it_base64_t(str.end()));
  654. base64.append(writePaddChars, '=');
  655. return base64;
  656. }