1 /*
2 * Implementation the Pedro mini-XMPP client
3 *
4 * Authors:
5 * Bob Jamison
6 *
7 * Copyright (C) 2005-2006 Bob Jamison
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 */
25 #include <stdio.h>
26 #include <stdarg.h>
28 #include <sys/stat.h>
30 #include <time.h>
32 #include "pedroxmpp.h"
33 #include "pedrodom.h"
34 #include "pedroutil.h"
36 #include <map>
40 namespace Pedro
41 {
44 //########################################################################
45 //########################################################################
46 //# X M P P E V E N T
47 //########################################################################
48 //########################################################################
51 XmppEvent::XmppEvent(int type)
52 {
53 eventType = type;
54 presence = false;
55 dom = NULL;
56 }
58 XmppEvent::XmppEvent(const XmppEvent &other)
59 {
60 assign(other);
61 }
63 XmppEvent &XmppEvent::operator=(const XmppEvent &other)
64 {
65 assign(other);
66 return (*this);
67 }
69 XmppEvent::~XmppEvent()
70 {
71 if (dom)
72 delete dom;
73 }
75 void XmppEvent::assign(const XmppEvent &other)
76 {
77 eventType = other.eventType;
78 presence = other.presence;
79 status = other.status;
80 show = other.show;
81 to = other.to;
82 from = other.from;
83 group = other.group;
84 data = other.data;
85 fileName = other.fileName;
86 fileDesc = other.fileDesc;
87 fileSize = other.fileSize;
88 fileHash = other.fileHash;
89 setDOM(other.dom);
90 }
92 int XmppEvent::getType() const
93 {
94 return eventType;
95 }
97 DOMString XmppEvent::getIqId() const
98 {
99 return iqId;
100 }
102 void XmppEvent::setIqId(const DOMString &val)
103 {
104 iqId = val;
105 }
107 DOMString XmppEvent::getStreamId() const
108 {
109 return streamId;
110 }
112 void XmppEvent::setStreamId(const DOMString &val)
113 {
114 streamId = val;
115 }
117 bool XmppEvent::getPresence() const
118 {
119 return presence;
120 }
122 void XmppEvent::setPresence(bool val)
123 {
124 presence = val;
125 }
127 DOMString XmppEvent::getShow() const
128 {
129 return show;
130 }
132 void XmppEvent::setShow(const DOMString &val)
133 {
134 show = val;
135 }
137 DOMString XmppEvent::getStatus() const
138 {
139 return status;
140 }
142 void XmppEvent::setStatus(const DOMString &val)
143 {
144 status = val;
145 }
147 DOMString XmppEvent::getTo() const
148 {
149 return to;
150 }
152 void XmppEvent::setTo(const DOMString &val)
153 {
154 to = val;
155 }
157 DOMString XmppEvent::getFrom() const
158 {
159 return from;
160 }
162 void XmppEvent::setFrom(const DOMString &val)
163 {
164 from = val;
165 }
167 DOMString XmppEvent::getGroup() const
168 {
169 return group;
170 }
172 void XmppEvent::setGroup(const DOMString &val)
173 {
174 group = val;
175 }
177 DOMString XmppEvent::getData() const
178 {
179 return data;
180 }
182 void XmppEvent::setData(const DOMString &val)
183 {
184 data = val;
185 }
187 DOMString XmppEvent::getFileName() const
188 {
189 return fileName;
190 }
192 void XmppEvent::setFileName(const DOMString &val)
193 {
194 fileName = val;
195 }
197 DOMString XmppEvent::getFileDesc() const
198 {
199 return fileDesc;
200 }
202 void XmppEvent::setFileDesc(const DOMString &val)
203 {
204 fileDesc = val;
205 }
207 long XmppEvent::getFileSize() const
208 {
209 return fileSize;
210 }
212 void XmppEvent::setFileSize(long val)
213 {
214 fileSize = val;
215 }
217 DOMString XmppEvent::getFileHash() const
218 {
219 return fileHash;
220 }
222 void XmppEvent::setFileHash(const DOMString &val)
223 {
224 fileHash = val;
225 }
227 Element *XmppEvent::getDOM() const
228 {
229 return dom;
230 }
232 void XmppEvent::setDOM(const Element *val)
233 {
234 if (!val)
235 dom = NULL;
236 else
237 dom = ((Element *)val)->clone();
238 }
241 std::vector<XmppUser> XmppEvent::getUserList() const
242 {
243 return userList;
244 }
246 void XmppEvent::setUserList(const std::vector<XmppUser> &val)
247 {
248 userList = val;
249 }
259 //########################################################################
260 //########################################################################
261 //# X M P P E V E N T T A R G E T
262 //########################################################################
263 //########################################################################
266 //###########################
267 //# CONSTRUCTORS
268 //###########################
270 XmppEventTarget::XmppEventTarget()
271 {
272 eventQueueEnabled = false;
273 }
276 XmppEventTarget::XmppEventTarget(const XmppEventTarget &other)
277 {
278 listeners = other.listeners;
279 eventQueueEnabled = other.eventQueueEnabled;
280 }
282 XmppEventTarget::~XmppEventTarget()
283 {
284 }
287 //###########################
288 //# M E S S A G E S
289 //###########################
291 /**
292 * Print a printf()-like formatted error message
293 */
294 void XmppEventTarget::error(char *fmt, ...)
295 {
296 va_list args;
297 va_start(args,fmt);
298 vsnprintf(targetWriteBuf, targetWriteBufLen, fmt, args);
299 va_end(args) ;
300 fprintf(stderr, "Error:%s\n", targetWriteBuf);
301 XmppEvent evt(XmppEvent::EVENT_ERROR);
302 evt.setData(targetWriteBuf);
303 dispatchXmppEvent(evt);
304 }
308 /**
309 * Print a printf()-like formatted trace message
310 */
311 void XmppEventTarget::status(char *fmt, ...)
312 {
313 va_list args;
314 va_start(args,fmt);
315 vsnprintf(targetWriteBuf, targetWriteBufLen, fmt, args);
316 va_end(args) ;
317 //printf("Status:%s\n", targetWriteBuf);
318 XmppEvent evt(XmppEvent::EVENT_STATUS);
319 evt.setData(targetWriteBuf);
320 dispatchXmppEvent(evt);
321 }
325 //###########################
326 //# L I S T E N E R S
327 //###########################
329 void XmppEventTarget::dispatchXmppEvent(const XmppEvent &event)
330 {
331 std::vector<XmppEventListener *>::iterator iter;
332 for (iter = listeners.begin(); iter != listeners.end() ; iter++)
333 (*iter)->processXmppEvent(event);
334 if (eventQueueEnabled)
335 eventQueue.push_back(event);
336 }
338 void XmppEventTarget::addXmppEventListener(const XmppEventListener &listener)
339 {
340 XmppEventListener *lsnr = (XmppEventListener *)&listener;
341 std::vector<XmppEventListener *>::iterator iter;
342 for (iter = listeners.begin(); iter != listeners.end() ; iter++)
343 if (*iter == lsnr)
344 return;
345 listeners.push_back(lsnr);
346 }
348 void XmppEventTarget::removeXmppEventListener(const XmppEventListener &listener)
349 {
350 XmppEventListener *lsnr = (XmppEventListener *)&listener;
351 std::vector<XmppEventListener *>::iterator iter;
352 for (iter = listeners.begin(); iter != listeners.end() ; iter++)
353 if (*iter == lsnr)
354 listeners.erase(iter);
355 }
357 void XmppEventTarget::clearXmppEventListeners()
358 {
359 listeners.clear();
360 }
363 //###########################
364 //# E V E N T Q U E U E
365 //###########################
367 void XmppEventTarget::eventQueueEnable(bool val)
368 {
369 eventQueueEnabled = val;
370 if (!eventQueueEnabled)
371 eventQueue.clear();
372 }
374 int XmppEventTarget::eventQueueAvailable()
375 {
376 return eventQueue.size();
377 }
379 XmppEvent XmppEventTarget::eventQueuePop()
380 {
381 if (!eventQueueEnabled || eventQueue.size()<1)
382 {
383 XmppEvent dummy(XmppEvent::EVENT_NONE);
384 return dummy;
385 }
386 XmppEvent event = *(eventQueue.begin());
387 eventQueue.erase(eventQueue.begin());
388 return event;
389 }
395 //########################################################################
396 //########################################################################
397 //# X M P P S T R E A M
398 //########################################################################
399 //########################################################################
402 /**
403 *
404 */
405 class XmppStream
406 {
407 public:
409 /**
410 *
411 */
412 XmppStream();
414 /**
415 *
416 */
417 virtual ~XmppStream();
419 /**
420 *
421 */
422 virtual void reset();
424 /**
425 *
426 */
427 virtual int getState();
429 /**
430 *
431 */
432 virtual void setState(int val);
434 /**
435 *
436 */
437 virtual DOMString getStreamId();
439 /**
440 *
441 */
442 void setStreamId(const DOMString &val);
444 /**
445 *
446 */
447 virtual DOMString getIqId();
449 /**
450 *
451 */
452 void setIqId(const DOMString &val);
454 /**
455 *
456 */
457 virtual int getSeqNr();
459 /**
460 *
461 */
462 virtual DOMString getPeerId();
464 /**
465 *
466 */
467 virtual void setPeerId(const DOMString &val);
469 /**
470 *
471 */
472 int available();
474 /**
475 *
476 */
477 void receiveData(std::vector<unsigned char> &newData);
479 /**
480 *
481 */
482 std::vector<unsigned char> read();
484 private:
487 DOMString streamId;
489 DOMString iqId;
491 DOMString sourceId;
493 int state;
495 long seqNr;
497 std::vector<unsigned char> data;
498 };
501 /**
502 *
503 */
504 XmppStream::XmppStream()
505 {
506 reset();
507 }
509 /**
510 *
511 */
512 XmppStream::~XmppStream()
513 {
514 reset();
515 }
517 /**
518 *
519 */
520 void XmppStream::reset()
521 {
522 state = XmppClient::STREAM_AVAILABLE;
523 seqNr = 0;
524 data.clear();
525 }
527 /**
528 *
529 */
530 int XmppStream::getState()
531 {
532 return state;
533 }
535 /**
536 *
537 */
538 void XmppStream::setState(int val)
539 {
540 state = val;
541 }
543 /**
544 *
545 */
546 DOMString XmppStream::getStreamId()
547 {
548 return streamId;
549 }
551 /**
552 *
553 */
554 void XmppStream::setStreamId(const DOMString &val)
555 {
556 streamId = val;
557 }
559 /**
560 *
561 */
562 DOMString XmppStream::getIqId()
563 {
564 return iqId;
565 }
568 /**
569 *
570 */
571 void XmppStream::setIqId(const DOMString &val)
572 {
573 iqId = val;
574 }
576 /**
577 * Source or destination JID
578 */
579 void XmppStream::setPeerId(const DOMString &val)
580 {
581 sourceId = val;
582 }
584 /**
585 * Source or destination JID
586 */
587 DOMString XmppStream::getPeerId()
588 {
589 return sourceId;
590 }
592 /**
593 * Stream packet sequence number
594 */
595 int XmppStream::getSeqNr()
596 {
597 seqNr++;
598 if (seqNr >= 65535)
599 seqNr = 0;
600 return seqNr;
601 }
603 /**
604 *
605 */
606 int XmppStream::available()
607 {
608 return data.size();
609 }
611 /**
612 *
613 */
614 void XmppStream::receiveData(std::vector<unsigned char> &newData)
615 {
616 std::vector<unsigned char>::iterator iter;
617 for (iter=newData.begin() ; iter!=newData.end() ; iter++)
618 data.push_back(*iter);
619 }
621 /**
622 *
623 */
624 std::vector<unsigned char> XmppStream::read()
625 {
626 if (state != XmppClient::STREAM_OPEN)
627 {
628 std::vector<unsigned char>dummy;
629 return dummy;
630 }
631 std::vector<unsigned char> ret = data;
632 data.clear();
633 return ret;
634 }
641 //########################################################################
642 //########################################################################
643 //# X M P P C L I E N T
644 //########################################################################
645 //########################################################################
647 class ReceiverThread : public Runnable
648 {
649 public:
651 ReceiverThread(XmppClient &par) : client(par) {}
653 virtual ~ReceiverThread() {}
655 void run()
656 { client.receiveAndProcessLoop(); }
658 private:
660 XmppClient &client;
661 };
667 //########################################################################
668 //# CONSTRUCTORS
669 //########################################################################
671 XmppClient::XmppClient()
672 {
673 init();
674 }
677 XmppClient::XmppClient(const XmppClient &other) : XmppEventTarget(other)
678 {
679 init();
680 assign(other);
681 }
683 void XmppClient::assign(const XmppClient &other)
684 {
685 msgId = other.msgId;
686 host = other.host;
687 realm = other.realm;
688 port = other.port;
689 username = other.username;
690 password = other.password;
691 resource = other.resource;
692 connected = other.connected;
693 doRegister = other.doRegister;
694 groupChats = other.groupChats;
695 }
698 void XmppClient::init()
699 {
700 sock = new TcpSocket();
701 msgId = 0;
702 connected = false;
703 doRegister = false;
705 for (int i=0 ; i<outputStreamCount ; i++)
706 {
707 outputStreams[i] = new XmppStream();
708 }
709 for (int i=0 ; i<inputStreamCount ; i++)
710 {
711 inputStreams[i] = new XmppStream();
712 }
713 for (int i=0 ; i<fileSendCount ; i++)
714 {
715 fileSends[i] = new XmppStream();
716 }
717 }
719 XmppClient::~XmppClient()
720 {
721 disconnect();
722 delete sock;
723 for (int i=0 ; i<outputStreamCount ; i++)
724 {
725 delete outputStreams[i];
726 }
727 for (int i=0 ; i<inputStreamCount ; i++)
728 {
729 delete inputStreams[i];
730 }
731 for (int i=0 ; i<fileSendCount ; i++)
732 {
733 delete fileSends[i];
734 }
735 groupChatsClear();
736 }
743 //########################################################################
744 //# UTILILY
745 //########################################################################
747 /**
748 *
749 */
750 bool XmppClient::pause(unsigned long millis)
751 {
752 Thread::sleep(millis);
753 return true;
754 }
757 static int strIndex(const DOMString &str, char *key)
758 {
759 unsigned int p = str.find(key);
760 if (p == str.npos)
761 return -1;
762 return p;
763 }
767 DOMString XmppClient::toXml(const DOMString &str)
768 {
769 return Parser::encode(str);
770 }
774 static DOMString trim(const DOMString &str)
775 {
776 unsigned int i;
777 for (i=0 ; i<str.size() ; i++)
778 if (!isspace(str[i]))
779 break;
780 int start = i;
781 for (i=str.size() ; i>0 ; i--)
782 if (!isspace(str[i-1]))
783 break;
784 int end = i;
785 if (start>=end)
786 return "";
787 return str.substr(start, end);
788 }
794 //########################################################################
795 //# VARIABLES (ones that need special handling)
796 //########################################################################
798 /**
799 *
800 */
801 DOMString XmppClient::getUsername()
802 {
803 return username;
804 }
806 /**
807 *
808 */
809 void XmppClient::setUsername(const DOMString &val)
810 {
811 int p = strIndex(val, "@");
812 if (p > 0)
813 {
814 username = val.substr(0, p);
815 realm = val.substr(p+1, jid.size()-p-1);
816 }
817 else
818 {
819 realm = host;
820 username = val;
821 }
822 }
831 //########################################################################
832 //# RECEIVING
833 //########################################################################
836 DOMString XmppClient::readStanza()
837 {
839 int openCount = 0;
840 bool inTag = false;
841 bool slashSeen = false;
842 bool trivialTag = false;
843 bool querySeen = false;
844 bool inQuote = false;
845 bool textSeen = false;
846 DOMString buf;
849 time_t timeout = time((time_t *)0) + 180;
851 while (true)
852 {
853 int ch = sock->read();
854 //printf("%c", ch); fflush(stdout);
855 if (ch<0)
856 {
857 if (ch == -2) //a simple timeout, not an error
858 {
859 //Since we are timed out, let's assume that we
860 //are between chunks of text. Let's reset all states.
861 //printf("-----#### Timeout\n");
862 time_t currentTime = time((time_t *)0);
863 if (currentTime > timeout)
864 {
865 timeout = currentTime + 180;
866 if (!write("\n"))
867 {
868 error("ping send error");
869 disconnect();
870 return "";
871 }
872 }
873 continue;
874 }
875 else
876 {
877 keepGoing = false;
878 if (!sock->isConnected())
879 {
880 disconnect();
881 return "";
882 }
883 else
884 {
885 error("socket read error");
886 disconnect();
887 return "";
888 }
889 }
890 }
891 buf.push_back(ch);
892 if (ch == '<')
893 {
894 inTag = true;
895 slashSeen = false;
896 querySeen = false;
897 inQuote = false;
898 textSeen = false;
899 trivialTag = false;
900 }
901 else if (ch == '>')
902 {
903 if (!inTag) //unescaped '>' in pcdata? horror
904 continue;
905 inTag = false;
906 if (!trivialTag && !querySeen)
907 {
908 if (slashSeen)
909 openCount--;
910 else
911 openCount++;
912 }
913 //printf("# openCount:%d t:%d q:%d\n",
914 // openCount, trivialTag, querySeen);
915 //check if we are 'balanced', but not a <?version?> tag
916 if (openCount <= 0 && !querySeen)
917 {
918 break;
919 }
920 //we know that this one will be open-ended
921 if (strIndex(buf, "<stream:stream") >= 0)
922 {
923 buf.append("</stream:stream>");
924 break;
925 }
926 }
927 else if (ch == '/')
928 {
929 if (inTag && !inQuote)
930 {
931 slashSeen = true;
932 if (textSeen) // <tagName/> <--looks like this
933 trivialTag = true;
934 }
935 }
936 else if (ch == '?')
937 {
938 if (inTag && !inQuote)
939 querySeen = true;
940 }
941 else if (ch == '"' || ch == '\'')
942 {
943 if (inTag)
944 inQuote = !inQuote;
945 }
946 else
947 {
948 if (inTag && !inQuote && !isspace(ch))
949 textSeen = true;
950 }
951 }
952 return buf;
953 }
957 static bool isGroupChat(Element *root)
958 {
959 if (!root)
960 return false;
961 std::vector<Element *>elems = root->findElements("x");
962 for (unsigned int i=0 ; i<elems.size() ; i++)
963 {
964 DOMString xmlns = elems[i]->getAttribute("xmlns");
965 //printf("### XMLNS ### %s\n", xmlns.c_str());
966 if (strIndex(xmlns, "http://jabber.org/protocol/muc") >=0 )
967 return true;
968 }
969 return false;
970 }
975 static bool parseJid(const DOMString &fullJid,
976 DOMString &jid, DOMString &resource)
977 {
978 int p = strIndex(fullJid, "/");
979 if (p < 0)
980 {
981 jid = fullJid;
982 resource = "";
983 return true;
984 }
985 jid = fullJid.substr(0, p);
986 resource = fullJid.substr(p+1, fullJid.size()-p-1);
987 return true;
988 }
993 bool XmppClient::processMessage(Element *root)
994 {
995 DOMString from = root->getTagAttribute("message", "from");
996 DOMString to = root->getTagAttribute("message", "to");
997 DOMString type = root->getTagAttribute("message", "type");
999 //####Check for embedded namespaces here
1000 //# IN BAND BYTESTREAMS
1001 DOMString ibbNamespace = "http://jabber.org/protocol/ibb";
1002 if (root->getTagAttribute("data", "xmlns") == ibbNamespace)
1003 {
1004 DOMString streamId = root->getTagAttribute("data", "sid");
1005 if (streamId.size() > 0)
1006 {
1007 for (int i=0 ; i<inputStreamCount ; i++)
1008 {
1009 XmppStream *ins = inputStreams[i];
1010 //printf("##ins:%s streamid:%s\n",
1011 // ins->getStreamId().c_str(), streamId.c_str());
1012 if (ins->getStreamId() == streamId)
1013 {
1014 //# We have a winner
1015 if (ins->getState() != STREAM_OPEN)
1016 {
1017 XmppEvent event(XmppEvent::EVENT_ERROR);
1018 event.setFrom(from);
1019 event.setData("received unrequested stream data");
1020 dispatchXmppEvent(event);
1021 return true;
1022 }
1023 DOMString data = root->getTagValue("data");
1024 std::vector<unsigned char>binData =
1025 Base64Decoder::decode(data);
1026 ins->receiveData(binData);
1027 }
1028 }
1029 }
1030 }
1033 //#### NORMAL MESSAGES
1034 DOMString subject = root->getTagValue("subject");
1035 DOMString body = root->getTagValue("body");
1036 DOMString thread = root->getTagValue("thread");
1037 //##rfc 3921, para 2.4. ignore if no recognizable info
1038 //if (subject.size() < 1 && thread.size()<1)
1039 // return true;
1041 if (type == "groupchat")
1042 {
1043 DOMString fromGid;
1044 DOMString fromNick;
1045 parseJid(from, fromGid, fromNick);
1046 //printf("fromGid:%s fromNick:%s\n",
1047 // fromGid.c_str(), fromNick.c_str());
1048 DOMString toGid;
1049 DOMString toNick;
1050 parseJid(to, toGid, toNick);
1051 //printf("toGid:%s toNick:%s\n",
1052 // toGid.c_str(), toNick.c_str());
1054 if (fromNick.size() > 0)//normal group message
1055 {
1056 XmppEvent event(XmppEvent::EVENT_MUC_MESSAGE);
1057 event.setGroup(fromGid);
1058 event.setFrom(fromNick);
1059 event.setData(body);
1060 event.setDOM(root);
1061 dispatchXmppEvent(event);
1062 }
1063 else // from the server itself
1064 {
1065 //note the space before, so it doesnt match 'unlocked'
1066 if (strIndex(body, " locked") >= 0)
1067 {
1068 printf("LOCKED!! ;)\n");
1069 char *fmt =
1070 "<iq from='%s' id='create%d' to='%s' type='set'>"
1071 "<query xmlns='http://jabber.org/protocol/muc#owner'>"
1072 "<x xmlns='jabber:x:data' type='submit'/>"
1073 "</query></iq>\n";
1074 if (!write(fmt, jid.c_str(), msgId++, fromGid.c_str()))
1075 return false;
1076 }
1077 }
1078 }
1079 else
1080 {
1081 XmppEvent event(XmppEvent::EVENT_MESSAGE);
1082 event.setFrom(from);
1083 event.setData(body);
1084 event.setDOM(root);
1085 dispatchXmppEvent(event);
1086 }
1088 return true;
1089 }
1094 bool XmppClient::processPresence(Element *root)
1095 {
1097 DOMString fullJid = root->getTagAttribute("presence", "from");
1098 DOMString to = root->getTagAttribute("presence", "to");
1099 DOMString presenceStr = root->getTagAttribute("presence", "type");
1100 bool presence = true;
1101 if (presenceStr == "unavailable")
1102 presence = false;
1103 DOMString status = root->getTagValue("status");
1104 DOMString show = root->getTagValue("show");
1106 if (isGroupChat(root))
1107 {
1108 DOMString fromGid;
1109 DOMString fromNick;
1110 parseJid(fullJid, fromGid, fromNick);
1111 //printf("fromGid:%s fromNick:%s\n",
1112 // fromGid.c_str(), fromNick.c_str());
1113 DOMString item_jid = root->getTagAttribute("item", "jid");
1114 if (item_jid == jid) //Me
1115 {
1116 if (presence)
1117 {
1118 groupChatCreate(fromGid);
1119 groupChatUserAdd(fromGid, fromNick, "");
1120 groupChatUserShow(fromGid, fromNick, "available");
1122 XmppEvent event(XmppEvent::EVENT_MUC_JOIN);
1123 event.setGroup(fromGid);
1124 event.setFrom(fromNick);
1125 event.setPresence(presence);
1126 event.setShow(show);
1127 event.setStatus(status);
1128 dispatchXmppEvent(event);
1129 }
1130 else
1131 {
1132 groupChatDelete(fromGid);
1133 groupChatUserDelete(fromGid, fromNick);
1135 XmppEvent event(XmppEvent::EVENT_MUC_LEAVE);
1136 event.setGroup(fromGid);
1137 event.setFrom(fromNick);
1138 event.setPresence(presence);
1139 event.setShow(show);
1140 event.setStatus(status);
1141 dispatchXmppEvent(event);
1142 }
1143 }
1144 else // someone else
1145 {
1146 if (presence)
1147 {
1148 groupChatUserAdd(fromGid, fromNick, "");
1149 }
1150 else
1151 groupChatUserDelete(fromGid, fromNick);
1152 groupChatUserShow(fromGid, fromNick, show);
1153 XmppEvent event(XmppEvent::EVENT_MUC_PRESENCE);
1154 event.setGroup(fromGid);
1155 event.setFrom(fromNick);
1156 event.setPresence(presence);
1157 event.setShow(show);
1158 event.setStatus(status);
1159 dispatchXmppEvent(event);
1160 }
1161 }
1162 else
1163 {
1164 DOMString shortJid;
1165 DOMString dummy;
1166 parseJid(fullJid, shortJid, dummy);
1167 rosterShow(shortJid, show); //users in roster do not have resource
1169 XmppEvent event(XmppEvent::EVENT_PRESENCE);
1170 event.setFrom(fullJid);
1171 event.setPresence(presence);
1172 event.setShow(show);
1173 event.setStatus(status);
1174 dispatchXmppEvent(event);
1175 }
1177 return true;
1178 }
1182 bool XmppClient::processIq(Element *root)
1183 {
1184 DOMString from = root->getTagAttribute("iq", "from");
1185 DOMString id = root->getTagAttribute("iq", "id");
1186 DOMString type = root->getTagAttribute("iq", "type");
1187 DOMString xmlns = root->getTagAttribute("query", "xmlns");
1189 if (id.size()<1)
1190 return true;
1192 //Group chat
1193 if (strIndex(xmlns, "http://jabber.org/protocol/muc") >=0 )
1194 {
1195 printf("results of MUC query\n");
1196 }
1197 //printf("###IQ xmlns:%s\n", xmlns.c_str());
1199 //### FILE TRANSFERS
1200 DOMString siNamespace = "http://jabber.org/protocol/si";
1201 if (root->getTagAttribute("si", "xmlns") == siNamespace)
1202 {
1203 if (type == "set")
1204 {
1205 DOMString streamId = root->getTagAttribute("si", "id");
1206 DOMString fname = root->getTagAttribute("file", "name");
1207 DOMString sizeStr = root->getTagAttribute("file", "size");
1208 DOMString hash = root->getTagAttribute("file", "hash");
1209 XmppEvent event(XmppEvent::XmppEvent::EVENT_FILE_RECEIVE);
1210 event.setFrom(from);
1211 event.setIqId(id);
1212 event.setStreamId(streamId);
1213 event.setFileName(fname);
1214 event.setFileHash(hash);
1215 event.setFileSize(atol(sizeStr.c_str()));
1216 dispatchXmppEvent(event);
1217 }
1218 else //result
1219 {
1220 //printf("Got result\n");
1221 for (int i=0 ; i<fileSendCount ; i++)
1222 {
1223 XmppStream *outf = fileSends[i];
1224 if (outf->getIqId() == id &&
1225 from == outf->getPeerId())
1226 {
1227 if (type == "error")
1228 {
1229 outf->setState(STREAM_ERROR);
1230 error("user '%s' rejected file", from.c_str());
1231 return true;
1232 }
1233 else if (type == "result")
1234 {
1235 if (outf->getState() == STREAM_OPENING)
1236 {
1237 XmppEvent event(XmppEvent::XmppEvent::EVENT_FILE_ACCEPTED);
1238 event.setFrom(from);
1239 dispatchXmppEvent(event);
1240 outf->setState(STREAM_OPEN);
1241 }
1242 else if (outf->getState() == STREAM_CLOSING)
1243 {
1244 outf->setState(STREAM_CLOSED);
1245 }
1246 return true;
1247 }
1248 }
1249 }
1250 }
1251 return true;
1252 }
1255 //### IN-BAND BYTESTREAMS
1256 //### Incoming stream requests
1257 DOMString ibbNamespace = "http://jabber.org/protocol/ibb";
1258 if (root->getTagAttribute("open", "xmlns") == ibbNamespace)
1259 {
1260 DOMString streamId = root->getTagAttribute("open", "sid");
1261 XmppEvent event(XmppEvent::XmppEvent::EVENT_STREAM_RECEIVE_INIT);
1262 dispatchXmppEvent(event);
1263 if (streamId.size()>0)
1264 {
1265 for (int i=0 ; i<inputStreamCount ; i++)
1266 {
1267 XmppStream *ins = inputStreams[i];
1268 if (ins->getStreamId() == streamId)
1269 {
1270 ins->setState(STREAM_OPENING);
1271 ins->setIqId(id);
1272 return true;
1273 }
1274 }
1275 }
1276 return true;
1277 }
1278 else if (root->getTagAttribute("close", "xmlns") == ibbNamespace)
1279 {
1280 XmppEvent event(XmppEvent::XmppEvent::EVENT_STREAM_RECEIVE_CLOSE);
1281 dispatchXmppEvent(event);
1282 DOMString streamId = root->getTagAttribute("close", "sid");
1283 if (streamId.size()>0)
1284 {
1285 for (int i=0 ; i<inputStreamCount ; i++)
1286 {
1287 XmppStream *ins = inputStreams[i];
1288 if (ins->getStreamId() == streamId &&
1289 from == ins->getPeerId())
1290 {
1291 ins->setState(STREAM_CLOSING);
1292 ins->setIqId(id);
1293 return true;
1294 }
1295 }
1296 }
1297 return true;
1298 }
1299 //### Responses to outgoing requests
1300 for (int i=0 ; i<outputStreamCount ; i++)
1301 {
1302 XmppStream *outs = outputStreams[i];
1303 if (outs->getIqId() == id)
1304 {
1305 if (type == "error")
1306 {
1307 outs->setState(STREAM_ERROR);
1308 return true;
1309 }
1310 else if (type == "result")
1311 {
1312 if (outs->getState() == STREAM_OPENING)
1313 {
1314 outs->setState(STREAM_OPEN);
1315 }
1316 else if (outs->getState() == STREAM_CLOSING)
1317 {
1318 outs->setState(STREAM_CLOSED);
1319 }
1320 return true;
1321 }
1322 }
1323 }
1325 //###Normal Roster stuff
1326 if (root->getTagAttribute("query", "xmlns") == "jabber:iq:roster")
1327 {
1328 roster.clear();
1329 std::vector<Element *>elems = root->findElements("item");
1330 for (unsigned int i=0 ; i<elems.size() ; i++)
1331 {
1332 Element *item = elems[i];
1333 DOMString userJid = item->getAttribute("jid");
1334 DOMString name = item->getAttribute("name");
1335 DOMString subscription = item->getAttribute("subscription");
1336 DOMString group = item->getTagValue("group");
1337 //printf("jid:%s name:%s sub:%s group:%s\n", userJid.c_str(), name.c_str(),
1338 // subscription.c_str(), group.c_str());
1339 XmppUser user(userJid, name, subscription, group);
1340 roster.push_back(user);
1341 }
1342 XmppEvent event(XmppEvent::XmppEvent::EVENT_ROSTER);
1343 dispatchXmppEvent(event);
1344 }
1346 else if (id.find("regnew") != id.npos)
1347 {
1349 }
1351 else if (id.find("regpass") != id.npos)
1352 {
1353 std::vector<Element *> list = root->findElements("error");
1354 if (list.size()==0)
1355 {
1356 XmppEvent evt(XmppEvent::EVENT_REGISTRATION_CHANGE_PASS);
1357 evt.setTo(username);
1358 evt.setFrom(host);
1359 dispatchXmppEvent(evt);
1360 return true;
1361 }
1363 Element *errElem = list[0];
1364 DOMString errMsg = "Password change error: ";
1365 if (errElem->findElements("bad-request").size()>0)
1366 {
1367 errMsg.append("password change does not contain complete information");
1368 }
1369 else if (errElem->findElements("not-authorized").size()>0)
1370 {
1371 errMsg.append("server does not consider the channel safe "
1372 "enough to enable a password change");
1373 }
1374 else if (errElem->findElements("not-allowed").size()>0)
1375 {
1376 errMsg.append("server does not allow password changes");
1377 }
1378 else if (errElem->findElements("unexpected-request").size()>0)
1379 {
1380 errMsg.append(
1381 "IQ set does not contain a 'from' address because "
1382 "the entity is not registered with the server");
1383 }
1384 error((char *)errMsg.c_str());
1385 }
1387 else if (id.find("regcancel") != id.npos)
1388 {
1389 std::vector<Element *> list = root->findElements("error");
1390 if (list.size()==0)
1391 {
1392 XmppEvent evt(XmppEvent::EVENT_REGISTRATION_CANCEL);
1393 evt.setTo(username);
1394 evt.setFrom(host);
1395 dispatchXmppEvent(evt);
1396 return true;
1397 }
1399 Element *errElem = list[0];
1400 DOMString errMsg = "Registration cancel error: ";
1401 if (errElem->findElements("bad-request").size()>0)
1402 {
1403 errMsg.append("The <remove/> element was not the only child element of the <query/> element.");
1404 }
1405 else if (errElem->findElements("forbidden").size()>0)
1406 {
1407 errMsg.append("sender does not have sufficient permissions to cancel the registration");
1408 }
1409 else if (errElem->findElements("not-allowed").size()>0)
1410 {
1411 errMsg.append("not allowed to cancel registrations in-band");
1412 }
1413 else if (errElem->findElements("registration-required").size()>0)
1414 {
1415 errMsg.append("not previously registered");
1416 }
1417 else if (errElem->findElements("unexpected-request").size()>0)
1418 {
1419 errMsg.append(
1420 "IQ set does not contain a 'from' address because "
1421 "the entity is not registered with the server");
1422 }
1423 error((char *)errMsg.c_str());
1424 }
1426 return true;
1427 }
1431 bool XmppClient::receiveAndProcess()
1432 {
1433 if (!keepGoing)
1434 return false;
1436 Parser parser;
1438 DOMString recvBuf = readStanza();
1439 recvBuf = trim(recvBuf);
1440 if (recvBuf.size() < 1)
1441 return true;
1443 //Ugly hack. Apparently the first char can be dropped on timeouts
1444 //if (recvBuf[0] != '<')
1445 // recvBuf.insert(0, "<");
1447 status("RECV: %s", recvBuf.c_str());
1448 Element *root = parser.parse(recvBuf);
1449 if (!root)
1450 {
1451 printf("Bad elem\n");
1452 return true;
1453 }
1455 //#### MESSAGE
1456 std::vector<Element *>elems = root->findElements("message");
1457 if (elems.size()>0)
1458 {
1459 if (!processMessage(root))
1460 return false;
1461 }
1463 //#### PRESENCE
1464 elems = root->findElements("presence");
1465 if (elems.size()>0)
1466 {
1467 if (!processPresence(root))
1468 return false;
1469 }
1471 //#### INFO
1472 elems = root->findElements("iq");
1473 if (elems.size()>0)
1474 {
1475 if (!processIq(root))
1476 return false;
1477 }
1479 delete root;
1481 return true;
1482 }
1485 bool XmppClient::receiveAndProcessLoop()
1486 {
1487 keepGoing = true;
1488 while (true)
1489 {
1490 if (!keepGoing)
1491 {
1492 status("Abort requested");
1493 break;
1494 }
1495 if (!receiveAndProcess())
1496 return false;
1497 }
1498 return true;
1499 }
1504 //########################################################################
1505 //# SENDING
1506 //########################################################################
1509 bool XmppClient::write(char *fmt, ...)
1510 {
1511 va_list args;
1512 va_start(args,fmt);
1513 vsnprintf((char *)writeBuf, writeBufLen, fmt,args);
1514 va_end(args) ;
1515 status("SEND: %s", writeBuf);
1516 if (!sock->write((char *)writeBuf))
1517 {
1518 error("Cannot write to socket");
1519 return false;
1520 }
1521 return true;
1522 }
1529 //########################################################################
1530 //# R E G I S T R A T I O N
1531 //########################################################################
1533 /**
1534 * Perform JEP-077 In-Band Registration. Performed synchronously after SSL,
1535 * before authentication
1536 */
1537 bool XmppClient::inBandRegistrationNew()
1538 {
1539 Parser parser;
1541 char *fmt =
1542 "<iq type='get' id='regnew%d'>"
1543 "<query xmlns='jabber:iq:register'/>"
1544 "</iq>\n\n";
1545 if (!write(fmt, msgId++))
1546 return false;
1548 DOMString recbuf = readStanza();
1549 status("RECV reg: %s", recbuf.c_str());
1550 Element *elem = parser.parse(recbuf);
1551 //elem->print();
1553 //# does the entity send the "instructions" tag?
1554 std::vector<Element *> fields = elem->findElements("field");
1555 std::vector<DOMString> fnames;
1556 for (unsigned int i=0; i<fields.size() ; i++)
1557 {
1558 DOMString fname = fields[i]->getAttribute("var");
1559 if (fname == "FORM_TYPE")
1560 continue;
1561 fnames.push_back(fname);
1562 status("field name:%s", fname.c_str());
1563 }
1565 delete elem;
1567 if (fnames.size() == 0)
1568 {
1569 error("server did not offer registration");
1570 return false;
1571 }
1574 fmt =
1575 "<iq type='set' id='regnew%d'>"
1576 "<query xmlns='jabber:iq:register'>"
1577 "<username>%s</username>"
1578 "<password>%s</password>"
1579 "<email/><name/>"
1580 "</query>"
1581 "</iq>\n\n";
1582 if (!write(fmt, msgId++, toXml(username).c_str(),
1583 toXml(password).c_str() ))
1584 return false;
1587 recbuf = readStanza();
1588 status("RECV reg: %s", recbuf.c_str());
1589 elem = parser.parse(recbuf);
1590 //elem->print();
1592 std::vector<Element *> list = elem->findElements("error");
1593 if (list.size()>0)
1594 {
1595 Element *errElem = list[0];
1596 DOMString code = errElem->getAttribute("code");
1597 DOMString errMsg = "Registration error: ";
1598 if (code == "409")
1599 {
1600 errMsg.append("conflict with existing user name");
1601 }
1602 else if (code == "406")
1603 {
1604 errMsg.append("some registration information was not provided");
1605 }
1606 error((char *)errMsg.c_str());
1607 delete elem;
1608 return false;
1609 }
1611 delete elem;
1613 XmppEvent evt(XmppEvent::EVENT_REGISTRATION_NEW);
1614 evt.setTo(username);
1615 evt.setFrom(host);
1616 dispatchXmppEvent(evt);
1618 return true;
1619 }
1622 /**
1623 * Perform JEP-077 In-Band Registration. Performed asynchronously, after login.
1624 * See processIq() for response handling.
1625 */
1626 bool XmppClient::inBandRegistrationChangePassword(const DOMString &newpassword)
1627 {
1628 Parser parser;
1630 //# Let's try it form-style to allow the common old/new password thing
1631 char *fmt =
1632 "<iq type='set' id='regpass%d' from='%s' to='%s'>"
1633 " <query xmlns='jabber:iq:register'>"
1634 " <x xmlns='jabber:x:data' type='form'>"
1635 " <field type='hidden' var='FORM_TYPE'>"
1636 " <value>jabber:iq:register:changepassword</value>"
1637 " </field>"
1638 " <field type='text-single' var='username'>"
1639 " <value>%s</value>"
1640 " </field>"
1641 " <field type='text-private' var='old_password'>"
1642 " <value>%s</value>"
1643 " </field>"
1644 " <field type='text-private' var='password'>"
1645 " <value>%s</value>"
1646 " </field>"
1647 " </x>"
1648 " </query>"
1649 "</iq>\n\n";
1651 if (!write(fmt, msgId++, jid.c_str(), host.c_str(),
1652 username.c_str(), password.c_str(), newpassword.c_str()))
1653 return false;
1655 return true;
1657 }
1660 /**
1661 * Perform JEP-077 In-Band Registration. Performed asynchronously, after login.
1662 * See processIq() for response handling.
1663 */
1664 bool XmppClient::inBandRegistrationCancel()
1665 {
1666 Parser parser;
1668 char *fmt =
1669 "<iq type='set' id='regcancel%d' from='%s'>"
1670 "<query xmlns='jabber:iq:register'><remove/></query>"
1671 "</iq>\n\n";
1672 if (!write(fmt, msgId++, jid.c_str()))
1673 return false;
1675 return true;
1676 }
1682 //########################################################################
1683 //# A U T H E N T I C A T E
1684 //########################################################################
1686 bool XmppClient::iqAuthenticate(const DOMString &streamId)
1687 {
1688 Parser parser;
1690 char *fmt =
1691 "<iq type='get' to='%s' id='auth%d'>"
1692 "<query xmlns='jabber:iq:auth'><username>%s</username></query>"
1693 "</iq>\n";
1694 if (!write(fmt, realm.c_str(), msgId++, username.c_str()))
1695 return false;
1697 DOMString recbuf = readStanza();
1698 //printf("iq received: '%s'\n", recbuf.c_str());
1699 Element *elem = parser.parse(recbuf);
1700 //elem->print();
1701 DOMString iqType = elem->getTagAttribute("iq", "type");
1702 //printf("##iqType:%s\n", iqType.c_str());
1703 delete elem;
1705 if (iqType != "result")
1706 {
1707 error("error:server does not allow login");
1708 return false;
1709 }
1711 bool digest = true;
1712 if (digest)
1713 {
1714 //## Digest authentication
1715 DOMString digest = streamId;
1716 digest.append(password);
1717 digest = Sha1::hashHex((unsigned char *)digest.c_str(), digest.size());
1718 //printf("digest:%s\n", digest.c_str());
1719 fmt =
1720 "<iq type='set' id='auth%d'>"
1721 "<query xmlns='jabber:iq:auth'>"
1722 "<username>%s</username>"
1723 "<digest>%s</digest>"
1724 "<resource>%s</resource>"
1725 "</query>"
1726 "</iq>\n";
1727 if (!write(fmt, msgId++, username.c_str(),
1728 digest.c_str(), resource.c_str()))
1729 return false;
1730 }
1731 else
1732 {
1734 //## Plaintext authentication
1735 fmt =
1736 "<iq type='set' id='auth%d'>"
1737 "<query xmlns='jabber:iq:auth'>"
1738 "<username>%s</username>"
1739 "<password>%s</password>"
1740 "<resource>%s</resource>"
1741 "</query>"
1742 "</iq>\n";
1743 if (!write(fmt, msgId++, username.c_str(),
1744 password.c_str(), resource.c_str()))
1745 return false;
1746 }
1748 recbuf = readStanza();
1749 //printf("iq received: '%s'\n", recbuf.c_str());
1750 elem = parser.parse(recbuf);
1751 //elem->print();
1752 iqType = elem->getTagAttribute("iq", "type");
1753 //printf("##iqType:%s\n", iqType.c_str());
1754 delete elem;
1756 if (iqType != "result")
1757 {
1758 error("server does not allow login");
1759 return false;
1760 }
1762 return true;
1763 }
1766 /**
1767 * Parse a sasl challenge to retrieve all of its key=value pairs
1768 */
1769 static bool saslParse(const DOMString &s,
1770 std::map<DOMString, DOMString> &vals)
1771 {
1773 vals.clear();
1775 int p = 0;
1776 int siz = s.size();
1778 while (p < siz)
1779 {
1780 DOMString key;
1781 DOMString value;
1782 char ch = '\0';
1784 //# Parse key
1785 while (p<siz)
1786 {
1787 ch = s[p++];
1788 if (ch == '=')
1789 break;
1790 key.push_back(ch);
1791 }
1793 //No value?
1794 if (ch != '=')
1795 break;
1797 //# Parse value
1798 bool quoted = false;
1799 while (p<siz)
1800 {
1801 ch = s[p++];
1802 if (ch == '"')
1803 quoted = !quoted;
1804 else if (ch == ',' && !quoted)
1805 break;
1806 else
1807 value.push_back(ch);
1808 }
1810 //printf("# Key: '%s' Value: '%s'\n", key.c_str(), value.c_str());
1811 vals[key] = value;
1812 if (ch != ',')
1813 break;
1814 }
1816 return true;
1817 }
1821 /**
1822 * Attempt suthentication using the MD5 SASL mechanism
1823 */
1824 bool XmppClient::saslMd5Authenticate()
1825 {
1826 Parser parser;
1827 char *fmt =
1828 "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' "
1829 "mechanism='DIGEST-MD5'/>\n";
1830 if (!write(fmt))
1831 return false;
1833 DOMString recbuf = readStanza();
1834 status("challenge received: '%s'", recbuf.c_str());
1835 Element *elem = parser.parse(recbuf);
1836 //elem->print();
1837 DOMString b64challenge = elem->getTagValue("challenge");
1838 delete elem;
1840 if (b64challenge.size() < 1)
1841 {
1842 error("login: no SASL challenge offered by server");
1843 return false;
1844 }
1845 DOMString challenge = Base64Decoder::decodeToString(b64challenge);
1846 status("md5 challenge:'%s'", challenge.c_str());
1848 std::map<DOMString, DOMString> attrs;
1849 if (!saslParse(challenge, attrs))
1850 {
1851 error("login: error parsing SASL challenge");
1852 return false;
1853 }
1855 DOMString nonce = attrs["nonce"];
1856 if (nonce.size()==0)
1857 {
1858 error("login: no SASL nonce sent by server");
1859 return false;
1860 }
1862 DOMString realm = attrs["realm"];
1863 if (realm.size()==0)
1864 {
1865 error("login: no SASL realm sent by server");
1866 return false;
1867 }
1869 status("SASL recv nonce: '%s' realm:'%s'\n", nonce.c_str(), realm.c_str());
1871 char idBuf[10];
1872 snprintf(idBuf, 9, "%dsasl", msgId++);
1873 DOMString cnonce = Sha1::hashHex((unsigned char *)idBuf, 7);
1874 DOMString authzid = username; authzid.append("@"); authzid.append(host);
1875 DOMString digest_uri = "xmpp/"; digest_uri.append(host);
1877 //## Make A1
1878 Md5 md5;
1879 md5.append(username);
1880 md5.append(":");
1881 md5.append(realm);
1882 md5.append(":");
1883 md5.append(password);
1884 unsigned char a1tmp[16];
1885 md5.finish(a1tmp);
1886 md5.init();
1887 md5.append(a1tmp, 16);
1888 md5.append(":");
1889 md5.append(nonce);
1890 md5.append(":");
1891 md5.append(cnonce);
1892 //RFC2831 says authzid is optional. Wildfire has trouble with authzid's
1893 //md5.append(":");
1894 //md5.append(authzid);
1895 md5.append("");
1896 DOMString a1 = md5.finishHex();
1897 status("##a1:'%s'", a1.c_str());
1899 //# Make A2
1900 md5.init();
1901 md5.append("AUTHENTICATE:");
1902 md5.append(digest_uri);
1903 DOMString a2 = md5.finishHex();
1904 status("##a2:'%s'", a2.c_str());
1906 //# Now make the response
1907 md5.init();
1908 md5.append(a1);
1909 md5.append(":");
1910 md5.append(nonce);
1911 md5.append(":");
1912 md5.append("00000001");//nc
1913 md5.append(":");
1914 md5.append(cnonce);
1915 md5.append(":");
1916 md5.append("auth");//qop
1917 md5.append(":");
1918 md5.append(a2);
1919 DOMString response = md5.finishHex();
1921 DOMString resp;
1922 resp.append("username=\""); resp.append(username); resp.append("\",");
1923 resp.append("realm=\""); resp.append(realm); resp.append("\",");
1924 resp.append("nonce=\""); resp.append(nonce); resp.append("\",");
1925 resp.append("cnonce=\""); resp.append(cnonce); resp.append("\",");
1926 resp.append("nc=00000001,qop=auth,");
1927 resp.append("digest-uri=\""); resp.append(digest_uri); resp.append("\"," );
1928 //resp.append("authzid=\""); resp.append(authzid); resp.append("\",");
1929 resp.append("response="); resp.append(response); resp.append(",");
1930 resp.append("charset=utf-8");
1931 status("sending response:'%s'", resp.c_str());
1932 resp = Base64Encoder::encode(resp);
1933 status("base64 response:'%s'", resp.c_str());
1934 fmt =
1935 "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response>\n";
1936 if (!write(fmt, resp.c_str()))
1937 return false;
1939 recbuf = readStanza();
1940 status("server says:: '%s'", recbuf.c_str());
1941 elem = parser.parse(recbuf);
1942 //elem->print();
1943 b64challenge = elem->getTagValue("challenge");
1944 delete elem;
1946 if (b64challenge.size() < 1)
1947 {
1948 error("login: no second SASL challenge offered by server");
1949 return false;
1950 }
1952 challenge = Base64Decoder::decodeToString(b64challenge);
1953 status("md5 challenge: '%s'", challenge.c_str());
1955 if (!saslParse(challenge, attrs))
1956 {
1957 error("login: error parsing SASL challenge");
1958 return false;
1959 }
1961 DOMString rspauth = attrs["rspauth"];
1962 if (rspauth.size()==0)
1963 {
1964 error("login: no SASL respauth sent by server\n");
1965 return false;
1966 }
1968 fmt =
1969 "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>\n";
1970 if (!write(fmt))
1971 return false;
1973 recbuf = readStanza();
1974 status("SASL recv: '%s", recbuf.c_str());
1975 elem = parser.parse(recbuf);
1976 //elem->print();
1977 b64challenge = elem->getTagValue("challenge");
1978 bool success = (elem->findElements("success").size() > 0);
1979 delete elem;
1981 return success;
1982 }
1986 /**
1987 * Attempt to authentication using the SASL PLAIN mechanism. This
1988 * is used most commonly my Google Talk.
1989 */
1990 bool XmppClient::saslPlainAuthenticate()
1991 {
1992 Parser parser;
1994 DOMString id = username;
1995 //id.append("@");
1996 //id.append(host);
1997 Base64Encoder encoder;
1998 encoder.append('\0');
1999 encoder.append(id);
2000 encoder.append('\0');
2001 encoder.append(password);
2002 DOMString base64Auth = encoder.finish();
2003 //printf("authbuf:%s\n", base64Auth.c_str());
2005 char *fmt =
2006 "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' "
2007 "mechanism='PLAIN'>%s</auth>\n";
2008 if (!write(fmt, base64Auth.c_str()))
2009 return false;
2010 DOMString recbuf = readStanza();
2011 status("challenge received: '%s'", recbuf.c_str());
2012 Element *elem = parser.parse(recbuf);
2014 bool success = (elem->findElements("success").size() > 0);
2015 delete elem;
2017 return success;
2018 }
2022 /**
2023 * Handshake with SASL, and use one of its offered mechanisms to
2024 * authenticate.
2025 */
2026 bool XmppClient::saslAuthenticate()
2027 {
2028 Parser parser;
2030 DOMString recbuf = readStanza();
2031 status("RECV: '%s'\n", recbuf.c_str());
2032 Element *elem = parser.parse(recbuf);
2033 //elem->print();
2035 //Check for starttls
2036 bool wantStartTls = false;
2037 if (elem->findElements("starttls").size() > 0)
2038 {
2039 wantStartTls = true;
2040 if (elem->findElements("required").size() > 0)
2041 status("login: STARTTLS required");
2042 else
2043 status("login: STARTTLS available");
2044 }
2046 if (wantStartTls && !sock->getEnableSSL())
2047 {
2048 delete elem;
2049 char *fmt =
2050 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n";
2051 if (!write(fmt))
2052 return false;
2053 recbuf = readStanza();
2054 status("RECV: '%s'\n", recbuf.c_str());
2055 elem = parser.parse(recbuf);
2056 if (elem->getTagAttribute("proceed", "xmlns").size()<1)
2057 {
2058 error("Server rejected TLS negotiation");
2059 disconnect();
2060 return false;
2061 }
2062 delete elem;
2063 if (!sock->startTls())
2064 {
2065 error("Could not start TLS");
2066 disconnect();
2067 return false;
2068 }
2070 fmt =
2071 "<stream:stream xmlns='jabber:client' "
2072 "xmlns:stream='http://etherx.jabber.org/streams' "
2073 "to='%s' version='1.0'>\n\n";
2074 if (!write(fmt, realm.c_str()))
2075 return false;
2077 recbuf = readStanza();
2078 status("RECVx: '%s'", recbuf.c_str());
2079 recbuf.append("</stream:stream>");
2080 elem = parser.parse(recbuf);
2081 bool success =
2082 (elem->getTagAttribute("stream:stream", "id").size()>0);
2083 if (!success)
2084 {
2085 error("STARTTLS negotiation failed");
2086 disconnect();
2087 return false;
2088 }
2089 delete elem;
2090 recbuf = readStanza();
2091 status("RECV: '%s'\n", recbuf.c_str());
2092 elem = parser.parse(recbuf);
2094 XmppEvent event(XmppEvent::EVENT_SSL_STARTED);
2095 dispatchXmppEvent(event);
2096 }
2098 //register, if user requests
2099 if (doRegister)
2100 {
2101 if (!inBandRegistrationNew())
2102 return false;
2103 }
2105 //check for sasl authentication mechanisms
2106 std::vector<Element *> elems =
2107 elem->findElements("mechanism");
2108 if (elems.size() < 1)
2109 {
2110 error("login: no SASL mechanism offered by server");
2111 return false;
2112 }
2113 bool md5Found = false;
2114 bool plainFound = false;
2115 for (unsigned int i=0 ; i<elems.size() ; i++)
2116 {
2117 DOMString mech = elems[i]->getValue();
2118 if (mech == "DIGEST-MD5")
2119 {
2120 status("MD5 authentication offered");
2121 md5Found = true;
2122 }
2123 else if (mech == "PLAIN")
2124 {
2125 status("PLAIN authentication offered");
2126 plainFound = true;
2127 }
2128 }
2129 delete elem;
2131 bool success = false;
2132 if (md5Found)
2133 {
2134 success = saslMd5Authenticate();
2135 }
2136 else if (plainFound)
2137 {
2138 success = saslPlainAuthenticate();
2139 }
2140 else
2141 {
2142 error("not able to handle sasl authentication mechanisms");
2143 return false;
2144 }
2146 if (success)
2147 status("###### SASL authentication success\n");
2148 else
2149 error("###### SASL authentication failure\n");
2151 return success;
2152 }
2159 //########################################################################
2160 //# CONNECT
2161 //########################################################################
2164 /**
2165 * Check if we are connected, and fail with an error if we are not
2166 */
2167 bool XmppClient::checkConnect()
2168 {
2169 if (!connected)
2170 {
2171 XmppEvent evt(XmppEvent::EVENT_ERROR);
2172 evt.setData("Attempted operation while disconnected");
2173 dispatchXmppEvent(evt);
2174 return false;
2175 }
2176 return true;
2177 }
2181 /**
2182 * Create an XMPP session with a server. This
2183 * is basically the transport layer of XMPP.
2184 */
2185 bool XmppClient::createSession()
2186 {
2188 Parser parser;
2189 if (port==443 || port==5223)
2190 sock->enableSSL(true);
2191 if (!sock->connect(host, port))
2192 {
2193 return false;
2194 }
2196 if (sock->getEnableSSL())
2197 {
2198 XmppEvent event(XmppEvent::EVENT_SSL_STARTED);
2199 dispatchXmppEvent(event);
2200 }
2202 char *fmt =
2203 "<stream:stream "
2204 "to='%s' "
2205 "xmlns='jabber:client' "
2206 "xmlns:stream='http://etherx.jabber.org/streams' "
2207 "version='1.0'>\n\n";
2208 if (!write(fmt, realm.c_str()))
2209 return false;
2211 DOMString recbuf = readStanza();
2212 //printf("received: '%s'\n", recbuf.c_str());
2213 recbuf.append("</stream:stream>");
2214 Element *elem = parser.parse(recbuf);
2215 //elem->print();
2216 bool useSasl = false;
2217 DOMString streamId = elem->getTagAttribute("stream:stream", "id");
2218 //printf("### StreamID: %s\n", streamId.c_str());
2219 DOMString streamVersion = elem->getTagAttribute("stream:stream", "version");
2220 if (streamVersion == "1.0")
2221 useSasl = true;
2223 if (useSasl)
2224 {
2225 if (!saslAuthenticate())
2226 return false;
2227 fmt =
2228 "<stream:stream "
2229 "to='%s' "
2230 "xmlns='jabber:client' "
2231 "xmlns:stream='http://etherx.jabber.org/streams' "
2232 "version='1.0'>\n\n";
2234 if (!write(fmt, realm.c_str()))
2235 return false;
2236 recbuf = readStanza();
2237 recbuf.append("</stream:stream>\n");
2238 //printf("now server says:: '%s'\n", recbuf.c_str());
2239 elem = parser.parse(recbuf);
2240 //elem->print();
2241 delete elem;
2243 recbuf = readStanza();
2244 //printf("now server says:: '%s'\n", recbuf.c_str());
2245 elem = parser.parse(recbuf);
2246 bool hasBind = (elem->findElements("bind").size() > 0);
2247 //elem->print();
2248 delete elem;
2250 if (!hasBind)
2251 {
2252 error("no binding provided by server");
2253 return false;
2254 }
2257 }
2258 else // not SASL
2259 {
2260 if (!iqAuthenticate(streamId))
2261 return false;
2262 }
2265 //### Resource binding
2266 fmt =
2267 "<iq type='set' id='bind%d'>"
2268 "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
2269 "<resource>%s</resource>"
2270 "</bind></iq>\n";
2271 if (!write(fmt, msgId++, resource.c_str()))
2272 return false;
2274 recbuf = readStanza();
2275 status("bind result: '%s'", recbuf.c_str());
2276 elem = parser.parse(recbuf);
2277 //elem->print();
2278 DOMString bindType = elem->getTagAttribute("iq", "type");
2279 //printf("##bindType:%s\n", bindType.c_str());
2280 delete elem;
2282 if (bindType != "result")
2283 {
2284 error("no binding with server failed");
2285 return false;
2286 }
2288 fmt =
2289 "<iq type='set' id='sess%d'>"
2290 "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>"
2291 "</iq>\n";
2292 if (!write(fmt, msgId++))
2293 return false;
2295 recbuf = readStanza();
2296 status("session received: '%s'", recbuf.c_str());
2297 elem = parser.parse(recbuf);
2298 //elem->print();
2299 DOMString sessionType = elem->getTagAttribute("iq", "type");
2300 //printf("##sessionType:%s\n", sessionType.c_str());
2301 delete elem;
2303 if (sessionType != "result")
2304 {
2305 error("no session provided by server");
2306 return false;
2307 }
2309 //printf("########## COOL #########\n");
2310 //Now that we are bound, we have a valid JID
2311 jid = username;
2312 jid.append("@");
2313 jid.append(realm);
2314 jid.append("/");
2315 jid.append(resource);
2317 //We are now done with the synchronous handshaking. Let's go into
2318 //async mode
2320 fmt =
2321 "<iq type='get' id='roster%d'><query xmlns='jabber:iq:roster'/></iq>\n";
2322 if (!write(fmt, msgId++))
2323 return false;
2325 fmt =
2326 "<iq type='get' id='discoItems%d' to='%s'>"
2327 "<query xmlns='http://jabber.org/protocol/disco#items'/></iq>\n";
2328 if (!write(fmt, msgId++, realm.c_str()))
2329 return false;
2331 fmt =
2332 "<iq type='get' id='discoInfo%d' to='conference.%s'>"
2333 "<query xmlns='http://jabber.org/protocol/disco#info'/></iq>\n";
2334 if (!write(fmt, msgId++, realm.c_str()))
2335 return false;
2337 fmt =
2338 "<presence/>\n";
2339 if (!write(fmt))
2340 return false;
2342 /*
2343 recbuf = readStanza();
2344 status("stream received: '%s'", recbuf.c_str());
2345 elem = parser.parse(recbuf);
2346 //elem->print();
2347 delete elem;
2348 */
2350 //We are now logged in
2351 status("Connected");
2352 connected = true;
2353 XmppEvent evt(XmppEvent::EVENT_CONNECTED);
2354 evt.setData(host);
2355 dispatchXmppEvent(evt);
2356 //Thread::sleep(1000000);
2358 sock->setReceiveTimeout(1000);
2359 ReceiverThread runner(*this);
2360 Thread thread(runner);
2361 thread.start();
2363 return true;
2364 }
2368 /**
2369 * Public call to connect
2370 */
2371 bool XmppClient::connect()
2372 {
2373 if (!createSession())
2374 {
2375 disconnect();
2376 return false;
2377 }
2378 return true;
2379 }
2382 /**
2383 * Public call to connect
2384 */
2385 bool XmppClient::connect(DOMString hostArg, int portArg,
2386 DOMString usernameArg,
2387 DOMString passwordArg,
2388 DOMString resourceArg)
2389 {
2390 host = hostArg;
2391 port = portArg;
2392 password = passwordArg;
2393 resource = resourceArg;
2395 //parse this one
2396 setUsername(usernameArg);
2398 bool ret = connect();
2399 return ret;
2400 }
2404 /**
2405 * Public call to disconnect
2406 */
2407 bool XmppClient::disconnect()
2408 {
2409 if (connected)
2410 {
2411 char *fmt =
2412 "<presence from='%s' type='unavailable'/>\n";
2413 write(fmt, jid.c_str());
2414 }
2415 keepGoing = false;
2416 connected = false;
2417 Thread::sleep(2000); //allow receiving thread to quit
2418 sock->disconnect();
2419 roster.clear();
2420 groupChatsClear();
2421 XmppEvent event(XmppEvent::EVENT_DISCONNECTED);
2422 event.setData(host);
2423 dispatchXmppEvent(event);
2424 return true;
2425 }
2431 //########################################################################
2432 //# ROSTER
2433 //########################################################################
2435 /**
2436 * Add an XMPP id to your roster
2437 */
2438 bool XmppClient::rosterAdd(const DOMString &rosterGroup,
2439 const DOMString &otherJid,
2440 const DOMString &name)
2441 {
2442 if (!checkConnect())
2443 return false;
2444 char *fmt =
2445 "<iq from='%s' type='set' id='roster_%d'>"
2446 "<query xmlns='jabber:iq:roster'>"
2447 "<item jid='%s' name='%s'><group>%s</group></item>"
2448 "</query></iq>\n";
2449 if (!write(fmt, jid.c_str(), msgId++, otherJid.c_str(),
2450 name.c_str(), rosterGroup.c_str()))
2451 {
2452 return false;
2453 }
2454 return true;
2455 }
2459 /**
2460 * Delete an XMPP id from your roster.
2461 */
2462 bool XmppClient::rosterDelete(const DOMString &otherJid)
2463 {
2464 if (!checkConnect())
2465 return false;
2466 char *fmt =
2467 "<iq from='%s' type='set' id='roster_%d'>"
2468 "<query xmlns='jabber:iq:roster'>"
2469 "<item jid='%s' subscription='remove'><group>%s</group></item>"
2470 "</query></iq>\n";
2471 if (!write(fmt, jid.c_str(), msgId++, otherJid.c_str()))
2472 {
2473 return false;
2474 }
2475 return true;
2476 }
2479 /**
2480 * Comparison method for sort() call below
2481 */
2482 static bool xmppRosterCompare(const XmppUser& p1, const XmppUser& p2)
2483 {
2484 DOMString s1 = p1.group;
2485 DOMString s2 = p2.group;
2486 for (unsigned int len=0 ; len<s1.size() && len<s2.size() ; len++)
2487 {
2488 int comp = tolower(s1[len]) - tolower(s2[len]);
2489 if (comp)
2490 return (comp<0);
2491 }
2493 s1 = p1.jid;
2494 s2 = p2.jid;
2495 for (unsigned int len=0 ; len<s1.size() && len<s2.size() ; len++)
2496 {
2497 int comp = tolower(s1[len]) - tolower(s2[len]);
2498 if (comp)
2499 return (comp<0);
2500 }
2501 return false;
2502 }
2506 /**
2507 * Sort and return the roster that has just been reported by
2508 * an XmppEvent::EVENT_ROSTER event.
2509 */
2510 std::vector<XmppUser> XmppClient::getRoster()
2511 {
2512 std::vector<XmppUser> ros = roster;
2513 std::sort(ros.begin(), ros.end(), xmppRosterCompare);
2514 return ros;
2515 }
2518 /**
2519 *
2520 */
2521 void XmppClient::rosterShow(const DOMString &jid, const DOMString &show)
2522 {
2523 DOMString theShow = show;
2524 if (theShow == "")
2525 theShow = "available";
2527 std::vector<XmppUser>::iterator iter;
2528 for (iter=roster.begin() ; iter != roster.end() ; iter++)
2529 {
2530 if (iter->jid == jid)
2531 iter->show = theShow;
2532 }
2533 }
2540 //########################################################################
2541 //# CHAT (individual)
2542 //########################################################################
2544 /**
2545 * Send a message to an xmpp jid
2546 */
2547 bool XmppClient::message(const DOMString &user, const DOMString &subj,
2548 const DOMString &msg)
2549 {
2550 if (!checkConnect())
2551 return false;
2553 DOMString xmlSubj = toXml(subj);
2554 DOMString xmlMsg = toXml(msg);
2556 if (xmlSubj.size() > 0)
2557 {
2558 char *fmt =
2559 "<message from='%s' to='%s' type='chat'>"
2560 "<subject>%s</subject><body>%s</body></message>\n";
2561 if (!write(fmt, jid.c_str(), user.c_str(),
2562 xmlSubj.c_str(), xmlMsg.c_str()))
2563 return false;
2564 }
2565 else
2566 {
2567 char *fmt =
2568 "<message from='%s' to='%s'>"
2569 "<body>%s</body></message>\n";
2570 if (!write(fmt, jid.c_str(), user.c_str(), xmlMsg.c_str()))
2571 return false;
2572 }
2573 return true;
2574 }
2578 /**
2579 *
2580 */
2581 bool XmppClient::message(const DOMString &user, const DOMString &msg)
2582 {
2583 return message(user, "", msg);
2584 }
2588 /**
2589 *
2590 */
2591 bool XmppClient::presence(const DOMString &presence)
2592 {
2593 if (!checkConnect())
2594 return false;
2596 DOMString xmlPres = toXml(presence);
2598 char *fmt =
2599 "<presence from='%s'><show>%s</show></presence>\n";
2600 if (!write(fmt, jid.c_str(), xmlPres.c_str()))
2601 return false;
2602 return true;
2603 }
2610 //########################################################################
2611 //# GROUP CHAT
2612 //########################################################################
2614 /**
2615 *
2616 */
2617 bool XmppClient::groupChatCreate(const DOMString &groupJid)
2618 {
2619 std::vector<XmppGroupChat *>::iterator iter;
2620 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2621 {
2622 if ((*iter)->getGroupJid() == groupJid)
2623 {
2624 error("Group chat '%s' already exists", groupJid.c_str());
2625 return false;
2626 }
2627 }
2628 XmppGroupChat *chat = new XmppGroupChat(groupJid);
2629 groupChats.push_back(chat);
2630 return true;
2631 }
2635 /**
2636 *
2637 */
2638 void XmppClient::groupChatDelete(const DOMString &groupJid)
2639 {
2640 std::vector<XmppGroupChat *>::iterator iter;
2641 for (iter=groupChats.begin() ; iter!=groupChats.end() ; )
2642 {
2643 XmppGroupChat *chat = *iter;
2644 if (chat->getGroupJid() == groupJid)
2645 {
2646 iter = groupChats.erase(iter);
2647 delete chat;
2648 }
2649 else
2650 iter++;
2651 }
2652 }
2656 /**
2657 *
2658 */
2659 bool XmppClient::groupChatExists(const DOMString &groupJid)
2660 {
2661 std::vector<XmppGroupChat *>::iterator iter;
2662 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2663 if ((*iter)->getGroupJid() == groupJid)
2664 return true;
2665 return false;
2666 }
2670 /**
2671 *
2672 */
2673 void XmppClient::groupChatsClear()
2674 {
2675 std::vector<XmppGroupChat *>::iterator iter;
2676 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2677 delete (*iter);
2678 groupChats.clear();
2679 }
2684 /**
2685 *
2686 */
2687 void XmppClient::groupChatUserAdd(const DOMString &groupJid,
2688 const DOMString &nick,
2689 const DOMString &jid)
2690 {
2691 std::vector<XmppGroupChat *>::iterator iter;
2692 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2693 {
2694 if ((*iter)->getGroupJid() == groupJid)
2695 {
2696 (*iter)->userAdd(nick, jid);
2697 }
2698 }
2699 }
2703 /**
2704 *
2705 */
2706 void XmppClient::groupChatUserShow(const DOMString &groupJid,
2707 const DOMString &nick,
2708 const DOMString &show)
2709 {
2710 std::vector<XmppGroupChat *>::iterator iter;
2711 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2712 {
2713 if ((*iter)->getGroupJid() == groupJid)
2714 {
2715 (*iter)->userShow(nick, show);
2716 }
2717 }
2718 }
2723 /**
2724 *
2725 */
2726 void XmppClient::groupChatUserDelete(const DOMString &groupJid,
2727 const DOMString &nick)
2728 {
2729 std::vector<XmppGroupChat *>::iterator iter;
2730 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2731 {
2732 if ((*iter)->getGroupJid() == groupJid)
2733 {
2734 (*iter)->userDelete(nick);
2735 }
2736 }
2737 }
2741 /**
2742 * Comparison method for the sort() below
2743 */
2744 static bool xmppUserCompare(const XmppUser& p1, const XmppUser& p2)
2745 {
2746 DOMString s1 = p1.nick;
2747 DOMString s2 = p2.nick;
2748 int comp = 0;
2749 for (unsigned int len=0 ; len<s1.size() && len<s2.size() ; len++)
2750 {
2751 comp = tolower(s1[len]) - tolower(s2[len]);
2752 if (comp)
2753 break;
2754 }
2755 return (comp<0);
2756 }
2760 /**
2761 * Return the user list for the named group
2762 */
2763 std::vector<XmppUser> XmppClient::groupChatGetUserList(
2764 const DOMString &groupJid)
2765 {
2766 if (!checkConnect())
2767 {
2768 std::vector<XmppUser> dummy;
2769 return dummy;
2770 }
2772 std::vector<XmppGroupChat *>::iterator iter;
2773 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2774 {
2775 if ((*iter)->getGroupJid() == groupJid )
2776 {
2777 std::vector<XmppUser> uList = (*iter)->getUserList();
2778 std::sort(uList.begin(), uList.end(), xmppUserCompare);
2779 return uList;
2780 }
2781 }
2782 std::vector<XmppUser> dummy;
2783 return dummy;
2784 }
2789 /**
2790 * Try to join a group
2791 */
2792 bool XmppClient::groupChatJoin(const DOMString &groupJid,
2793 const DOMString &nick,
2794 const DOMString &pass)
2795 {
2796 if (!checkConnect())
2797 return false;
2799 DOMString user = nick;
2800 if (user.size()<1)
2801 user = username;
2803 char *fmt =
2804 "<presence to='%s/%s'>"
2805 "<x xmlns='http://jabber.org/protocol/muc'/></presence>\n";
2806 if (!write(fmt, groupJid.c_str(), user.c_str()))
2807 return false;
2808 return true;
2809 }
2814 /**
2815 * Leave a group
2816 */
2817 bool XmppClient::groupChatLeave(const DOMString &groupJid,
2818 const DOMString &nick)
2819 {
2820 if (!checkConnect())
2821 return false;
2823 DOMString user = nick;
2824 if (user.size()<1)
2825 user = username;
2827 char *fmt =
2828 "<presence to='%s/%s' type='unavailable'>"
2829 "<x xmlns='http://jabber.org/protocol/muc'/></presence>\n";
2830 if (!write(fmt, groupJid.c_str(), user.c_str()))
2831 return false;
2832 return true;
2833 }
2838 /**
2839 * Send a message to a group
2840 */
2841 bool XmppClient::groupChatMessage(const DOMString &groupJid,
2842 const DOMString &msg)
2843 {
2844 if (!checkConnect())
2845 {
2846 return false;
2847 }
2849 DOMString xmlMsg = toXml(msg);
2851 char *fmt =
2852 "<message from='%s' to='%s' type='groupchat'>"
2853 "<body>%s</body></message>\n";
2854 if (!write(fmt, jid.c_str(), groupJid.c_str(), xmlMsg.c_str()))
2855 return false;
2856 return true;
2857 }
2862 /**
2863 * Send a message to an individual in a group
2864 */
2865 bool XmppClient::groupChatPrivateMessage(const DOMString &groupJid,
2866 const DOMString &toNick,
2867 const DOMString &msg)
2868 {
2869 if (!checkConnect())
2870 return false;
2872 DOMString xmlMsg = toXml(msg);
2874 char *fmt =
2875 "<message from='%s' to='%s/%s' type='chat'>"
2876 "<body>%s</body></message>\n";
2877 if (!write(fmt, jid.c_str(), groupJid.c_str(),
2878 toNick.c_str(), xmlMsg.c_str()))
2879 return false;
2880 return true;
2881 }
2886 /**
2887 * Change your presence within a group
2888 */
2889 bool XmppClient::groupChatPresence(const DOMString &groupJid,
2890 const DOMString &myNick,
2891 const DOMString &presence)
2892 {
2893 if (!checkConnect())
2894 return false;
2896 DOMString user = myNick;
2897 if (user.size()<1)
2898 user = username;
2900 DOMString xmlPresence = toXml(presence);
2902 char *fmt =
2903 "<presence from='%s' to='%s/%s' type='unavailable'>"
2904 "<x xmlns='http://jabber.org/protocol/muc'/></presence>\n";
2905 if (!write(fmt, jid.c_str(), groupJid.c_str(),
2906 user.c_str(), xmlPresence.c_str()))
2907 return true;
2908 return true;
2909 }
2915 //########################################################################
2916 //# S T R E A M S
2917 //########################################################################
2920 /**
2921 *
2922 */
2923 int XmppClient::outputStreamOpen(const DOMString &destId,
2924 const DOMString &streamIdArg)
2925 {
2926 int i;
2927 for (i=0; i<outputStreamCount ; i++)
2928 if (outputStreams[i]->getState() == STREAM_AVAILABLE)
2929 break;
2930 if (i>=outputStreamCount)
2931 {
2932 error("No available output streams");
2933 return -1;
2934 }
2935 int streamNr = i;
2936 XmppStream *outs = outputStreams[streamNr];
2938 outs->setState(STREAM_OPENING);
2940 char buf[32];
2941 snprintf(buf, 31, "inband%d", getMsgId());
2942 DOMString iqId = buf;
2944 DOMString streamId = streamIdArg;
2945 if (streamId.size()<1)
2946 {
2947 snprintf(buf, 31, "stream%d", getMsgId());
2948 DOMString streamId = buf;
2949 }
2950 outs->setIqId(iqId);
2951 outs->setStreamId(streamId);
2952 outs->setPeerId(destId);
2954 char *fmt =
2955 "<iq type='set' from='%s' to='%s' id='%s'>"
2956 "<open sid='%s' block-size='4096'"
2957 " xmlns='http://jabber.org/protocol/ibb'/></iq>\n";
2958 if (!write(fmt, jid.c_str(),
2959 destId.c_str(), iqId.c_str(),
2960 streamId.c_str()))
2961 {
2962 outs->reset();
2963 return -1;
2964 }
2966 int state = outs->getState();
2967 for (int tim=0 ; tim<20 ; tim++)
2968 {
2969 if (state == STREAM_OPEN)
2970 break;
2971 else if (state == STREAM_ERROR)
2972 {
2973 printf("ERROR\n");
2974 outs->reset();
2975 return -1;
2976 }
2977 Thread::sleep(1000);
2978 state = outs->getState();
2979 }
2980 if (state != STREAM_OPEN)
2981 {
2982 printf("TIMEOUT ERROR\n");
2983 outs->reset();
2984 return -1;
2985 }
2987 return streamNr;
2988 }
2990 /**
2991 *
2992 */
2993 int XmppClient::outputStreamWrite(int streamNr,
2994 const unsigned char *buf, unsigned long len)
2995 {
2996 XmppStream *outs = outputStreams[streamNr];
2998 unsigned long outLen = 0;
2999 unsigned char *p = (unsigned char *)buf;
3001 while (outLen < len)
3002 {
3003 unsigned long chunksize = 1024;
3004 if (chunksize + outLen > len)
3005 chunksize = len - outLen;
3007 Base64Encoder encoder;
3008 encoder.append(p, chunksize);
3009 DOMString b64data = encoder.finish();
3010 p += chunksize;
3011 outLen += chunksize;
3013 char *fmt =
3014 "<message from='%s' to='%s' id='msg%d'>"
3015 "<data xmlns='http://jabber.org/protocol/ibb' sid='%s' seq='%d'>"
3016 "%s"
3017 "</data>"
3018 "<amp xmlns='http://jabber.org/protocol/amp'>"
3019 "<rule condition='deliver-at' value='stored' action='error'/>"
3020 "<rule condition='match-resource' value='exact' action='error'/>"
3021 "</amp>"
3022 "</message>\n";
3023 if (!write(fmt, jid.c_str(),
3024 outs->getPeerId().c_str(),
3025 getMsgId(),
3026 outs->getStreamId().c_str(),
3027 outs->getSeqNr(),
3028 b64data.c_str()))
3029 {
3030 outs->reset();
3031 return -1;
3032 }
3033 pause(5000);
3034 }
3035 return outLen;
3036 }
3038 /**
3039 *
3040 */
3041 int XmppClient::outputStreamClose(int streamNr)
3042 {
3043 XmppStream *outs = outputStreams[streamNr];
3045 char buf[32];
3046 snprintf(buf, 31, "inband%d", getMsgId());
3047 DOMString iqId = buf;
3048 outs->setIqId(iqId);
3050 outs->setState(STREAM_CLOSING);
3051 char *fmt =
3052 "<iq type='set' from='%s' to='%s' id='%s'>"
3053 "<close sid='%s' xmlns='http://jabber.org/protocol/ibb'/></iq>\n";
3054 if (!write(fmt, jid.c_str(),
3055 outs->getPeerId().c_str(),
3056 iqId.c_str(),
3057 outs->getStreamId().c_str()))
3058 return false;
3060 int state = outs->getState();
3061 for (int tim=0 ; tim<20 ; tim++)
3062 {
3063 if (state == STREAM_CLOSED)
3064 break;
3065 else if (state == STREAM_ERROR)
3066 {
3067 printf("ERROR\n");
3068 outs->reset();
3069 return -1;
3070 }
3071 Thread::sleep(1000);
3072 state = outs->getState();
3073 }
3074 if (state != STREAM_CLOSED)
3075 {
3076 printf("TIMEOUT ERROR\n");
3077 outs->reset();
3078 return -1;
3079 }
3081 outs->reset();
3082 return 1;
3083 }
3086 /**
3087 *
3088 */
3089 int XmppClient::inputStreamOpen(const DOMString &fromJid, const DOMString &streamId,
3090 const DOMString &iqId)
3091 {
3092 int i;
3093 for (i=0 ; i<inputStreamCount ; i++)
3094 {
3095 if (inputStreams[i]->getState() == STREAM_AVAILABLE)
3096 break;
3097 }
3098 if (i>=inputStreamCount)
3099 {
3100 error("No available input streams");
3101 return -1;
3102 }
3103 int streamNr = i;
3104 XmppStream *ins = inputStreams[streamNr];
3105 ins->reset();
3106 ins->setPeerId(fromJid);
3107 ins->setState(STREAM_CLOSED);
3108 ins->setStreamId(streamId);
3110 int state = ins->getState();
3111 for (int tim=0 ; tim<20 ; tim++)
3112 {
3113 if (state == STREAM_OPENING)
3114 break;
3115 else if (state == STREAM_ERROR)
3116 {
3117 printf("ERROR\n");
3118 ins->reset();
3119 return -1;
3120 }
3121 Thread::sleep(1000);
3122 state = ins->getState();
3123 }
3124 if (state != STREAM_OPENING)
3125 {
3126 printf("TIMEOUT ERROR\n");
3127 ins->reset();
3128 return -1;
3129 }
3130 char *fmt =
3131 "<iq type='result' from='%s' to='%s' id='%s'/>\n";
3132 if (!write(fmt, jid.c_str(), fromJid.c_str(), ins->getIqId().c_str()))
3133 {
3134 return -1;
3135 }
3137 ins->setState(STREAM_OPEN);
3138 return streamNr;
3139 }
3143 /**
3144 *
3145 */
3146 int XmppClient::inputStreamAvailable(int streamNr)
3147 {
3148 XmppStream *ins = inputStreams[streamNr];
3149 return ins->available();
3150 }
3152 /**
3153 *
3154 */
3155 std::vector<unsigned char> XmppClient::inputStreamRead(int streamNr)
3156 {
3157 XmppStream *ins = inputStreams[streamNr];
3158 return ins->read();
3159 }
3161 /**
3162 *
3163 */
3164 bool XmppClient::inputStreamClosing(int streamNr)
3165 {
3166 XmppStream *ins = inputStreams[streamNr];
3167 if (ins->getState() == STREAM_CLOSING)
3168 return true;
3169 return false;
3170 }
3173 /**
3174 *
3175 */
3176 int XmppClient::inputStreamClose(int streamNr)
3177 {
3178 int ret=1;
3179 XmppStream *ins = inputStreams[streamNr];
3180 if (ins->getState() == STREAM_CLOSING)
3181 {
3182 char *fmt =
3183 "<iq type='result' from='%s' to='%s' id='%s'/>\n";
3184 if (!write(fmt, jid.c_str(), ins->getPeerId().c_str(),
3185 ins->getIqId().c_str()))
3186 {
3187 ret = -1;
3188 }
3189 }
3190 ins->reset();
3191 return ret;
3192 }
3199 //########################################################################
3200 //# FILE TRANSFERS
3201 //########################################################################
3204 /**
3205 *
3206 */
3207 bool XmppClient::fileSend(const DOMString &destJidArg,
3208 const DOMString &offeredNameArg,
3209 const DOMString &fileNameArg,
3210 const DOMString &descriptionArg)
3211 {
3212 DOMString destJid = destJidArg;
3213 DOMString offeredName = offeredNameArg;
3214 DOMString fileName = fileNameArg;
3215 DOMString description = descriptionArg;
3217 int i;
3218 for (i=0; i<fileSendCount ; i++)
3219 if (fileSends[i]->getState() == STREAM_AVAILABLE)
3220 break;
3221 if (i>=fileSendCount)
3222 {
3223 error("No available file send streams");
3224 return false;
3225 }
3226 int fileSendNr = i;
3227 XmppStream *outf = fileSends[fileSendNr];
3229 outf->setState(STREAM_OPENING);
3231 struct stat finfo;
3232 if (stat(fileName.c_str(), &finfo)<0)
3233 {
3234 error("Cannot stat file '%s' for sending", fileName.c_str());
3235 return false;
3236 }
3237 long fileLen = finfo.st_size;
3238 if (!fileLen > 1000000)
3239 {
3240 error("'%s' too large", fileName.c_str());
3241 return false;
3242 }
3243 if (!S_ISREG(finfo.st_mode))
3244 {
3245 error("'%s' is not a regular file", fileName.c_str());
3246 return false;
3247 }
3248 FILE *f = fopen(fileName.c_str(), "rb");
3249 if (!f)
3250 {
3251 error("cannot open '%s' for sending", fileName.c_str());
3252 return false;
3253 }
3254 unsigned char *sendBuf = (unsigned char *)malloc(fileLen+1);
3255 if (!sendBuf)
3256 {
3257 error("cannot cannot allocate send buffer for %s", fileName.c_str());
3258 return false;
3259 }
3260 for (long i=0 ; i<fileLen && !feof(f); i++)
3261 {
3262 sendBuf[i] = fgetc(f);
3263 }
3264 fclose(f);
3266 //## get the last path segment from the whole path
3267 if (offeredName.size()<1)
3268 {
3269 int slashPos = -1;
3270 for (unsigned int i=0 ; i<fileName.size() ; i++)
3271 {
3272 int ch = fileName[i];
3273 if (ch == '/' || ch == '\\')
3274 slashPos = i;
3275 }
3276 if (slashPos>=0 && slashPos<=(int)(fileName.size()-1))
3277 {
3278 offeredName = fileName.substr(slashPos+1,
3279 fileName.size()-slashPos-1);
3280 printf("offeredName:%s\n", offeredName.c_str());
3281 }
3282 }
3284 char buf[32];
3285 snprintf(buf, 31, "file%d", getMsgId());
3286 DOMString iqId = buf;
3287 outf->setIqId(iqId);
3289 snprintf(buf, 31, "stream%d", getMsgId());
3290 DOMString streamId = buf;
3291 //outf->setStreamId(streamId);
3293 DOMString hash = Md5::hashHex(sendBuf, fileLen);
3294 printf("Hash:%s\n", hash.c_str());
3296 outf->setPeerId(destJid);
3298 char dtgBuf[81];
3299 struct tm *timeVal = gmtime(&(finfo.st_mtime));
3300 strftime(dtgBuf, 80, "%Y-%m-%dT%H:%M:%Sz", timeVal);
3302 char *fmt =
3303 "<iq type='set' id='%s' to='%s'>"
3304 "<si xmlns='http://jabber.org/protocol/si' id='%s'"
3305 " mime-type='text/plain'"
3306 " profile='http://jabber.org/protocol/si/profile/file-transfer'>"
3307 "<file xmlns='http://jabber.org/protocol/si/profile/file-transfer'"
3308 " name='%s' size='%d' hash='%s' date='%s'><desc>%s</desc></file>"
3309 "<feature xmlns='http://jabber.org/protocol/feature-neg'>"
3310 "<x xmlns='jabber:x:data' type='form'>"
3311 "<field var='stream-method' type='list-single'>"
3312 //"<option><value>http://jabber.org/protocol/bytestreams</value></option>"
3313 "<option><value>http://jabber.org/protocol/ibb</value></option>"
3314 "</field></x></feature></si></iq>\n";
3315 if (!write(fmt, iqId.c_str(), destJid.c_str(),
3316 streamId.c_str(), offeredName.c_str(), fileLen,
3317 hash.c_str(), dtgBuf, description.c_str()))
3318 {
3319 free(sendBuf);
3320 return false;
3321 }
3323 int state = outf->getState();
3324 for (int tim=0 ; tim<20 ; tim++)
3325 {
3326 printf("##### waiting for open\n");
3327 if (state == STREAM_OPEN)
3328 {
3329 outf->reset();
3330 break;
3331 }
3332 else if (state == STREAM_ERROR)
3333 {
3334 printf("ERROR\n");
3335 outf->reset();
3336 return false;
3337 }
3338 Thread::sleep(1000);
3339 state = outf->getState();
3340 }
3341 if (state != STREAM_OPEN)
3342 {
3343 printf("TIMEOUT ERROR\n");
3344 outf->reset();
3345 return false;
3346 }
3348 //free up this reqource
3349 outf->reset();
3351 int streamNr = outputStreamOpen(destJid, streamId);
3352 if (streamNr<0)
3353 {
3354 error("cannot open output stream %s", streamId.c_str());
3355 outf->reset();
3356 return false;
3357 }
3359 int ret = outputStreamWrite(streamNr, sendBuf, fileLen);
3361 if (ret<0)
3362 {
3363 }
3365 outputStreamClose(streamNr);
3367 free(sendBuf);
3368 return true;
3369 }
3372 class FileSendThread : public Thread
3373 {
3374 public:
3376 FileSendThread(XmppClient &par,
3377 const DOMString &destJidArg,
3378 const DOMString &offeredNameArg,
3379 const DOMString &fileNameArg,
3380 const DOMString &descriptionArg) : client(par)
3381 {
3382 destJid = destJidArg;
3383 offeredName = offeredNameArg;
3384 fileName = fileNameArg;
3385 description = descriptionArg;
3386 }
3388 virtual ~FileSendThread() {}
3390 void run()
3391 {
3392 client.fileSend(destJid, offeredName,
3393 fileName, description);
3394 }
3396 private:
3398 XmppClient &client;
3399 DOMString destJid;
3400 DOMString offeredName;
3401 DOMString fileName;
3402 DOMString description;
3403 };
3405 /**
3406 *
3407 */
3408 bool XmppClient::fileSendBackground(const DOMString &destJid,
3409 const DOMString &offeredName,
3410 const DOMString &fileName,
3411 const DOMString &description)
3412 {
3413 FileSendThread thread(*this, destJid, offeredName,
3414 fileName, description);
3415 thread.start();
3416 return true;
3417 }
3420 /**
3421 *
3422 */
3423 bool XmppClient::fileReceive(const DOMString &fromJid,
3424 const DOMString &iqId,
3425 const DOMString &streamId,
3426 const DOMString &fileName,
3427 long fileSize,
3428 const DOMString &fileHash)
3429 {
3430 char *fmt =
3431 "<iq type='result' to='%s' id='%s'>"
3432 "<si xmlns='http://jabber.org/protocol/si'>"
3433 "<file xmlns='http://jabber.org/protocol/si/profile/file-transfer'/>"
3434 "<feature xmlns='http://jabber.org/protocol/feature-neg'>"
3435 "<x xmlns='jabber:x:data' type='submit'>"
3436 "<field var='stream-method'>"
3437 "<value>http://jabber.org/protocol/ibb</value>"
3438 "</field></x></feature></si></iq>\n";
3439 if (!write(fmt, fromJid.c_str(), iqId.c_str()))
3440 {
3441 return false;
3442 }
3444 int streamNr = inputStreamOpen(fromJid, streamId, iqId);
3445 if (streamNr < 0)
3446 {
3447 return false;
3448 }
3451 Md5 md5;
3452 FILE *f = fopen(fileName.c_str(), "wb");
3453 if (!f)
3454 {
3455 return false;
3456 }
3458 while (true)
3459 {
3460 if (inputStreamAvailable(streamNr)<1)
3461 {
3462 if (inputStreamClosing(streamNr))
3463 break;
3464 pause(100);
3465 continue;
3466 }
3467 std::vector<unsigned char> ret = inputStreamRead(streamNr);
3468 std::vector<unsigned char>::iterator iter;
3469 for (iter=ret.begin() ; iter!=ret.end() ; iter++)
3470 {
3471 unsigned char ch = *iter;
3472 md5.append(&ch, 1);
3473 fwrite(&ch, 1, 1, f);
3474 }
3475 }
3477 inputStreamClose(streamNr);
3478 fclose(f);
3480 DOMString hash = md5.finishHex();
3481 printf("received file hash:%s\n", hash.c_str());
3483 return true;
3484 }
3488 class FileReceiveThread : public Thread
3489 {
3490 public:
3492 FileReceiveThread(XmppClient &par,
3493 const DOMString &fromJidArg,
3494 const DOMString &iqIdArg,
3495 const DOMString &streamIdArg,
3496 const DOMString &fileNameArg,
3497 long fileSizeArg,
3498 const DOMString &fileHashArg) : client(par)
3499 {
3500 fromJid = fromJidArg;
3501 iqId = iqIdArg;
3502 streamId = streamIdArg;
3503 fileName = fileNameArg;
3504 fileSize = fileSizeArg;
3505 fileHash = fileHashArg;
3506 }
3508 virtual ~FileReceiveThread() {}
3510 void run()
3511 {
3512 client.fileReceive(fromJid, iqId, streamId,
3513 fileName, fileSize, fileHash);
3514 }
3516 private:
3518 XmppClient &client;
3519 DOMString fromJid;
3520 DOMString iqId;
3521 DOMString streamId;
3522 DOMString fileName;
3523 long fileSize;
3524 DOMString fileHash;
3525 };
3527 /**
3528 *
3529 */
3530 bool XmppClient::fileReceiveBackground(const DOMString &fromJid,
3531 const DOMString &iqId,
3532 const DOMString &streamId,
3533 const DOMString &fileName,
3534 long fileSize,
3535 const DOMString &fileHash)
3536 {
3537 FileReceiveThread thread(*this, fromJid, iqId, streamId,
3538 fileName, fileSize, fileHash);
3539 thread.start();
3540 return true;
3541 }
3545 //########################################################################
3546 //# X M P P G R O U P C H A T
3547 //########################################################################
3549 /**
3550 *
3551 */
3552 XmppGroupChat::XmppGroupChat(const DOMString &groupJidArg)
3553 {
3554 groupJid = groupJidArg;
3555 }
3557 /**
3558 *
3559 */
3560 XmppGroupChat::XmppGroupChat(const XmppGroupChat &other)
3561 {
3562 groupJid = other.groupJid;
3563 userList = other.userList;
3564 }
3566 /**
3567 *
3568 */
3569 XmppGroupChat::~XmppGroupChat()
3570 {
3571 }
3574 /**
3575 *
3576 */
3577 DOMString XmppGroupChat::getGroupJid()
3578 {
3579 return groupJid;
3580 }
3583 void XmppGroupChat::userAdd(const DOMString &nick,
3584 const DOMString &jid)
3585 {
3586 std::vector<XmppUser>::iterator iter;
3587 for (iter= userList.begin() ; iter!=userList.end() ; iter++)
3588 {
3589 if (iter->nick == nick)
3590 return;
3591 }
3592 XmppUser user(jid, nick);
3593 userList.push_back(user);
3594 }
3596 void XmppGroupChat::userShow(const DOMString &nick,
3597 const DOMString &show)
3598 {
3599 DOMString theShow = show;
3600 if (theShow == "")
3601 theShow = "available"; // a join message will now have a show
3602 std::vector<XmppUser>::iterator iter;
3603 for (iter= userList.begin() ; iter!=userList.end() ; iter++)
3604 {
3605 if (iter->nick == nick)
3606 iter->show = theShow;
3607 }
3608 }
3610 void XmppGroupChat::userDelete(const DOMString &nick)
3611 {
3612 std::vector<XmppUser>::iterator iter;
3613 for (iter= userList.begin() ; iter!=userList.end() ; )
3614 {
3615 if (iter->nick == nick)
3616 iter = userList.erase(iter);
3617 else
3618 iter++;
3619 }
3620 }
3622 std::vector<XmppUser> XmppGroupChat::getUserList() const
3623 {
3624 return userList;
3625 }
3635 } //namespace Pedro
3636 //########################################################################
3637 //# E N D O F F I L E
3638 //########################################################################