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 //Apparently this is not a problem
1866 //error("login: no SASL realm sent by server");
1867 //return false;
1868 }
1870 status("SASL recv nonce: '%s' realm:'%s'\n", nonce.c_str(), realm.c_str());
1872 char idBuf[10];
1873 snprintf(idBuf, 9, "%dsasl", msgId++);
1874 DOMString cnonce = Sha1::hashHex((unsigned char *)idBuf, 7);
1875 DOMString authzid = username; authzid.append("@"); authzid.append(host);
1876 DOMString digest_uri = "xmpp/"; digest_uri.append(host);
1878 //## Make A1
1879 Md5 md5;
1880 md5.append(username);
1881 md5.append(":");
1882 md5.append(realm);
1883 md5.append(":");
1884 md5.append(password);
1885 unsigned char a1tmp[16];
1886 md5.finish(a1tmp);
1887 md5.init();
1888 md5.append(a1tmp, 16);
1889 md5.append(":");
1890 md5.append(nonce);
1891 md5.append(":");
1892 md5.append(cnonce);
1893 //RFC2831 says authzid is optional. Wildfire has trouble with authzid's
1894 //md5.append(":");
1895 //md5.append(authzid);
1896 md5.append("");
1897 DOMString a1 = md5.finishHex();
1898 status("##a1:'%s'", a1.c_str());
1900 //# Make A2
1901 md5.init();
1902 md5.append("AUTHENTICATE:");
1903 md5.append(digest_uri);
1904 DOMString a2 = md5.finishHex();
1905 status("##a2:'%s'", a2.c_str());
1907 //# Now make the response
1908 md5.init();
1909 md5.append(a1);
1910 md5.append(":");
1911 md5.append(nonce);
1912 md5.append(":");
1913 md5.append("00000001");//nc
1914 md5.append(":");
1915 md5.append(cnonce);
1916 md5.append(":");
1917 md5.append("auth");//qop
1918 md5.append(":");
1919 md5.append(a2);
1920 DOMString response = md5.finishHex();
1922 DOMString resp;
1923 resp.append("username=\""); resp.append(username); resp.append("\",");
1924 resp.append("realm=\""); resp.append(realm); resp.append("\",");
1925 resp.append("nonce=\""); resp.append(nonce); resp.append("\",");
1926 resp.append("cnonce=\""); resp.append(cnonce); resp.append("\",");
1927 resp.append("nc=00000001,qop=auth,");
1928 resp.append("digest-uri=\""); resp.append(digest_uri); resp.append("\"," );
1929 //resp.append("authzid=\""); resp.append(authzid); resp.append("\",");
1930 resp.append("response="); resp.append(response); resp.append(",");
1931 resp.append("charset=utf-8");
1932 status("sending response:'%s'", resp.c_str());
1933 resp = Base64Encoder::encode(resp);
1934 status("base64 response:'%s'", resp.c_str());
1935 fmt =
1936 "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response>\n";
1937 if (!write(fmt, resp.c_str()))
1938 return false;
1940 recbuf = readStanza();
1941 status("server says:: '%s'", recbuf.c_str());
1942 elem = parser.parse(recbuf);
1943 //elem->print();
1944 b64challenge = elem->getTagValue("challenge");
1945 delete elem;
1947 if (b64challenge.size() < 1)
1948 {
1949 error("login: no second SASL challenge offered by server");
1950 return false;
1951 }
1953 challenge = Base64Decoder::decodeToString(b64challenge);
1954 status("md5 challenge: '%s'", challenge.c_str());
1956 if (!saslParse(challenge, attrs))
1957 {
1958 error("login: error parsing SASL challenge");
1959 return false;
1960 }
1962 DOMString rspauth = attrs["rspauth"];
1963 if (rspauth.size()==0)
1964 {
1965 error("login: no SASL respauth sent by server\n");
1966 return false;
1967 }
1969 fmt =
1970 "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>\n";
1971 if (!write(fmt))
1972 return false;
1974 recbuf = readStanza();
1975 status("SASL recv: '%s", recbuf.c_str());
1976 elem = parser.parse(recbuf);
1977 //elem->print();
1978 b64challenge = elem->getTagValue("challenge");
1979 bool success = (elem->findElements("success").size() > 0);
1980 delete elem;
1982 return success;
1983 }
1987 /**
1988 * Attempt to authentication using the SASL PLAIN mechanism. This
1989 * is used most commonly my Google Talk.
1990 */
1991 bool XmppClient::saslPlainAuthenticate()
1992 {
1993 Parser parser;
1995 DOMString id = username;
1996 //id.append("@");
1997 //id.append(host);
1998 Base64Encoder encoder;
1999 encoder.append('\0');
2000 encoder.append(id);
2001 encoder.append('\0');
2002 encoder.append(password);
2003 DOMString base64Auth = encoder.finish();
2004 //printf("authbuf:%s\n", base64Auth.c_str());
2006 char *fmt =
2007 "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' "
2008 "mechanism='PLAIN'>%s</auth>\n";
2009 if (!write(fmt, base64Auth.c_str()))
2010 return false;
2011 DOMString recbuf = readStanza();
2012 status("challenge received: '%s'", recbuf.c_str());
2013 Element *elem = parser.parse(recbuf);
2015 bool success = (elem->findElements("success").size() > 0);
2016 delete elem;
2018 return success;
2019 }
2023 /**
2024 * Handshake with SASL, and use one of its offered mechanisms to
2025 * authenticate.
2026 */
2027 bool XmppClient::saslAuthenticate()
2028 {
2029 Parser parser;
2031 DOMString recbuf = readStanza();
2032 status("RECV: '%s'\n", recbuf.c_str());
2033 Element *elem = parser.parse(recbuf);
2034 //elem->print();
2036 //Check for starttls
2037 bool wantStartTls = false;
2038 if (elem->findElements("starttls").size() > 0)
2039 {
2040 wantStartTls = true;
2041 if (elem->findElements("required").size() > 0)
2042 status("login: STARTTLS required");
2043 else
2044 status("login: STARTTLS available");
2045 }
2047 if (wantStartTls && !sock->getEnableSSL())
2048 {
2049 delete elem;
2050 char *fmt =
2051 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n";
2052 if (!write(fmt))
2053 return false;
2054 recbuf = readStanza();
2055 status("RECV: '%s'\n", recbuf.c_str());
2056 elem = parser.parse(recbuf);
2057 if (elem->getTagAttribute("proceed", "xmlns").size()<1)
2058 {
2059 error("Server rejected TLS negotiation");
2060 disconnect();
2061 return false;
2062 }
2063 delete elem;
2064 if (!sock->startTls())
2065 {
2066 error("Could not start TLS");
2067 disconnect();
2068 return false;
2069 }
2071 fmt =
2072 "<stream:stream xmlns='jabber:client' "
2073 "xmlns:stream='http://etherx.jabber.org/streams' "
2074 "to='%s' version='1.0'>\n\n";
2075 if (!write(fmt, realm.c_str()))
2076 return false;
2078 recbuf = readStanza();
2079 status("RECVx: '%s'", recbuf.c_str());
2080 recbuf.append("</stream:stream>");
2081 elem = parser.parse(recbuf);
2082 bool success =
2083 (elem->getTagAttribute("stream:stream", "id").size()>0);
2084 if (!success)
2085 {
2086 error("STARTTLS negotiation failed");
2087 disconnect();
2088 return false;
2089 }
2090 delete elem;
2091 recbuf = readStanza();
2092 status("RECV: '%s'\n", recbuf.c_str());
2093 elem = parser.parse(recbuf);
2095 XmppEvent event(XmppEvent::EVENT_SSL_STARTED);
2096 dispatchXmppEvent(event);
2097 }
2099 //register, if user requests
2100 if (doRegister)
2101 {
2102 if (!inBandRegistrationNew())
2103 return false;
2104 }
2106 //check for sasl authentication mechanisms
2107 std::vector<Element *> elems =
2108 elem->findElements("mechanism");
2109 if (elems.size() < 1)
2110 {
2111 error("login: no SASL mechanism offered by server");
2112 return false;
2113 }
2114 bool md5Found = false;
2115 bool plainFound = false;
2116 for (unsigned int i=0 ; i<elems.size() ; i++)
2117 {
2118 DOMString mech = elems[i]->getValue();
2119 if (mech == "DIGEST-MD5")
2120 {
2121 status("MD5 authentication offered");
2122 md5Found = true;
2123 }
2124 else if (mech == "PLAIN")
2125 {
2126 status("PLAIN authentication offered");
2127 plainFound = true;
2128 }
2129 }
2130 delete elem;
2132 bool success = false;
2133 if (md5Found)
2134 {
2135 success = saslMd5Authenticate();
2136 }
2137 else if (plainFound)
2138 {
2139 success = saslPlainAuthenticate();
2140 }
2141 else
2142 {
2143 error("not able to handle sasl authentication mechanisms");
2144 return false;
2145 }
2147 if (success)
2148 status("###### SASL authentication success\n");
2149 else
2150 error("###### SASL authentication failure\n");
2152 return success;
2153 }
2160 //########################################################################
2161 //# CONNECT
2162 //########################################################################
2165 /**
2166 * Check if we are connected, and fail with an error if we are not
2167 */
2168 bool XmppClient::checkConnect()
2169 {
2170 if (!connected)
2171 {
2172 XmppEvent evt(XmppEvent::EVENT_ERROR);
2173 evt.setData("Attempted operation while disconnected");
2174 dispatchXmppEvent(evt);
2175 return false;
2176 }
2177 return true;
2178 }
2182 /**
2183 * Create an XMPP session with a server. This
2184 * is basically the transport layer of XMPP.
2185 */
2186 bool XmppClient::createSession()
2187 {
2189 Parser parser;
2190 if (port==443 || port==5223)
2191 sock->enableSSL(true);
2192 if (!sock->connect(host, port))
2193 {
2194 return false;
2195 }
2197 if (sock->getEnableSSL())
2198 {
2199 XmppEvent event(XmppEvent::EVENT_SSL_STARTED);
2200 dispatchXmppEvent(event);
2201 }
2203 char *fmt =
2204 "<stream:stream "
2205 "to='%s' "
2206 "xmlns='jabber:client' "
2207 "xmlns:stream='http://etherx.jabber.org/streams' "
2208 "version='1.0'>\n\n";
2209 if (!write(fmt, realm.c_str()))
2210 return false;
2212 DOMString recbuf = readStanza();
2213 //printf("received: '%s'\n", recbuf.c_str());
2214 recbuf.append("</stream:stream>");
2215 Element *elem = parser.parse(recbuf);
2216 //elem->print();
2217 bool useSasl = false;
2218 DOMString streamId = elem->getTagAttribute("stream:stream", "id");
2219 //printf("### StreamID: %s\n", streamId.c_str());
2220 DOMString streamVersion = elem->getTagAttribute("stream:stream", "version");
2221 if (streamVersion == "1.0")
2222 useSasl = true;
2224 if (useSasl)
2225 {
2226 if (!saslAuthenticate())
2227 return false;
2228 fmt =
2229 "<stream:stream "
2230 "to='%s' "
2231 "xmlns='jabber:client' "
2232 "xmlns:stream='http://etherx.jabber.org/streams' "
2233 "version='1.0'>\n\n";
2235 if (!write(fmt, realm.c_str()))
2236 return false;
2237 recbuf = readStanza();
2238 recbuf.append("</stream:stream>\n");
2239 //printf("now server says:: '%s'\n", recbuf.c_str());
2240 elem = parser.parse(recbuf);
2241 //elem->print();
2242 delete elem;
2244 recbuf = readStanza();
2245 //printf("now server says:: '%s'\n", recbuf.c_str());
2246 elem = parser.parse(recbuf);
2247 bool hasBind = (elem->findElements("bind").size() > 0);
2248 //elem->print();
2249 delete elem;
2251 if (!hasBind)
2252 {
2253 error("no binding provided by server");
2254 return false;
2255 }
2258 }
2259 else // not SASL
2260 {
2261 if (!iqAuthenticate(streamId))
2262 return false;
2263 }
2266 //### Resource binding
2267 fmt =
2268 "<iq type='set' id='bind%d'>"
2269 "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
2270 "<resource>%s</resource>"
2271 "</bind></iq>\n";
2272 if (!write(fmt, msgId++, resource.c_str()))
2273 return false;
2275 recbuf = readStanza();
2276 status("bind result: '%s'", recbuf.c_str());
2277 elem = parser.parse(recbuf);
2278 //elem->print();
2279 DOMString bindType = elem->getTagAttribute("iq", "type");
2280 //printf("##bindType:%s\n", bindType.c_str());
2281 delete elem;
2283 if (bindType != "result")
2284 {
2285 error("no binding with server failed");
2286 return false;
2287 }
2289 fmt =
2290 "<iq type='set' id='sess%d'>"
2291 "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>"
2292 "</iq>\n";
2293 if (!write(fmt, msgId++))
2294 return false;
2296 recbuf = readStanza();
2297 status("session received: '%s'", recbuf.c_str());
2298 elem = parser.parse(recbuf);
2299 //elem->print();
2300 DOMString sessionType = elem->getTagAttribute("iq", "type");
2301 //printf("##sessionType:%s\n", sessionType.c_str());
2302 delete elem;
2304 if (sessionType != "result")
2305 {
2306 error("no session provided by server");
2307 return false;
2308 }
2310 //printf("########## COOL #########\n");
2311 //Now that we are bound, we have a valid JID
2312 jid = username;
2313 jid.append("@");
2314 jid.append(realm);
2315 jid.append("/");
2316 jid.append(resource);
2318 //We are now done with the synchronous handshaking. Let's go into
2319 //async mode
2321 fmt =
2322 "<iq type='get' id='roster%d'><query xmlns='jabber:iq:roster'/></iq>\n";
2323 if (!write(fmt, msgId++))
2324 return false;
2326 fmt =
2327 "<iq type='get' id='discoItems%d' to='%s'>"
2328 "<query xmlns='http://jabber.org/protocol/disco#items'/></iq>\n";
2329 if (!write(fmt, msgId++, realm.c_str()))
2330 return false;
2332 fmt =
2333 "<iq type='get' id='discoInfo%d' to='conference.%s'>"
2334 "<query xmlns='http://jabber.org/protocol/disco#info'/></iq>\n";
2335 if (!write(fmt, msgId++, realm.c_str()))
2336 return false;
2338 fmt =
2339 "<presence/>\n";
2340 if (!write(fmt))
2341 return false;
2343 /*
2344 recbuf = readStanza();
2345 status("stream received: '%s'", recbuf.c_str());
2346 elem = parser.parse(recbuf);
2347 //elem->print();
2348 delete elem;
2349 */
2351 //We are now logged in
2352 status("Connected");
2353 connected = true;
2354 XmppEvent evt(XmppEvent::EVENT_CONNECTED);
2355 evt.setData(host);
2356 dispatchXmppEvent(evt);
2357 //Thread::sleep(1000000);
2359 sock->setReceiveTimeout(1000);
2360 ReceiverThread runner(*this);
2361 Thread thread(runner);
2362 thread.start();
2364 return true;
2365 }
2369 /**
2370 * Public call to connect
2371 */
2372 bool XmppClient::connect()
2373 {
2374 if (!createSession())
2375 {
2376 disconnect();
2377 return false;
2378 }
2379 return true;
2380 }
2383 /**
2384 * Public call to connect
2385 */
2386 bool XmppClient::connect(DOMString hostArg, int portArg,
2387 DOMString usernameArg,
2388 DOMString passwordArg,
2389 DOMString resourceArg)
2390 {
2391 host = hostArg;
2392 port = portArg;
2393 password = passwordArg;
2394 resource = resourceArg;
2396 //parse this one
2397 setUsername(usernameArg);
2399 bool ret = connect();
2400 return ret;
2401 }
2405 /**
2406 * Public call to disconnect
2407 */
2408 bool XmppClient::disconnect()
2409 {
2410 if (connected)
2411 {
2412 char *fmt =
2413 "<presence from='%s' type='unavailable'/>\n";
2414 write(fmt, jid.c_str());
2415 }
2416 keepGoing = false;
2417 connected = false;
2418 Thread::sleep(2000); //allow receiving thread to quit
2419 sock->disconnect();
2420 roster.clear();
2421 groupChatsClear();
2422 XmppEvent event(XmppEvent::EVENT_DISCONNECTED);
2423 event.setData(host);
2424 dispatchXmppEvent(event);
2425 return true;
2426 }
2432 //########################################################################
2433 //# ROSTER
2434 //########################################################################
2436 /**
2437 * Add an XMPP id to your roster
2438 */
2439 bool XmppClient::rosterAdd(const DOMString &rosterGroup,
2440 const DOMString &otherJid,
2441 const DOMString &name)
2442 {
2443 if (!checkConnect())
2444 return false;
2445 char *fmt =
2446 "<iq from='%s' type='set' id='roster_%d'>"
2447 "<query xmlns='jabber:iq:roster'>"
2448 "<item jid='%s' name='%s'><group>%s</group></item>"
2449 "</query></iq>\n";
2450 if (!write(fmt, jid.c_str(), msgId++, otherJid.c_str(),
2451 name.c_str(), rosterGroup.c_str()))
2452 {
2453 return false;
2454 }
2455 return true;
2456 }
2460 /**
2461 * Delete an XMPP id from your roster.
2462 */
2463 bool XmppClient::rosterDelete(const DOMString &otherJid)
2464 {
2465 if (!checkConnect())
2466 return false;
2467 char *fmt =
2468 "<iq from='%s' type='set' id='roster_%d'>"
2469 "<query xmlns='jabber:iq:roster'>"
2470 "<item jid='%s' subscription='remove'><group>%s</group></item>"
2471 "</query></iq>\n";
2472 if (!write(fmt, jid.c_str(), msgId++, otherJid.c_str()))
2473 {
2474 return false;
2475 }
2476 return true;
2477 }
2480 /**
2481 * Comparison method for sort() call below
2482 */
2483 static bool xmppRosterCompare(const XmppUser& p1, const XmppUser& p2)
2484 {
2485 DOMString s1 = p1.group;
2486 DOMString s2 = p2.group;
2487 for (unsigned int len=0 ; len<s1.size() && len<s2.size() ; len++)
2488 {
2489 int comp = tolower(s1[len]) - tolower(s2[len]);
2490 if (comp)
2491 return (comp<0);
2492 }
2494 s1 = p1.jid;
2495 s2 = p2.jid;
2496 for (unsigned int len=0 ; len<s1.size() && len<s2.size() ; len++)
2497 {
2498 int comp = tolower(s1[len]) - tolower(s2[len]);
2499 if (comp)
2500 return (comp<0);
2501 }
2502 return false;
2503 }
2507 /**
2508 * Sort and return the roster that has just been reported by
2509 * an XmppEvent::EVENT_ROSTER event.
2510 */
2511 std::vector<XmppUser> XmppClient::getRoster()
2512 {
2513 std::vector<XmppUser> ros = roster;
2514 std::sort(ros.begin(), ros.end(), xmppRosterCompare);
2515 return ros;
2516 }
2519 /**
2520 *
2521 */
2522 void XmppClient::rosterShow(const DOMString &jid, const DOMString &show)
2523 {
2524 DOMString theShow = show;
2525 if (theShow == "")
2526 theShow = "available";
2528 std::vector<XmppUser>::iterator iter;
2529 for (iter=roster.begin() ; iter != roster.end() ; iter++)
2530 {
2531 if (iter->jid == jid)
2532 iter->show = theShow;
2533 }
2534 }
2541 //########################################################################
2542 //# CHAT (individual)
2543 //########################################################################
2545 /**
2546 * Send a message to an xmpp jid
2547 */
2548 bool XmppClient::message(const DOMString &user, const DOMString &subj,
2549 const DOMString &msg)
2550 {
2551 if (!checkConnect())
2552 return false;
2554 DOMString xmlSubj = toXml(subj);
2555 DOMString xmlMsg = toXml(msg);
2557 if (xmlSubj.size() > 0)
2558 {
2559 char *fmt =
2560 "<message from='%s' to='%s' type='chat'>"
2561 "<subject>%s</subject><body>%s</body></message>\n";
2562 if (!write(fmt, jid.c_str(), user.c_str(),
2563 xmlSubj.c_str(), xmlMsg.c_str()))
2564 return false;
2565 }
2566 else
2567 {
2568 char *fmt =
2569 "<message from='%s' to='%s'>"
2570 "<body>%s</body></message>\n";
2571 if (!write(fmt, jid.c_str(), user.c_str(), xmlMsg.c_str()))
2572 return false;
2573 }
2574 return true;
2575 }
2579 /**
2580 *
2581 */
2582 bool XmppClient::message(const DOMString &user, const DOMString &msg)
2583 {
2584 return message(user, "", msg);
2585 }
2589 /**
2590 *
2591 */
2592 bool XmppClient::presence(const DOMString &presence)
2593 {
2594 if (!checkConnect())
2595 return false;
2597 DOMString xmlPres = toXml(presence);
2599 char *fmt =
2600 "<presence from='%s'><show>%s</show></presence>\n";
2601 if (!write(fmt, jid.c_str(), xmlPres.c_str()))
2602 return false;
2603 return true;
2604 }
2611 //########################################################################
2612 //# GROUP CHAT
2613 //########################################################################
2615 /**
2616 *
2617 */
2618 bool XmppClient::groupChatCreate(const DOMString &groupJid)
2619 {
2620 std::vector<XmppGroupChat *>::iterator iter;
2621 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2622 {
2623 if ((*iter)->getGroupJid() == groupJid)
2624 {
2625 error("Group chat '%s' already exists", groupJid.c_str());
2626 return false;
2627 }
2628 }
2629 XmppGroupChat *chat = new XmppGroupChat(groupJid);
2630 groupChats.push_back(chat);
2631 return true;
2632 }
2636 /**
2637 *
2638 */
2639 void XmppClient::groupChatDelete(const DOMString &groupJid)
2640 {
2641 std::vector<XmppGroupChat *>::iterator iter;
2642 for (iter=groupChats.begin() ; iter!=groupChats.end() ; )
2643 {
2644 XmppGroupChat *chat = *iter;
2645 if (chat->getGroupJid() == groupJid)
2646 {
2647 iter = groupChats.erase(iter);
2648 delete chat;
2649 }
2650 else
2651 iter++;
2652 }
2653 }
2657 /**
2658 *
2659 */
2660 bool XmppClient::groupChatExists(const DOMString &groupJid)
2661 {
2662 std::vector<XmppGroupChat *>::iterator iter;
2663 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2664 if ((*iter)->getGroupJid() == groupJid)
2665 return true;
2666 return false;
2667 }
2671 /**
2672 *
2673 */
2674 void XmppClient::groupChatsClear()
2675 {
2676 std::vector<XmppGroupChat *>::iterator iter;
2677 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2678 delete (*iter);
2679 groupChats.clear();
2680 }
2685 /**
2686 *
2687 */
2688 void XmppClient::groupChatUserAdd(const DOMString &groupJid,
2689 const DOMString &nick,
2690 const DOMString &jid)
2691 {
2692 std::vector<XmppGroupChat *>::iterator iter;
2693 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2694 {
2695 if ((*iter)->getGroupJid() == groupJid)
2696 {
2697 (*iter)->userAdd(nick, jid);
2698 }
2699 }
2700 }
2704 /**
2705 *
2706 */
2707 void XmppClient::groupChatUserShow(const DOMString &groupJid,
2708 const DOMString &nick,
2709 const DOMString &show)
2710 {
2711 std::vector<XmppGroupChat *>::iterator iter;
2712 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2713 {
2714 if ((*iter)->getGroupJid() == groupJid)
2715 {
2716 (*iter)->userShow(nick, show);
2717 }
2718 }
2719 }
2724 /**
2725 *
2726 */
2727 void XmppClient::groupChatUserDelete(const DOMString &groupJid,
2728 const DOMString &nick)
2729 {
2730 std::vector<XmppGroupChat *>::iterator iter;
2731 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2732 {
2733 if ((*iter)->getGroupJid() == groupJid)
2734 {
2735 (*iter)->userDelete(nick);
2736 }
2737 }
2738 }
2742 /**
2743 * Comparison method for the sort() below
2744 */
2745 static bool xmppUserCompare(const XmppUser& p1, const XmppUser& p2)
2746 {
2747 DOMString s1 = p1.nick;
2748 DOMString s2 = p2.nick;
2749 int comp = 0;
2750 for (unsigned int len=0 ; len<s1.size() && len<s2.size() ; len++)
2751 {
2752 comp = tolower(s1[len]) - tolower(s2[len]);
2753 if (comp)
2754 break;
2755 }
2756 return (comp<0);
2757 }
2761 /**
2762 * Return the user list for the named group
2763 */
2764 std::vector<XmppUser> XmppClient::groupChatGetUserList(
2765 const DOMString &groupJid)
2766 {
2767 if (!checkConnect())
2768 {
2769 std::vector<XmppUser> dummy;
2770 return dummy;
2771 }
2773 std::vector<XmppGroupChat *>::iterator iter;
2774 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2775 {
2776 if ((*iter)->getGroupJid() == groupJid )
2777 {
2778 std::vector<XmppUser> uList = (*iter)->getUserList();
2779 std::sort(uList.begin(), uList.end(), xmppUserCompare);
2780 return uList;
2781 }
2782 }
2783 std::vector<XmppUser> dummy;
2784 return dummy;
2785 }
2790 /**
2791 * Try to join a group
2792 */
2793 bool XmppClient::groupChatJoin(const DOMString &groupJid,
2794 const DOMString &nick,
2795 const DOMString &pass)
2796 {
2797 if (!checkConnect())
2798 return false;
2800 DOMString user = nick;
2801 if (user.size()<1)
2802 user = username;
2804 char *fmt =
2805 "<presence to='%s/%s'>"
2806 "<x xmlns='http://jabber.org/protocol/muc'/></presence>\n";
2807 if (!write(fmt, groupJid.c_str(), user.c_str()))
2808 return false;
2809 return true;
2810 }
2815 /**
2816 * Leave a group
2817 */
2818 bool XmppClient::groupChatLeave(const DOMString &groupJid,
2819 const DOMString &nick)
2820 {
2821 if (!checkConnect())
2822 return false;
2824 DOMString user = nick;
2825 if (user.size()<1)
2826 user = username;
2828 char *fmt =
2829 "<presence to='%s/%s' type='unavailable'>"
2830 "<x xmlns='http://jabber.org/protocol/muc'/></presence>\n";
2831 if (!write(fmt, groupJid.c_str(), user.c_str()))
2832 return false;
2833 return true;
2834 }
2839 /**
2840 * Send a message to a group
2841 */
2842 bool XmppClient::groupChatMessage(const DOMString &groupJid,
2843 const DOMString &msg)
2844 {
2845 if (!checkConnect())
2846 {
2847 return false;
2848 }
2850 DOMString xmlMsg = toXml(msg);
2852 char *fmt =
2853 "<message from='%s' to='%s' type='groupchat'>"
2854 "<body>%s</body></message>\n";
2855 if (!write(fmt, jid.c_str(), groupJid.c_str(), xmlMsg.c_str()))
2856 return false;
2857 return true;
2858 }
2863 /**
2864 * Send a message to an individual in a group
2865 */
2866 bool XmppClient::groupChatPrivateMessage(const DOMString &groupJid,
2867 const DOMString &toNick,
2868 const DOMString &msg)
2869 {
2870 if (!checkConnect())
2871 return false;
2873 DOMString xmlMsg = toXml(msg);
2875 char *fmt =
2876 "<message from='%s' to='%s/%s' type='chat'>"
2877 "<body>%s</body></message>\n";
2878 if (!write(fmt, jid.c_str(), groupJid.c_str(),
2879 toNick.c_str(), xmlMsg.c_str()))
2880 return false;
2881 return true;
2882 }
2887 /**
2888 * Change your presence within a group
2889 */
2890 bool XmppClient::groupChatPresence(const DOMString &groupJid,
2891 const DOMString &myNick,
2892 const DOMString &presence)
2893 {
2894 if (!checkConnect())
2895 return false;
2897 DOMString user = myNick;
2898 if (user.size()<1)
2899 user = username;
2901 DOMString xmlPresence = toXml(presence);
2903 char *fmt =
2904 "<presence from='%s' to='%s/%s' type='unavailable'>"
2905 "<x xmlns='http://jabber.org/protocol/muc'/></presence>\n";
2906 if (!write(fmt, jid.c_str(), groupJid.c_str(),
2907 user.c_str(), xmlPresence.c_str()))
2908 return true;
2909 return true;
2910 }
2916 //########################################################################
2917 //# S T R E A M S
2918 //########################################################################
2921 /**
2922 *
2923 */
2924 int XmppClient::outputStreamOpen(const DOMString &destId,
2925 const DOMString &streamIdArg)
2926 {
2927 int i;
2928 for (i=0; i<outputStreamCount ; i++)
2929 if (outputStreams[i]->getState() == STREAM_AVAILABLE)
2930 break;
2931 if (i>=outputStreamCount)
2932 {
2933 error("No available output streams");
2934 return -1;
2935 }
2936 int streamNr = i;
2937 XmppStream *outs = outputStreams[streamNr];
2939 outs->setState(STREAM_OPENING);
2941 char buf[32];
2942 snprintf(buf, 31, "inband%d", getMsgId());
2943 DOMString iqId = buf;
2945 DOMString streamId = streamIdArg;
2946 if (streamId.size()<1)
2947 {
2948 snprintf(buf, 31, "stream%d", getMsgId());
2949 DOMString streamId = buf;
2950 }
2951 outs->setIqId(iqId);
2952 outs->setStreamId(streamId);
2953 outs->setPeerId(destId);
2955 char *fmt =
2956 "<iq type='set' from='%s' to='%s' id='%s'>"
2957 "<open sid='%s' block-size='4096'"
2958 " xmlns='http://jabber.org/protocol/ibb'/></iq>\n";
2959 if (!write(fmt, jid.c_str(),
2960 destId.c_str(), iqId.c_str(),
2961 streamId.c_str()))
2962 {
2963 outs->reset();
2964 return -1;
2965 }
2967 int state = outs->getState();
2968 for (int tim=0 ; tim<20 ; tim++)
2969 {
2970 if (state == STREAM_OPEN)
2971 break;
2972 else if (state == STREAM_ERROR)
2973 {
2974 printf("ERROR\n");
2975 outs->reset();
2976 return -1;
2977 }
2978 Thread::sleep(1000);
2979 state = outs->getState();
2980 }
2981 if (state != STREAM_OPEN)
2982 {
2983 printf("TIMEOUT ERROR\n");
2984 outs->reset();
2985 return -1;
2986 }
2988 return streamNr;
2989 }
2991 /**
2992 *
2993 */
2994 int XmppClient::outputStreamWrite(int streamNr,
2995 const unsigned char *buf, unsigned long len)
2996 {
2997 XmppStream *outs = outputStreams[streamNr];
2999 unsigned long outLen = 0;
3000 unsigned char *p = (unsigned char *)buf;
3002 while (outLen < len)
3003 {
3004 unsigned long chunksize = 1024;
3005 if (chunksize + outLen > len)
3006 chunksize = len - outLen;
3008 Base64Encoder encoder;
3009 encoder.append(p, chunksize);
3010 DOMString b64data = encoder.finish();
3011 p += chunksize;
3012 outLen += chunksize;
3014 char *fmt =
3015 "<message from='%s' to='%s' id='msg%d'>"
3016 "<data xmlns='http://jabber.org/protocol/ibb' sid='%s' seq='%d'>"
3017 "%s"
3018 "</data>"
3019 "<amp xmlns='http://jabber.org/protocol/amp'>"
3020 "<rule condition='deliver-at' value='stored' action='error'/>"
3021 "<rule condition='match-resource' value='exact' action='error'/>"
3022 "</amp>"
3023 "</message>\n";
3024 if (!write(fmt, jid.c_str(),
3025 outs->getPeerId().c_str(),
3026 getMsgId(),
3027 outs->getStreamId().c_str(),
3028 outs->getSeqNr(),
3029 b64data.c_str()))
3030 {
3031 outs->reset();
3032 return -1;
3033 }
3034 pause(5000);
3035 }
3036 return outLen;
3037 }
3039 /**
3040 *
3041 */
3042 int XmppClient::outputStreamClose(int streamNr)
3043 {
3044 XmppStream *outs = outputStreams[streamNr];
3046 char buf[32];
3047 snprintf(buf, 31, "inband%d", getMsgId());
3048 DOMString iqId = buf;
3049 outs->setIqId(iqId);
3051 outs->setState(STREAM_CLOSING);
3052 char *fmt =
3053 "<iq type='set' from='%s' to='%s' id='%s'>"
3054 "<close sid='%s' xmlns='http://jabber.org/protocol/ibb'/></iq>\n";
3055 if (!write(fmt, jid.c_str(),
3056 outs->getPeerId().c_str(),
3057 iqId.c_str(),
3058 outs->getStreamId().c_str()))
3059 return false;
3061 int state = outs->getState();
3062 for (int tim=0 ; tim<20 ; tim++)
3063 {
3064 if (state == STREAM_CLOSED)
3065 break;
3066 else if (state == STREAM_ERROR)
3067 {
3068 printf("ERROR\n");
3069 outs->reset();
3070 return -1;
3071 }
3072 Thread::sleep(1000);
3073 state = outs->getState();
3074 }
3075 if (state != STREAM_CLOSED)
3076 {
3077 printf("TIMEOUT ERROR\n");
3078 outs->reset();
3079 return -1;
3080 }
3082 outs->reset();
3083 return 1;
3084 }
3087 /**
3088 *
3089 */
3090 int XmppClient::inputStreamOpen(const DOMString &fromJid, const DOMString &streamId,
3091 const DOMString &iqId)
3092 {
3093 int i;
3094 for (i=0 ; i<inputStreamCount ; i++)
3095 {
3096 if (inputStreams[i]->getState() == STREAM_AVAILABLE)
3097 break;
3098 }
3099 if (i>=inputStreamCount)
3100 {
3101 error("No available input streams");
3102 return -1;
3103 }
3104 int streamNr = i;
3105 XmppStream *ins = inputStreams[streamNr];
3106 ins->reset();
3107 ins->setPeerId(fromJid);
3108 ins->setState(STREAM_CLOSED);
3109 ins->setStreamId(streamId);
3111 int state = ins->getState();
3112 for (int tim=0 ; tim<20 ; tim++)
3113 {
3114 if (state == STREAM_OPENING)
3115 break;
3116 else if (state == STREAM_ERROR)
3117 {
3118 printf("ERROR\n");
3119 ins->reset();
3120 return -1;
3121 }
3122 Thread::sleep(1000);
3123 state = ins->getState();
3124 }
3125 if (state != STREAM_OPENING)
3126 {
3127 printf("TIMEOUT ERROR\n");
3128 ins->reset();
3129 return -1;
3130 }
3131 char *fmt =
3132 "<iq type='result' from='%s' to='%s' id='%s'/>\n";
3133 if (!write(fmt, jid.c_str(), fromJid.c_str(), ins->getIqId().c_str()))
3134 {
3135 return -1;
3136 }
3138 ins->setState(STREAM_OPEN);
3139 return streamNr;
3140 }
3144 /**
3145 *
3146 */
3147 int XmppClient::inputStreamAvailable(int streamNr)
3148 {
3149 XmppStream *ins = inputStreams[streamNr];
3150 return ins->available();
3151 }
3153 /**
3154 *
3155 */
3156 std::vector<unsigned char> XmppClient::inputStreamRead(int streamNr)
3157 {
3158 XmppStream *ins = inputStreams[streamNr];
3159 return ins->read();
3160 }
3162 /**
3163 *
3164 */
3165 bool XmppClient::inputStreamClosing(int streamNr)
3166 {
3167 XmppStream *ins = inputStreams[streamNr];
3168 if (ins->getState() == STREAM_CLOSING)
3169 return true;
3170 return false;
3171 }
3174 /**
3175 *
3176 */
3177 int XmppClient::inputStreamClose(int streamNr)
3178 {
3179 int ret=1;
3180 XmppStream *ins = inputStreams[streamNr];
3181 if (ins->getState() == STREAM_CLOSING)
3182 {
3183 char *fmt =
3184 "<iq type='result' from='%s' to='%s' id='%s'/>\n";
3185 if (!write(fmt, jid.c_str(), ins->getPeerId().c_str(),
3186 ins->getIqId().c_str()))
3187 {
3188 ret = -1;
3189 }
3190 }
3191 ins->reset();
3192 return ret;
3193 }
3200 //########################################################################
3201 //# FILE TRANSFERS
3202 //########################################################################
3205 /**
3206 *
3207 */
3208 bool XmppClient::fileSend(const DOMString &destJidArg,
3209 const DOMString &offeredNameArg,
3210 const DOMString &fileNameArg,
3211 const DOMString &descriptionArg)
3212 {
3213 DOMString destJid = destJidArg;
3214 DOMString offeredName = offeredNameArg;
3215 DOMString fileName = fileNameArg;
3216 DOMString description = descriptionArg;
3218 int i;
3219 for (i=0; i<fileSendCount ; i++)
3220 if (fileSends[i]->getState() == STREAM_AVAILABLE)
3221 break;
3222 if (i>=fileSendCount)
3223 {
3224 error("No available file send streams");
3225 return false;
3226 }
3227 int fileSendNr = i;
3228 XmppStream *outf = fileSends[fileSendNr];
3230 outf->setState(STREAM_OPENING);
3232 struct stat finfo;
3233 if (stat(fileName.c_str(), &finfo)<0)
3234 {
3235 error("Cannot stat file '%s' for sending", fileName.c_str());
3236 return false;
3237 }
3238 long fileLen = finfo.st_size;
3239 if (!fileLen > 1000000)
3240 {
3241 error("'%s' too large", fileName.c_str());
3242 return false;
3243 }
3244 if (!S_ISREG(finfo.st_mode))
3245 {
3246 error("'%s' is not a regular file", fileName.c_str());
3247 return false;
3248 }
3249 FILE *f = fopen(fileName.c_str(), "rb");
3250 if (!f)
3251 {
3252 error("cannot open '%s' for sending", fileName.c_str());
3253 return false;
3254 }
3255 unsigned char *sendBuf = (unsigned char *)malloc(fileLen+1);
3256 if (!sendBuf)
3257 {
3258 error("cannot cannot allocate send buffer for %s", fileName.c_str());
3259 return false;
3260 }
3261 for (long i=0 ; i<fileLen && !feof(f); i++)
3262 {
3263 sendBuf[i] = fgetc(f);
3264 }
3265 fclose(f);
3267 //## get the last path segment from the whole path
3268 if (offeredName.size()<1)
3269 {
3270 int slashPos = -1;
3271 for (unsigned int i=0 ; i<fileName.size() ; i++)
3272 {
3273 int ch = fileName[i];
3274 if (ch == '/' || ch == '\\')
3275 slashPos = i;
3276 }
3277 if (slashPos>=0 && slashPos<=(int)(fileName.size()-1))
3278 {
3279 offeredName = fileName.substr(slashPos+1,
3280 fileName.size()-slashPos-1);
3281 printf("offeredName:%s\n", offeredName.c_str());
3282 }
3283 }
3285 char buf[32];
3286 snprintf(buf, 31, "file%d", getMsgId());
3287 DOMString iqId = buf;
3288 outf->setIqId(iqId);
3290 snprintf(buf, 31, "stream%d", getMsgId());
3291 DOMString streamId = buf;
3292 //outf->setStreamId(streamId);
3294 DOMString hash = Md5::hashHex(sendBuf, fileLen);
3295 printf("Hash:%s\n", hash.c_str());
3297 outf->setPeerId(destJid);
3299 char dtgBuf[81];
3300 struct tm *timeVal = gmtime(&(finfo.st_mtime));
3301 strftime(dtgBuf, 80, "%Y-%m-%dT%H:%M:%Sz", timeVal);
3303 char *fmt =
3304 "<iq type='set' id='%s' to='%s'>"
3305 "<si xmlns='http://jabber.org/protocol/si' id='%s'"
3306 " mime-type='text/plain'"
3307 " profile='http://jabber.org/protocol/si/profile/file-transfer'>"
3308 "<file xmlns='http://jabber.org/protocol/si/profile/file-transfer'"
3309 " name='%s' size='%d' hash='%s' date='%s'><desc>%s</desc></file>"
3310 "<feature xmlns='http://jabber.org/protocol/feature-neg'>"
3311 "<x xmlns='jabber:x:data' type='form'>"
3312 "<field var='stream-method' type='list-single'>"
3313 //"<option><value>http://jabber.org/protocol/bytestreams</value></option>"
3314 "<option><value>http://jabber.org/protocol/ibb</value></option>"
3315 "</field></x></feature></si></iq>\n";
3316 if (!write(fmt, iqId.c_str(), destJid.c_str(),
3317 streamId.c_str(), offeredName.c_str(), fileLen,
3318 hash.c_str(), dtgBuf, description.c_str()))
3319 {
3320 free(sendBuf);
3321 return false;
3322 }
3324 int state = outf->getState();
3325 for (int tim=0 ; tim<20 ; tim++)
3326 {
3327 printf("##### waiting for open\n");
3328 if (state == STREAM_OPEN)
3329 {
3330 outf->reset();
3331 break;
3332 }
3333 else if (state == STREAM_ERROR)
3334 {
3335 printf("ERROR\n");
3336 outf->reset();
3337 return false;
3338 }
3339 Thread::sleep(1000);
3340 state = outf->getState();
3341 }
3342 if (state != STREAM_OPEN)
3343 {
3344 printf("TIMEOUT ERROR\n");
3345 outf->reset();
3346 return false;
3347 }
3349 //free up this reqource
3350 outf->reset();
3352 int streamNr = outputStreamOpen(destJid, streamId);
3353 if (streamNr<0)
3354 {
3355 error("cannot open output stream %s", streamId.c_str());
3356 outf->reset();
3357 return false;
3358 }
3360 int ret = outputStreamWrite(streamNr, sendBuf, fileLen);
3362 if (ret<0)
3363 {
3364 }
3366 outputStreamClose(streamNr);
3368 free(sendBuf);
3369 return true;
3370 }
3373 class FileSendThread : public Thread
3374 {
3375 public:
3377 FileSendThread(XmppClient &par,
3378 const DOMString &destJidArg,
3379 const DOMString &offeredNameArg,
3380 const DOMString &fileNameArg,
3381 const DOMString &descriptionArg) : client(par)
3382 {
3383 destJid = destJidArg;
3384 offeredName = offeredNameArg;
3385 fileName = fileNameArg;
3386 description = descriptionArg;
3387 }
3389 virtual ~FileSendThread() {}
3391 void run()
3392 {
3393 client.fileSend(destJid, offeredName,
3394 fileName, description);
3395 }
3397 private:
3399 XmppClient &client;
3400 DOMString destJid;
3401 DOMString offeredName;
3402 DOMString fileName;
3403 DOMString description;
3404 };
3406 /**
3407 *
3408 */
3409 bool XmppClient::fileSendBackground(const DOMString &destJid,
3410 const DOMString &offeredName,
3411 const DOMString &fileName,
3412 const DOMString &description)
3413 {
3414 FileSendThread thread(*this, destJid, offeredName,
3415 fileName, description);
3416 thread.start();
3417 return true;
3418 }
3421 /**
3422 *
3423 */
3424 bool XmppClient::fileReceive(const DOMString &fromJid,
3425 const DOMString &iqId,
3426 const DOMString &streamId,
3427 const DOMString &fileName,
3428 long fileSize,
3429 const DOMString &fileHash)
3430 {
3431 char *fmt =
3432 "<iq type='result' to='%s' id='%s'>"
3433 "<si xmlns='http://jabber.org/protocol/si'>"
3434 "<file xmlns='http://jabber.org/protocol/si/profile/file-transfer'/>"
3435 "<feature xmlns='http://jabber.org/protocol/feature-neg'>"
3436 "<x xmlns='jabber:x:data' type='submit'>"
3437 "<field var='stream-method'>"
3438 "<value>http://jabber.org/protocol/ibb</value>"
3439 "</field></x></feature></si></iq>\n";
3440 if (!write(fmt, fromJid.c_str(), iqId.c_str()))
3441 {
3442 return false;
3443 }
3445 int streamNr = inputStreamOpen(fromJid, streamId, iqId);
3446 if (streamNr < 0)
3447 {
3448 return false;
3449 }
3452 Md5 md5;
3453 FILE *f = fopen(fileName.c_str(), "wb");
3454 if (!f)
3455 {
3456 return false;
3457 }
3459 while (true)
3460 {
3461 if (inputStreamAvailable(streamNr)<1)
3462 {
3463 if (inputStreamClosing(streamNr))
3464 break;
3465 pause(100);
3466 continue;
3467 }
3468 std::vector<unsigned char> ret = inputStreamRead(streamNr);
3469 std::vector<unsigned char>::iterator iter;
3470 for (iter=ret.begin() ; iter!=ret.end() ; iter++)
3471 {
3472 unsigned char ch = *iter;
3473 md5.append(&ch, 1);
3474 fwrite(&ch, 1, 1, f);
3475 }
3476 }
3478 inputStreamClose(streamNr);
3479 fclose(f);
3481 DOMString hash = md5.finishHex();
3482 printf("received file hash:%s\n", hash.c_str());
3484 return true;
3485 }
3489 class FileReceiveThread : public Thread
3490 {
3491 public:
3493 FileReceiveThread(XmppClient &par,
3494 const DOMString &fromJidArg,
3495 const DOMString &iqIdArg,
3496 const DOMString &streamIdArg,
3497 const DOMString &fileNameArg,
3498 long fileSizeArg,
3499 const DOMString &fileHashArg) : client(par)
3500 {
3501 fromJid = fromJidArg;
3502 iqId = iqIdArg;
3503 streamId = streamIdArg;
3504 fileName = fileNameArg;
3505 fileSize = fileSizeArg;
3506 fileHash = fileHashArg;
3507 }
3509 virtual ~FileReceiveThread() {}
3511 void run()
3512 {
3513 client.fileReceive(fromJid, iqId, streamId,
3514 fileName, fileSize, fileHash);
3515 }
3517 private:
3519 XmppClient &client;
3520 DOMString fromJid;
3521 DOMString iqId;
3522 DOMString streamId;
3523 DOMString fileName;
3524 long fileSize;
3525 DOMString fileHash;
3526 };
3528 /**
3529 *
3530 */
3531 bool XmppClient::fileReceiveBackground(const DOMString &fromJid,
3532 const DOMString &iqId,
3533 const DOMString &streamId,
3534 const DOMString &fileName,
3535 long fileSize,
3536 const DOMString &fileHash)
3537 {
3538 FileReceiveThread thread(*this, fromJid, iqId, streamId,
3539 fileName, fileSize, fileHash);
3540 thread.start();
3541 return true;
3542 }
3546 //########################################################################
3547 //# X M P P G R O U P C H A T
3548 //########################################################################
3550 /**
3551 *
3552 */
3553 XmppGroupChat::XmppGroupChat(const DOMString &groupJidArg)
3554 {
3555 groupJid = groupJidArg;
3556 }
3558 /**
3559 *
3560 */
3561 XmppGroupChat::XmppGroupChat(const XmppGroupChat &other)
3562 {
3563 groupJid = other.groupJid;
3564 userList = other.userList;
3565 }
3567 /**
3568 *
3569 */
3570 XmppGroupChat::~XmppGroupChat()
3571 {
3572 }
3575 /**
3576 *
3577 */
3578 DOMString XmppGroupChat::getGroupJid()
3579 {
3580 return groupJid;
3581 }
3584 void XmppGroupChat::userAdd(const DOMString &nick,
3585 const DOMString &jid)
3586 {
3587 std::vector<XmppUser>::iterator iter;
3588 for (iter= userList.begin() ; iter!=userList.end() ; iter++)
3589 {
3590 if (iter->nick == nick)
3591 return;
3592 }
3593 XmppUser user(jid, nick);
3594 userList.push_back(user);
3595 }
3597 void XmppGroupChat::userShow(const DOMString &nick,
3598 const DOMString &show)
3599 {
3600 DOMString theShow = show;
3601 if (theShow == "")
3602 theShow = "available"; // a join message will now have a show
3603 std::vector<XmppUser>::iterator iter;
3604 for (iter= userList.begin() ; iter!=userList.end() ; iter++)
3605 {
3606 if (iter->nick == nick)
3607 iter->show = theShow;
3608 }
3609 }
3611 void XmppGroupChat::userDelete(const DOMString &nick)
3612 {
3613 std::vector<XmppUser>::iterator iter;
3614 for (iter= userList.begin() ; iter!=userList.end() ; )
3615 {
3616 if (iter->nick == nick)
3617 iter = userList.erase(iter);
3618 else
3619 iter++;
3620 }
3621 }
3623 std::vector<XmppUser> XmppGroupChat::getUserList() const
3624 {
3625 return userList;
3626 }
3636 } //namespace Pedro
3637 //########################################################################
3638 //# E N D O F F I L E
3639 //########################################################################