1 #include <utils/timed_events.hpp>
2 #include <utils/scopeguard.hpp>
3 #include <utils/tolower.hpp>
4 #include <logger/logger.hpp>
5 #include <utils/uuid.hpp>
6 
7 #include <xmpp/xmpp_component.hpp>
8 #include <config/config.hpp>
9 #include <utils/system.hpp>
10 #include <utils/time.hpp>
11 #include <xmpp/auth.hpp>
12 #include <xmpp/jid.hpp>
13 
14 #include <stdexcept>
15 #include <iostream>
16 #include <set>
17 
18 #include <cstdlib>
19 #include <set>
20 
21 #include <biboumi.h>
22 #ifdef SYSTEMD_FOUND
23 # include <systemd/sd-daemon.h>
24 #endif
25 
26 using namespace std::string_literals;
27 
28 static std::set<std::string> kickable_errors{
29     "gone",
30     "internal-server-error",
31     "item-not-found",
32     "jid-malformed",
33     "recipient-unavailable",
34     "redirect",
35     "remote-server-not-found",
36     "remote-server-timeout",
37     "service-unavailable",
38     "malformed-error"
39     };
40 
XmppComponent(std::shared_ptr<Poller> & poller,std::string hostname,std::string secret)41 XmppComponent::XmppComponent(std::shared_ptr<Poller>& poller, std::string hostname, std::string secret):
42   TCPClientSocketHandler(poller),
43   ever_auth(false),
44   first_connection_try(true),
45   secret(std::move(secret)),
46   authenticated(false),
47   doc_open(false),
48   served_hostname(std::move(hostname)),
49   stanza_handlers{},
50   adhoc_commands_handler(*this)
51 {
52   this->parser.add_stream_open_callback(std::bind(&XmppComponent::on_remote_stream_open, this,
53                                                   std::placeholders::_1));
54   this->parser.add_stanza_callback(std::bind(&XmppComponent::on_stanza, this,
55                                                   std::placeholders::_1));
56   this->parser.add_stream_close_callback(std::bind(&XmppComponent::on_remote_stream_close, this,
57                                                   std::placeholders::_1));
58   this->stanza_handlers.emplace("handshake",
59                                 std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1));
60   this->stanza_handlers.emplace("error",
61                                 std::bind(&XmppComponent::handle_error, this,std::placeholders::_1));
62 }
63 
start()64 void XmppComponent::start()
65 {
66   this->connect(Config::get("xmpp_server_ip", "127.0.0.1"), Config::get("port", "5347"), false);
67 }
68 
is_document_open() const69 bool XmppComponent::is_document_open() const
70 {
71   return this->doc_open;
72 }
73 
send_stanza(const Stanza & stanza)74 void XmppComponent::send_stanza(const Stanza& stanza)
75 {
76   std::string str = stanza.to_string();
77   log_debug("XMPP SENDING: ", str);
78   this->send_data(std::move(str));
79 }
80 
on_connection_failed(const std::string & reason)81 void XmppComponent::on_connection_failed(const std::string& reason)
82 {
83   this->first_connection_try = false;
84   log_error("Failed to connect to the XMPP server: ", reason);
85 #ifdef SYSTEMD_FOUND
86   sd_notifyf(0, "STATUS=Failed to connect to the XMPP server: %s", reason.data());
87 #endif
88 }
89 
on_connected()90 void XmppComponent::on_connected()
91 {
92   log_info("connected to XMPP server");
93   this->first_connection_try = true;
94   auto data = "<stream:stream to='" + this->served_hostname + \
95     "' xmlns:stream='http://etherx.jabber.org/streams' xmlns='" COMPONENT_NS "'>";
96   log_debug("XMPP SENDING: ", data);
97   this->send_data(std::move(data));
98   this->doc_open = true;
99   // We may have some pending data to send: this happens when we try to send
100   // some data before we are actually connected.  We send that data right now, if any
101   this->send_pending_data();
102 }
103 
on_connection_close(const std::string & error)104 void XmppComponent::on_connection_close(const std::string& error)
105 {
106   if (error.empty())
107     log_info("XMPP server closed connection");
108   else
109     log_info("XMPP server closed connection: ", error);
110 }
111 
parse_in_buffer(const size_t size)112 void XmppComponent::parse_in_buffer(const size_t size)
113 {
114   // in_buf.size, or size, cannot be bigger than our read-size (4096) so it’s safe
115   // to cast.
116 
117   if (!this->in_buf.empty())
118     { // This may happen if the parser could not allocate enough space for
119       // us. We try to feed it the data that was read into our in_buf
120       // instead. If this fails again we are in trouble.
121       this->parser.feed(this->in_buf.data(), static_cast<int>(this->in_buf.size()), false);
122       this->in_buf.clear();
123     }
124   else
125     { // Just tell the parser to parse the data that was placed into the
126       // buffer it provided to us with GetBuffer
127       this->parser.parse(static_cast<int>(size), false);
128     }
129 }
130 
on_remote_stream_open(const XmlNode & node)131 void XmppComponent::on_remote_stream_open(const XmlNode& node)
132 {
133   log_debug("XMPP RECEIVING: ", node.to_string());
134   this->stream_id = node.get_tag("id");
135   if (this->stream_id.empty())
136     {
137       log_error("Error: no attribute 'id' found");
138       this->send_stream_error("bad-format", "missing 'id' attribute");
139       this->close_document();
140       return ;
141     }
142 
143   // Try to authenticate
144   auto data = "<handshake xmlns='" COMPONENT_NS "'>" + get_handshake_digest(this->stream_id, this->secret) + "</handshake>";
145   log_debug("XMPP SENDING: ", data);
146   this->send_data(std::move(data));
147 }
148 
on_remote_stream_close(const XmlNode & node)149 void XmppComponent::on_remote_stream_close(const XmlNode& node)
150 {
151   log_debug("XMPP RECEIVING: ", node.to_string());
152   this->doc_open = false;
153 }
154 
reset()155 void XmppComponent::reset()
156 {
157   this->parser.reset();
158 }
159 
on_stanza(const Stanza & stanza)160 void XmppComponent::on_stanza(const Stanza& stanza)
161 {
162   log_debug("XMPP RECEIVING: ", stanza.to_string());
163   std::function<void(const Stanza&)> handler;
164   try
165     {
166       handler = this->stanza_handlers.at(stanza.get_name());
167     }
168   catch (const std::out_of_range& exception)
169     {
170       log_warning("No handler for stanza of type ", stanza.get_name());
171       return;
172     }
173   handler(stanza);
174 }
175 
send_stream_error(const std::string & name,const std::string & explanation)176 void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation)
177 {
178   Stanza node("stream:error");
179   {
180     XmlSubNode error(node, name);
181     error["xmlns"] = STREAM_NS;
182     if (!explanation.empty())
183       error.set_inner(explanation);
184   }
185   this->send_stanza(node);
186 }
187 
send_stanza_error(const std::string & kind,const std::string & to,const std::string & from,const std::string & id,const std::string & error_type,const std::string & defined_condition,const std::string & text,const bool fulljid)188 void XmppComponent::send_stanza_error(const std::string& kind, const std::string& to, const std::string& from,
189                                       const std::string& id, const std::string& error_type,
190                                       const std::string& defined_condition, const std::string& text,
191                                       const bool fulljid)
192 {
193   Stanza node(kind);
194   {
195     if (!to.empty())
196       node["to"] = to;
197     if (!from.empty())
198       {
199         if (fulljid)
200           node["from"] = from;
201         else
202           node["from"] = from + "@" + this->served_hostname;
203       }
204     if (!id.empty())
205       node["id"] = id;
206     node["type"] = "error";
207     {
208       XmlSubNode error(node, "error");
209       error["type"] = error_type;
210       {
211         XmlSubNode inner_error(error, defined_condition);
212         inner_error["xmlns"] = STANZA_NS;
213       }
214       if (!text.empty())
215         {
216           XmlSubNode text_node(error, "text");
217           text_node["xmlns"] = STANZA_NS;
218           text_node.set_inner(text);
219         }
220     }
221   }
222   this->send_stanza(node);
223 }
224 
close_document()225 void XmppComponent::close_document()
226 {
227   log_debug("XMPP SENDING: </stream:stream>");
228   this->send_data("</stream:stream>");
229   this->doc_open = false;
230 }
231 
handle_handshake(const Stanza &)232 void XmppComponent::handle_handshake(const Stanza&)
233 {
234   this->authenticated = true;
235   this->ever_auth = true;
236   log_info("Authenticated with the XMPP server");
237 #ifdef SYSTEMD_FOUND
238   sd_notify(0, "READY=1");
239   // Install an event that sends a keepalive to systemd.  If biboumi crashes
240   // or hangs for too long, systemd will restart it.
241   uint64_t usec;
242   if (sd_watchdog_enabled(0, &usec) > 0)
243     {
244       TimedEventsManager::instance().add_event(TimedEvent(
245              std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::microseconds(usec / 2)),
246              []() { sd_notify(0, "WATCHDOG=1"); }));
247     }
248 #endif
249   this->after_handshake();
250 }
251 
handle_error(const Stanza & stanza)252 void XmppComponent::handle_error(const Stanza& stanza)
253 {
254   const XmlNode* text = stanza.get_child("text", STREAMS_NS);
255   std::string error_message("Unspecified error");
256   if (text)
257     error_message = text->get_inner();
258   log_error("Stream error received from the XMPP server: ", error_message);
259 #ifdef SYSTEMD_FOUND
260   if (!this->ever_auth)
261     sd_notifyf(0, "STATUS=Failed to authenticate to the XMPP server: %s", error_message.data());
262 #endif
263 }
264 
get_receive_buffer(const size_t size) const265 void* XmppComponent::get_receive_buffer(const size_t size) const
266 {
267   return this->parser.get_buffer(size);
268 }
269 
send_message(const std::string & from,Xmpp::body && body,const std::string & to,const std::string & type,const bool fulljid,const bool nocopy,const bool muc_private)270 void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to,
271                                  const std::string& type, const bool fulljid, const bool nocopy,
272                                  const bool muc_private)
273 {
274   Stanza message("message");
275   {
276     message["to"] = to;
277     if (fulljid)
278       message["from"] = from;
279     else
280       {
281         if (!from.empty())
282           message["from"] = from + "@" + this->served_hostname;
283         else
284           message["from"] = this->served_hostname;
285       }
286     if (!type.empty())
287       message["type"] = type;
288     XmlSubNode body_node(message, "body");
289     body_node.set_inner(std::get<0>(body));
290     if (std::get<1>(body))
291       {
292         XmlSubNode html(message, "html");
293         html["xmlns"] = XHTMLIM_NS;
294         // Pass the ownership of the pointer to this xmlnode
295         html.add_child(std::move(std::get<1>(body)));
296       }
297     if (nocopy)
298       {
299         XmlSubNode private_node(message, "private");
300         private_node["xmlns"] = "urn:xmpp:carbons:2";
301         XmlSubNode nocopy_node(message, "no-copy");
302         nocopy_node["xmlns"] = "urn:xmpp:hints";
303       }
304     if (muc_private)
305       {
306         XmlSubNode x(message, "x");
307         x["xmlns"] = MUC_USER_NS;
308       }
309   }
310   this->send_stanza(message);
311 }
312 
send_user_join(const std::string & from,const std::string & nick,const std::string & realjid,const std::string & affiliation,const std::string & role,const std::string & to,const bool self)313 void XmppComponent::send_user_join(const std::string& from,
314                                    const std::string& nick,
315                                    const std::string& realjid,
316                                    const std::string& affiliation,
317                                    const std::string& role,
318                                    const std::string& to,
319                                    const bool self)
320 {
321   Stanza presence("presence");
322   {
323     presence["to"] = to;
324     presence["from"] = from + "@" + this->served_hostname + "/" + nick;
325 
326     XmlSubNode x(presence, "x");
327     x["xmlns"] = MUC_USER_NS;
328 
329     XmlSubNode item(x, "item");
330     if (!affiliation.empty())
331       item["affiliation"] = affiliation;
332     if (!role.empty())
333       item["role"] = role;
334     if (!realjid.empty())
335       {
336         const std::string preped_jid = jidprep(realjid);
337         if (!preped_jid.empty())
338           item["jid"] = preped_jid;
339       }
340 
341     if (self)
342       {
343         XmlSubNode status_self(x, "status");
344         status_self["code"] = "110";
345         XmlSubNode status_nick_modified(x, "status");
346         status_nick_modified["code"] = "210";
347         XmlSubNode status_nonanonymous(x, "status");
348         status_nonanonymous["code"] = "100";
349       }
350   }
351   this->send_stanza(presence);
352 }
353 
send_topic(const std::string & from,Xmpp::body && topic,const std::string & to,const std::string & who)354 void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to, const std::string& who)
355 {
356   Stanza message("message");
357   {
358     message["to"] = to;
359     if (who.empty())
360       message["from"] = from + "@" + this->served_hostname;
361     else
362       message["from"] = from + "@" + this->served_hostname + "/" + who;
363     message["type"] = "groupchat";
364     XmlSubNode subject(message, "subject");
365     subject.set_inner(std::get<0>(topic));
366   }
367   this->send_stanza(message);
368 }
369 
make_muc_message(const std::string & muc_name,const std::string & nick,Xmpp::body && xmpp_body,const std::string & jid_to,std::string uuid,std::string id)370 Stanza XmppComponent::make_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& xmpp_body, const std::string& jid_to, std::string uuid, std::string id)
371 {
372   Stanza message("message");
373   message["to"] = jid_to;
374   message["id"] = std::move(id);
375   if (!nick.empty())
376     message["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
377   else // Message from the room itself
378     message["from"] = muc_name + "@" + this->served_hostname;
379   message["type"] = "groupchat";
380 
381   {
382     XmlSubNode body(message, "body");
383     body.set_inner(std::get<0>(xmpp_body));
384   }
385 
386   if (std::get<1>(xmpp_body))
387     {
388       XmlSubNode html(message, "html");
389       html["xmlns"] = XHTMLIM_NS;
390       // Pass the ownership of the pointer to this xmlnode
391       html.add_child(std::move(std::get<1>(xmpp_body)));
392     }
393 
394   if (!uuid.empty())
395     {
396       XmlSubNode stanza_id(message, "stanza-id");
397       stanza_id["xmlns"] = STABLE_ID_NS;
398       stanza_id["by"] = muc_name + "@" + this->served_hostname;
399       stanza_id["id"] = std::move(uuid);
400     }
401 
402   return message;
403 }
404 
405 #ifdef USE_DATABASE
send_history_message(const std::string & muc_name,const std::string & nick,const std::string & body_txt,const std::string & jid_to,Database::time_point::rep timestamp)406 void XmppComponent::send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body_txt, const std::string& jid_to, Database::time_point::rep timestamp)
407 {
408   Stanza message("message");
409   message["to"] = jid_to;
410   if (!nick.empty())
411     message["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
412   else
413     message["from"] = muc_name + "@" + this->served_hostname;
414   message["type"] = "groupchat";
415 
416   {
417     XmlSubNode body(message, "body");
418     body.set_inner(body_txt);
419   }
420   {
421     XmlSubNode delay(message, "delay");
422     delay["xmlns"] = DELAY_NS;
423     delay["from"] = muc_name + "@" + this->served_hostname;
424     delay["stamp"] = utils::to_string(timestamp);
425   }
426 
427   this->send_stanza(message);
428 }
429 #endif
430 
send_muc_leave(const std::string & muc_name,const std::string & nick,Xmpp::body && message,const std::string & jid_to,const bool self,const bool user_requested,const std::string & affiliation,const std::string & role)431 void XmppComponent::send_muc_leave(const std::string& muc_name, const std::string& nick, Xmpp::body&& message,
432                                    const std::string& jid_to, const bool self, const bool user_requested,
433                                    const std::string& affiliation, const std::string& role)
434 {
435   Stanza presence("presence");
436   {
437     presence["to"] = jid_to;
438     presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
439     presence["type"] = "unavailable";
440     const std::string& message_str = std::get<0>(message);
441     XmlSubNode x(presence, "x");
442     x["xmlns"] = MUC_USER_NS;
443     if (self)
444       {
445         {
446           XmlSubNode status(x, "status");
447           status["code"] = "110";
448         }
449         if (!user_requested)
450           {
451             XmlSubNode status(x, "status");
452             status["code"] = "332";
453           }
454       }
455     XmlSubNode item(x, "item");
456     item["affiliation"] = affiliation;
457     item["role"] = role;
458     if (!message_str.empty())
459       {
460         XmlSubNode status(presence, "status");
461         status.set_inner(message_str);
462       }
463   }
464   this->send_stanza(presence);
465 }
466 
send_nick_change(const std::string & muc_name,const std::string & old_nick,const std::string & new_nick,const std::string & affiliation,const std::string & role,const std::string & jid_to,const bool self)467 void XmppComponent::send_nick_change(const std::string& muc_name,
468                                      const std::string& old_nick,
469                                      const std::string& new_nick,
470                                      const std::string& affiliation,
471                                      const std::string& role,
472                                      const std::string& jid_to,
473                                      const bool self)
474 {
475   Stanza presence("presence");
476   {
477     presence["to"] = jid_to;
478     presence["from"] = muc_name + "@" + this->served_hostname + "/" + old_nick;
479     presence["type"] = "unavailable";
480     XmlSubNode x(presence, "x");
481     x["xmlns"] = MUC_USER_NS;
482     XmlSubNode item(x, "item");
483     item["nick"] = new_nick;
484     item["affiliation"] = affiliation;
485     item["role"] = role;
486     XmlSubNode status(x, "status");
487     status["code"] = "303";
488     if (self)
489       {
490         XmlSubNode status(x, "status");
491         status["code"] = "110";
492       }
493   }
494   this->send_stanza(presence);
495 
496   this->send_user_join(muc_name, new_nick, "", affiliation, role, jid_to, self);
497 }
498 
kick_user(const std::string & muc_name,const std::string & target,const std::string & txt,const std::string & author,const std::string & jid_to,const bool self)499 void XmppComponent::kick_user(const std::string& muc_name, const std::string& target, const std::string& txt,
500                               const std::string& author, const std::string& jid_to, const bool self)
501 {
502   Stanza presence("presence");
503   {
504     presence["from"] = muc_name + "@" + this->served_hostname + "/" + target;
505     presence["to"] = jid_to;
506     presence["type"] = "unavailable";
507     XmlSubNode x(presence, "x");
508     x["xmlns"] = MUC_USER_NS;
509     XmlSubNode item(x, "item");
510     item["affiliation"] = "none";
511     item["role"] = "none";
512     XmlSubNode actor(item, "actor");
513     actor["nick"] = author;
514     actor["jid"] = author; // backward compatibility with old clients
515     XmlSubNode reason(item, "reason");
516     reason.set_inner(txt);
517     XmlSubNode status(x, "status");
518     status["code"] = "307";
519     if (self)
520       {
521         XmlSubNode status(x, "status");
522         status["code"] = "110";
523       }
524   }
525   this->send_stanza(presence);
526 }
527 
send_presence_error(const std::string & muc_name,const std::string & nickname,const std::string & jid_to,const std::string & type,const std::string & condition,const std::string & error_code,const std::string & text)528 void XmppComponent::send_presence_error(const std::string& muc_name,
529                                         const std::string& nickname,
530                                         const std::string& jid_to,
531                                         const std::string& type,
532                                         const std::string& condition,
533                                         const std::string& error_code,
534                                         const std::string& text)
535 {
536   Stanza presence("presence");
537   {
538     presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname;
539     presence["to"] = jid_to;
540     presence["type"] = "error";
541     XmlSubNode x(presence, "x");
542     x["xmlns"] = MUC_NS;
543     XmlSubNode error(presence, "error");
544     error["by"] = muc_name + "@" + this->served_hostname;
545     error["type"] = type;
546     if (!text.empty())
547       {
548         XmlSubNode text_node(error, "text");
549         text_node["xmlns"] = STANZA_NS;
550         text_node.set_inner(text);
551       }
552     if (!error_code.empty())
553       error["code"] = error_code;
554     XmlSubNode subnode(error, condition);
555     subnode["xmlns"] = STANZA_NS;
556   }
557   this->send_stanza(presence);
558 }
559 
send_affiliation_role_change(const std::string & muc_name,const std::string & target,const std::string & affiliation,const std::string & role,const std::string & jid_to)560 void XmppComponent::send_affiliation_role_change(const std::string& muc_name,
561                                                  const std::string& target,
562                                                  const std::string& affiliation,
563                                                  const std::string& role,
564                                                  const std::string& jid_to)
565 {
566   Stanza presence("presence");
567   {
568     presence["from"] = muc_name + "@" + this->served_hostname + "/" + target;
569     presence["to"] = jid_to;
570     XmlSubNode x(presence, "x");
571     x["xmlns"] = MUC_USER_NS;
572     XmlSubNode item(x, "item");
573     item["affiliation"] = affiliation;
574     item["role"] = role;
575   }
576   this->send_stanza(presence);
577 }
578 
send_version(const std::string & id,const std::string & jid_to,const std::string & jid_from,const std::string & version)579 void XmppComponent::send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from,
580                                  const std::string& version)
581 {
582   Stanza iq("iq");
583   iq["type"] = "result";
584   iq["id"] = id;
585   iq["to"] = jid_to;
586   iq["from"] = jid_from;
587   {
588     XmlSubNode query(iq, "query");
589     query["xmlns"] = VERSION_NS;
590     if (version.empty())
591       {
592         {
593           XmlSubNode name(query, "name");
594           name.set_inner("biboumi");
595         }
596         {
597           XmlSubNode version_node(query, "version");
598           version_node.set_inner(SOFTWARE_VERSION);
599         }
600         {
601           XmlSubNode os(query, "os");
602           os.set_inner(utils::get_system_name());
603         }
604     }
605     else
606     {
607       XmlSubNode name(query, "name");
608       name.set_inner(version);
609     }
610   }
611   this->send_stanza(iq);
612 }
613 
send_adhoc_commands_list(const std::string & id,const std::string & requester_jid,const std::string & from_jid,const bool with_admin_only,const AdhocCommandsHandler & adhoc_handler)614 void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid,
615                                              const std::string& from_jid,
616                                              const bool with_admin_only, const AdhocCommandsHandler& adhoc_handler)
617 {
618   Stanza iq("iq");
619   {
620     iq["type"] = "result";
621     iq["id"] = id;
622     iq["to"] = requester_jid;
623     iq["from"] = from_jid;
624     XmlSubNode query(iq, "query");
625     query["xmlns"] = DISCO_ITEMS_NS;
626     query["node"] = ADHOC_NS;
627     for (const auto &kv: adhoc_handler.get_commands())
628       {
629         if (kv.second.is_admin_only() && !with_admin_only)
630           continue;
631         XmlSubNode item(query, "item");
632         item["jid"] = from_jid;
633         item["node"] = kv.first;
634         item["name"] = kv.second.name;
635       }
636   }
637   this->send_stanza(iq);
638 }
639 
send_iq_version_request(const std::string & from,const std::string & jid_to)640 void XmppComponent::send_iq_version_request(const std::string& from,
641                                             const std::string& jid_to)
642 {
643   Stanza iq("iq");
644   {
645     iq["type"] = "get";
646     iq["id"] = "version_" + XmppComponent::next_id();
647     iq["from"] = from + "@" + this->served_hostname;
648     iq["to"] = jid_to;
649     XmlSubNode query(iq, "query");
650     query["xmlns"] = VERSION_NS;
651   }
652   this->send_stanza(iq);
653 }
654 
send_iq_result_full_jid(const std::string & id,const std::string & to_jid,const std::string & from_full_jid,std::unique_ptr<XmlNode> inner)655 void XmppComponent::send_iq_result_full_jid(const std::string& id, const std::string& to_jid, const std::string& from_full_jid, std::unique_ptr<XmlNode> inner)
656 {
657   Stanza iq("iq");
658   iq["from"] = from_full_jid;
659   iq["to"] = to_jid;
660   iq["id"] = id;
661   iq["type"] = "result";
662   if (inner)
663     iq.add_child(std::move(inner));
664   this->send_stanza(iq);
665 }
666 
send_iq_result(const std::string & id,const std::string & to_jid,const std::string & from_local_part)667 void XmppComponent::send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from_local_part)
668 {
669   Stanza iq("iq");
670   if (!from_local_part.empty())
671     iq["from"] = from_local_part + "@" + this->served_hostname;
672   else
673     iq["from"] = this->served_hostname;
674   iq["to"] = to_jid;
675   iq["id"] = id;
676   iq["type"] = "result";
677   this->send_stanza(iq);
678 }
679 
next_id()680 std::string XmppComponent::next_id()
681 {
682   return utils::gen_uuid();
683 }
684