3baeeee0636e518bb79ae73f153e4a706368c39d
1 /*
2 * Implementation the Pedro mini-XMPP client
3 *
4 * Authors:
5 * Bob Jamison
6 *
7 * Copyright (C) 2005-2008 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 <algorithm>
26 #include <cstdio>
27 #include <stdarg.h>
28 #include <stdlib.h>
30 #include <sys/stat.h>
32 #include <time.h>
34 #include "pedroxmpp.h"
35 #include "pedrodom.h"
36 #include "pedroutil.h"
38 #include <map>
42 namespace Pedro
43 {
46 //########################################################################
47 //########################################################################
48 //# X M P P E V E N T
49 //########################################################################
50 //########################################################################
53 XmppEvent::XmppEvent(int type)
54 {
55 eventType = type;
56 presence = false;
57 dom = NULL;
58 }
60 XmppEvent::XmppEvent(const XmppEvent &other)
61 {
62 assign(other);
63 }
65 XmppEvent &XmppEvent::operator=(const XmppEvent &other)
66 {
67 assign(other);
68 return (*this);
69 }
71 XmppEvent::~XmppEvent()
72 {
73 if (dom)
74 delete dom;
75 }
77 void XmppEvent::assign(const XmppEvent &other)
78 {
79 eventType = other.eventType;
80 presence = other.presence;
81 status = other.status;
82 show = other.show;
83 to = other.to;
84 from = other.from;
85 group = other.group;
86 data = other.data;
87 fileName = other.fileName;
88 fileDesc = other.fileDesc;
89 fileSize = other.fileSize;
90 fileHash = other.fileHash;
91 setDOM(other.dom);
92 }
94 int XmppEvent::getType() const
95 {
96 return eventType;
97 }
99 DOMString XmppEvent::getIqId() const
100 {
101 return iqId;
102 }
104 void XmppEvent::setIqId(const DOMString &val)
105 {
106 iqId = val;
107 }
109 DOMString XmppEvent::getStreamId() const
110 {
111 return streamId;
112 }
114 void XmppEvent::setStreamId(const DOMString &val)
115 {
116 streamId = val;
117 }
119 bool XmppEvent::getPresence() const
120 {
121 return presence;
122 }
124 void XmppEvent::setPresence(bool val)
125 {
126 presence = val;
127 }
129 DOMString XmppEvent::getShow() const
130 {
131 return show;
132 }
134 void XmppEvent::setShow(const DOMString &val)
135 {
136 show = val;
137 }
139 DOMString XmppEvent::getStatus() const
140 {
141 return status;
142 }
144 void XmppEvent::setStatus(const DOMString &val)
145 {
146 status = val;
147 }
149 DOMString XmppEvent::getTo() const
150 {
151 return to;
152 }
154 void XmppEvent::setTo(const DOMString &val)
155 {
156 to = val;
157 }
159 DOMString XmppEvent::getFrom() const
160 {
161 return from;
162 }
164 void XmppEvent::setFrom(const DOMString &val)
165 {
166 from = val;
167 }
169 DOMString XmppEvent::getGroup() const
170 {
171 return group;
172 }
174 void XmppEvent::setGroup(const DOMString &val)
175 {
176 group = val;
177 }
179 DOMString XmppEvent::getData() const
180 {
181 return data;
182 }
184 void XmppEvent::setData(const DOMString &val)
185 {
186 data = val;
187 }
189 DOMString XmppEvent::getFileName() const
190 {
191 return fileName;
192 }
194 void XmppEvent::setFileName(const DOMString &val)
195 {
196 fileName = val;
197 }
199 DOMString XmppEvent::getFileDesc() const
200 {
201 return fileDesc;
202 }
204 void XmppEvent::setFileDesc(const DOMString &val)
205 {
206 fileDesc = val;
207 }
209 long XmppEvent::getFileSize() const
210 {
211 return fileSize;
212 }
214 void XmppEvent::setFileSize(long val)
215 {
216 fileSize = val;
217 }
219 DOMString XmppEvent::getFileHash() const
220 {
221 return fileHash;
222 }
224 void XmppEvent::setFileHash(const DOMString &val)
225 {
226 fileHash = val;
227 }
229 Element *XmppEvent::getDOM() const
230 {
231 return dom;
232 }
234 void XmppEvent::setDOM(const Element *val)
235 {
236 if (!val)
237 dom = NULL;
238 else
239 dom = ((Element *)val)->clone();
240 }
243 std::vector<XmppUser> XmppEvent::getUserList() const
244 {
245 return userList;
246 }
248 void XmppEvent::setUserList(const std::vector<XmppUser> &val)
249 {
250 userList = val;
251 }
261 //########################################################################
262 //########################################################################
263 //# X M P P E V E N T T A R G E T
264 //########################################################################
265 //########################################################################
268 //###########################
269 //# CONSTRUCTORS
270 //###########################
272 XmppEventTarget::XmppEventTarget()
273 {
274 eventQueueEnabled = false;
275 }
278 XmppEventTarget::XmppEventTarget(const XmppEventTarget &other)
279 {
280 listeners = other.listeners;
281 eventQueueEnabled = other.eventQueueEnabled;
282 }
284 XmppEventTarget::~XmppEventTarget()
285 {
286 }
289 //###########################
290 //# M E S S A G E S
291 //###########################
293 /**
294 * Print a printf()-like formatted error message
295 */
296 void XmppEventTarget::error(const char *fmt, ...)
297 {
298 va_list args;
299 va_start(args,fmt);
300 gchar * buffer = g_strdup_vprintf(fmt, args);
301 va_end(args) ;
302 fprintf(stderr, "Error:%s\n", buffer);
303 XmppEvent evt(XmppEvent::EVENT_ERROR);
304 evt.setData(buffer);
305 dispatchXmppEvent(evt);
306 g_free(buffer);
307 }
311 /**
312 * Print a printf()-like formatted trace message
313 */
314 void XmppEventTarget::status(const char *fmt, ...)
315 {
316 va_list args;
317 va_start(args,fmt);
318 gchar * buffer = g_strdup_vprintf(fmt, args);
319 va_end(args) ;
320 //printf("Status:%s\n", buffer);
321 XmppEvent evt(XmppEvent::EVENT_STATUS);
322 evt.setData(buffer);
323 dispatchXmppEvent(evt);
324 g_free(buffer);
325 }
329 //###########################
330 //# L I S T E N E R S
331 //###########################
333 void XmppEventTarget::dispatchXmppEvent(const XmppEvent &event)
334 {
335 std::vector<XmppEventListener *>::iterator iter;
336 for (iter = listeners.begin(); iter != listeners.end() ; iter++)
337 (*iter)->processXmppEvent(event);
338 if (eventQueueEnabled)
339 eventQueue.push_back(event);
340 }
342 void XmppEventTarget::addXmppEventListener(const XmppEventListener &listener)
343 {
344 XmppEventListener *lsnr = (XmppEventListener *)&listener;
345 std::vector<XmppEventListener *>::iterator iter;
346 for (iter = listeners.begin(); iter != listeners.end() ; iter++)
347 if (*iter == lsnr)
348 return;
349 listeners.push_back(lsnr);
350 }
352 void XmppEventTarget::removeXmppEventListener(const XmppEventListener &listener)
353 {
354 XmppEventListener *lsnr = (XmppEventListener *)&listener;
355 std::vector<XmppEventListener *>::iterator iter;
356 for (iter = listeners.begin(); iter != listeners.end() ; iter++)
357 if (*iter == lsnr)
358 listeners.erase(iter);
359 }
361 void XmppEventTarget::clearXmppEventListeners()
362 {
363 listeners.clear();
364 }
367 //###########################
368 //# E V E N T Q U E U E
369 //###########################
371 void XmppEventTarget::eventQueueEnable(bool val)
372 {
373 eventQueueEnabled = val;
374 if (!eventQueueEnabled)
375 eventQueue.clear();
376 }
378 int XmppEventTarget::eventQueueAvailable()
379 {
380 return eventQueue.size();
381 }
383 XmppEvent XmppEventTarget::eventQueuePop()
384 {
385 if (!eventQueueEnabled || eventQueue.size()<1)
386 {
387 XmppEvent dummy(XmppEvent::EVENT_NONE);
388 return dummy;
389 }
390 XmppEvent event = *(eventQueue.begin());
391 eventQueue.erase(eventQueue.begin());
392 return event;
393 }
399 //########################################################################
400 //########################################################################
401 //# X M P P S T R E A M
402 //########################################################################
403 //########################################################################
406 /**
407 *
408 */
409 class XmppStream
410 {
411 public:
413 /**
414 *
415 */
416 XmppStream()
417 { reset(); }
419 /**
420 *
421 */
422 XmppStream(const XmppStream &other)
423 { assign(other); }
425 /**
426 *
427 */
428 XmppStream &operator=(const XmppStream &other)
429 { assign(other); return *this; }
431 /**
432 *
433 */
434 virtual ~XmppStream()
435 {}
437 /**
438 *
439 */
440 virtual void reset()
441 {
442 state = XmppClient::STREAM_AVAILABLE;
443 seqNr = 0;
444 messageId = "";
445 sourceId = "";
446 data.clear();
447 }
449 /**
450 *
451 */
452 virtual int getState()
453 { return state; }
455 /**
456 *
457 */
458 virtual void setState(int val)
459 { state = val; }
461 /**
462 *
463 */
464 virtual DOMString getStreamId()
465 { return streamId; }
467 /**
468 *
469 */
470 void setStreamId(const DOMString &val)
471 { streamId = val; }
473 /**
474 *
475 */
476 virtual DOMString getMessageId()
477 { return messageId; }
479 /**
480 *
481 */
482 void setMessageId(const DOMString &val)
483 { messageId = val; }
485 /**
486 *
487 */
488 virtual int getSeqNr()
489 {
490 seqNr++;
491 if (seqNr >= 65535)
492 seqNr = 0;
493 return seqNr;
494 }
496 /**
497 *
498 */
499 virtual DOMString getPeerId()
500 { return sourceId; }
502 /**
503 *
504 */
505 virtual void setPeerId(const DOMString &val)
506 { sourceId = val; }
508 /**
509 *
510 */
511 int available()
512 { return data.size(); }
514 /**
515 *
516 */
517 void receiveData(std::vector<unsigned char> &newData)
518 {
519 std::vector<unsigned char>::iterator iter;
520 for (iter=newData.begin() ; iter!=newData.end() ; iter++)
521 data.push_back(*iter);
522 }
524 /**
525 *
526 */
527 std::vector<unsigned char> read()
528 {
529 if (state != XmppClient::STREAM_OPEN)
530 {
531 std::vector<unsigned char>dummy;
532 return dummy;
533 }
534 std::vector<unsigned char> ret = data;
535 data.clear();
536 return ret;
537 }
539 private:
541 void assign(const XmppStream &other)
542 {
543 streamId = other.streamId;
544 messageId = other.messageId;
545 sourceId = other.sourceId;
546 state = other.state;
547 seqNr = other.seqNr;
548 data = other.data;
549 }
552 DOMString streamId;
554 DOMString messageId;
556 DOMString sourceId;
558 int state;
560 long seqNr;
562 std::vector<unsigned char> data;
563 };
574 //########################################################################
575 //########################################################################
576 //# X M P P C L I E N T
577 //########################################################################
578 //########################################################################
580 class ReceiverThread : public Runnable
581 {
582 public:
584 ReceiverThread(XmppClient &par) : client(par) {}
586 virtual ~ReceiverThread() {}
588 void run()
589 { client.receiveAndProcessLoop(); }
591 private:
593 XmppClient &client;
594 };
600 //########################################################################
601 //# CONSTRUCTORS
602 //########################################################################
604 XmppClient::XmppClient()
605 {
606 init();
607 }
610 XmppClient::XmppClient(const XmppClient &other) : XmppEventTarget(other)
611 {
612 init();
613 assign(other);
614 }
616 void XmppClient::assign(const XmppClient &other)
617 {
618 msgId = other.msgId;
619 host = other.host;
620 realm = other.realm;
621 port = other.port;
622 username = other.username;
623 password = other.password;
624 resource = other.resource;
625 connected = other.connected;
626 doRegister = other.doRegister;
627 groupChats = other.groupChats;
628 streamPacket = other.streamPacket;
629 }
632 void XmppClient::init()
633 {
634 sock = new TcpSocket();
635 msgId = 0;
636 connected = false;
637 doRegister = false;
638 streamPacket = "message";
640 }
642 XmppClient::~XmppClient()
643 {
644 disconnect();
645 delete sock;
646 std::map<DOMString, XmppStream *>::iterator iter;
647 for (iter = outputStreams.begin(); iter!=outputStreams.end() ; iter++)
648 delete iter->second;
649 for (iter = inputStreams.begin(); iter!=inputStreams.end() ; iter++)
650 delete iter->second;
651 for (iter = fileSends.begin(); iter!=fileSends.end() ; iter++)
652 delete iter->second;
653 groupChatsClear();
654 }
661 //########################################################################
662 //# UTILILY
663 //########################################################################
665 /**
666 *
667 */
668 bool XmppClient::pause(unsigned long millis)
669 {
670 Thread::sleep(millis);
671 return true;
672 }
675 static int strIndex(const DOMString &str, const char *key)
676 {
677 unsigned int p = str.find(key);
678 if (p == str.npos)
679 return -1;
680 return p;
681 }
684 DOMString XmppClient::toXml(const DOMString &str)
685 {
686 return Parser::encode(str);
687 }
691 static DOMString trim(const DOMString &str)
692 {
693 unsigned int i;
694 for (i=0 ; i<str.size() ; i++)
695 if (!isspace(str[i]))
696 break;
697 int start = i;
698 for (i=str.size() ; i>0 ; i--)
699 if (!isspace(str[i-1]))
700 break;
701 int end = i;
702 if (start>=end)
703 return "";
704 return str.substr(start, end);
705 }
711 //########################################################################
712 //# VARIABLES (ones that need special handling)
713 //########################################################################
715 /**
716 *
717 */
718 DOMString XmppClient::getUsername()
719 {
720 return username;
721 }
723 /**
724 *
725 */
726 void XmppClient::setUsername(const DOMString &val)
727 {
728 int p = strIndex(val, "@");
729 if (p > 0)
730 {
731 username = val.substr(0, p);
732 realm = val.substr(p+1, jid.size()-p-1);
733 }
734 else
735 {
736 realm = host;
737 username = val;
738 }
739 }
748 //########################################################################
749 //# RECEIVING
750 //########################################################################
753 DOMString XmppClient::readStanza()
754 {
756 int openCount = 0;
757 bool inTag = false;
758 bool slashSeen = false;
759 bool trivialTag = false;
760 bool querySeen = false;
761 bool inQuote = false;
762 bool textSeen = false;
763 DOMString buf;
766 time_t timeout = time((time_t *)0) + 180;
768 while (true)
769 {
770 int ch = sock->read();
771 //printf("%c", ch); fflush(stdout);
772 if (ch<0)
773 {
774 if (ch == -2) //a simple timeout, not an error
775 {
776 //Since we are timed out, let's assume that we
777 //are between chunks of text. Let's reset all states.
778 //printf("-----#### Timeout\n");
779 time_t currentTime = time((time_t *)0);
780 if (currentTime > timeout)
781 {
782 timeout = currentTime + 180;
783 if (!write("\n"))
784 {
785 error("ping send error");
786 disconnect();
787 return "";
788 }
789 }
790 continue;
791 }
792 else
793 {
794 keepGoing = false;
795 if (!sock->isConnected())
796 {
797 disconnect();
798 return "";
799 }
800 else
801 {
802 error("socket read error: %s", sock->getLastError().c_str());
803 disconnect();
804 return "";
805 }
806 }
807 }
808 buf.push_back(ch);
809 if (ch == '<')
810 {
811 inTag = true;
812 slashSeen = false;
813 querySeen = false;
814 inQuote = false;
815 textSeen = false;
816 trivialTag = false;
817 }
818 else if (ch == '>')
819 {
820 if (!inTag) //unescaped '>' in pcdata? horror
821 continue;
822 inTag = false;
823 if (!trivialTag && !querySeen)
824 {
825 if (slashSeen)
826 openCount--;
827 else
828 openCount++;
829 }
830 //printf("# openCount:%d t:%d q:%d\n",
831 // openCount, trivialTag, querySeen);
832 //check if we are 'balanced', but not a <?version?> tag
833 if (openCount <= 0 && !querySeen)
834 {
835 break;
836 }
837 //we know that this one will be open-ended
838 if (strIndex(buf, "<stream:stream") >= 0)
839 {
840 buf.append("</stream:stream>");
841 break;
842 }
843 }
844 else if (ch == '/')
845 {
846 if (inTag && !inQuote)
847 {
848 slashSeen = true;
849 if (textSeen) // <tagName/> <--looks like this
850 trivialTag = true;
851 }
852 }
853 else if (ch == '?')
854 {
855 if (inTag && !inQuote)
856 querySeen = true;
857 }
858 else if (ch == '"' || ch == '\'')
859 {
860 if (inTag)
861 inQuote = !inQuote;
862 }
863 else
864 {
865 if (inTag && !inQuote && !isspace(ch))
866 textSeen = true;
867 }
868 }
869 return buf;
870 }
874 static bool isGroupChat(Element *root)
875 {
876 if (!root)
877 return false;
878 ElementList elems = root->findElements("x");
879 for (unsigned int i=0 ; i<elems.size() ; i++)
880 {
881 DOMString xmlns = elems[i]->getAttribute("xmlns");
882 //printf("### XMLNS ### %s\n", xmlns.c_str());
883 if (strIndex(xmlns, "http://jabber.org/protocol/muc") >=0 )
884 return true;
885 }
886 return false;
887 }
892 static bool parseJid(const DOMString &fullJid,
893 DOMString &jid, DOMString &resource)
894 {
895 DOMString str = fullJid;
896 jid.clear();
897 resource.clear();
898 unsigned int p = str.size();
899 unsigned int p2 = str.rfind('/', p);
900 if (p2 != str.npos)
901 {
902 resource = str.substr(p2+1, p-(p2+1));
903 str = str.substr(0, p);
904 p = p2;
905 }
906 jid = str.substr(0, p);
907 printf("fullJid:%s jid:%s rsrc:%s\n",
908 fullJid.c_str(), jid.c_str(), resource.c_str());
909 return true;
910 }
915 bool XmppClient::processMessage(Element *root)
916 {
917 DOMString from = root->getTagAttribute("message", "from");
918 DOMString to = root->getTagAttribute("message", "to");
919 DOMString type = root->getTagAttribute("message", "type");
921 //####Check for embedded namespaces here
922 //### FILE TRANSFERS
923 if (processFileMessage(root))
924 return true;
926 //### STREAMS
927 if (processInBandByteStreamMessage(root))
928 return true;
931 //#### NORMAL MESSAGES
932 DOMString subject = root->getTagValue("subject");
933 DOMString body = root->getTagValue("body");
934 DOMString thread = root->getTagValue("thread");
935 //##rfc 3921, para 2.4. ignore if no recognizable info
936 //if (subject.size() < 1 && thread.size()<1)
937 // return true;
939 if (type == "groupchat")
940 {
941 DOMString fromGid;
942 DOMString fromNick;
943 parseJid(from, fromGid, fromNick);
944 //printf("fromGid:%s fromNick:%s\n",
945 // fromGid.c_str(), fromNick.c_str());
946 DOMString toGid;
947 DOMString toNick;
948 parseJid(to, toGid, toNick);
949 //printf("toGid:%s toNick:%s\n",
950 // toGid.c_str(), toNick.c_str());
952 if (fromNick.size() > 0)//normal group message
953 {
954 XmppEvent event(XmppEvent::EVENT_MUC_MESSAGE);
955 event.setGroup(fromGid);
956 event.setFrom(fromNick);
957 event.setData(body);
958 event.setDOM(root);
959 dispatchXmppEvent(event);
960 }
961 else // from the server itself
962 {
963 //note the space before, so it doesnt match 'unlocked'
964 if (strIndex(body, " locked") >= 0)
965 {
966 printf("LOCKED!! ;)\n");
967 const char *fmt =
968 "<iq id='create%d' to='%s' type='set'>"
969 "<query xmlns='http://jabber.org/protocol/muc#owner'>"
970 "<x xmlns='jabber:x:data' type='submit'/>"
971 "</query></iq>\n";
972 if (!write(fmt, msgId++, fromGid.c_str()))
973 return false;
974 }
975 }
976 }
977 else
978 {
979 XmppEvent event(XmppEvent::EVENT_MESSAGE);
980 event.setFrom(from);
981 event.setData(body);
982 event.setDOM(root);
983 dispatchXmppEvent(event);
984 }
986 return true;
987 }
992 bool XmppClient::processPresence(Element *root)
993 {
995 DOMString fullJid = root->getTagAttribute("presence", "from");
996 DOMString to = root->getTagAttribute("presence", "to");
997 DOMString presenceStr = root->getTagAttribute("presence", "type");
998 bool presence = true;
999 if (presenceStr == "unavailable")
1000 presence = false;
1001 DOMString status = root->getTagValue("status");
1002 DOMString show = root->getTagValue("show");
1004 if (isGroupChat(root))
1005 {
1006 DOMString fromGid;
1007 DOMString fromNick;
1008 parseJid(fullJid, fromGid, fromNick);
1009 //printf("fromGid:%s fromNick:%s\n",
1010 // fromGid.c_str(), fromNick.c_str());
1011 DOMString item_jid = root->getTagAttribute("item", "jid");
1012 if (item_jid == jid || item_jid == to) //Me
1013 {
1014 if (presence)
1015 {
1016 groupChatCreate(fromGid);
1017 groupChatUserAdd(fromGid, fromNick, "");
1018 groupChatUserShow(fromGid, fromNick, "available");
1020 XmppEvent event(XmppEvent::EVENT_MUC_JOIN);
1021 event.setGroup(fromGid);
1022 event.setFrom(fromNick);
1023 event.setPresence(presence);
1024 event.setShow(show);
1025 event.setStatus(status);
1026 dispatchXmppEvent(event);
1027 }
1028 else
1029 {
1030 groupChatDelete(fromGid);
1031 groupChatUserDelete(fromGid, fromNick);
1033 XmppEvent event(XmppEvent::EVENT_MUC_LEAVE);
1034 event.setGroup(fromGid);
1035 event.setFrom(fromNick);
1036 event.setPresence(presence);
1037 event.setShow(show);
1038 event.setStatus(status);
1039 dispatchXmppEvent(event);
1040 }
1041 }
1042 else // someone else
1043 {
1044 if (presence)
1045 {
1046 groupChatUserAdd(fromGid, fromNick, "");
1047 }
1048 else
1049 groupChatUserDelete(fromGid, fromNick);
1050 groupChatUserShow(fromGid, fromNick, show);
1051 XmppEvent event(XmppEvent::EVENT_MUC_PRESENCE);
1052 event.setGroup(fromGid);
1053 event.setFrom(fromNick);
1054 event.setPresence(presence);
1055 event.setShow(show);
1056 event.setStatus(status);
1057 dispatchXmppEvent(event);
1058 }
1059 }
1060 else
1061 {
1062 DOMString shortJid;
1063 DOMString dummy;
1064 parseJid(fullJid, shortJid, dummy);
1065 rosterShow(shortJid, show); //users in roster do not have resource
1067 XmppEvent event(XmppEvent::EVENT_PRESENCE);
1068 event.setFrom(fullJid);
1069 event.setPresence(presence);
1070 event.setShow(show);
1071 event.setStatus(status);
1072 dispatchXmppEvent(event);
1073 }
1075 return true;
1076 }
1080 bool XmppClient::processIq(Element *root)
1081 {
1082 DOMString from = root->getTagAttribute("iq", "from");
1083 DOMString id = root->getTagAttribute("iq", "id");
1084 DOMString type = root->getTagAttribute("iq", "type");
1085 DOMString xmlns = root->getTagAttribute("query", "xmlns");
1087 if (id.size()<1)
1088 return true;
1090 //Group chat
1091 if (strIndex(xmlns, "http://jabber.org/protocol/muc") >=0 )
1092 {
1093 printf("results of MUC query\n");
1094 }
1095 //printf("###IQ xmlns:%s\n", xmlns.c_str());
1097 //### FILE TRANSFERS
1098 if (processFileMessage(root))
1099 return true;
1101 //### STREAMS
1102 if (processInBandByteStreamMessage(root))
1103 return true;
1106 //###Normal Roster stuff
1107 if (root->getTagAttribute("query", "xmlns") == "jabber:iq:roster")
1108 {
1109 roster.clear();
1110 ElementList elems = root->findElements("item");
1111 for (unsigned int i=0 ; i<elems.size() ; i++)
1112 {
1113 Element *item = elems[i];
1114 DOMString userJid = item->getAttribute("jid");
1115 DOMString name = item->getAttribute("name");
1116 DOMString subscription = item->getAttribute("subscription");
1117 DOMString group = item->getTagValue("group");
1118 //printf("jid:%s name:%s sub:%s group:%s\n", userJid.c_str(), name.c_str(),
1119 // subscription.c_str(), group.c_str());
1120 XmppUser user(userJid, name, subscription, group);
1121 roster.push_back(user);
1122 }
1123 XmppEvent event(XmppEvent::XmppEvent::EVENT_ROSTER);
1124 dispatchXmppEvent(event);
1125 }
1127 else if (id.find("regnew") != id.npos)
1128 {
1130 }
1132 else if (id.find("regpass") != id.npos)
1133 {
1134 ElementList list = root->findElements("error");
1135 if (list.size()==0)
1136 {
1137 XmppEvent evt(XmppEvent::EVENT_REGISTRATION_CHANGE_PASS);
1138 evt.setTo(username);
1139 evt.setFrom(host);
1140 dispatchXmppEvent(evt);
1141 return true;
1142 }
1144 Element *errElem = list[0];
1145 DOMString errMsg = "Password change error: ";
1146 if (errElem->findElements("bad-request").size()>0)
1147 {
1148 errMsg.append("password change does not contain complete information");
1149 }
1150 else if (errElem->findElements("not-authorized").size()>0)
1151 {
1152 errMsg.append("server does not consider the channel safe "
1153 "enough to enable a password change");
1154 }
1155 else if (errElem->findElements("not-allowed").size()>0)
1156 {
1157 errMsg.append("server does not allow password changes");
1158 }
1159 else if (errElem->findElements("unexpected-request").size()>0)
1160 {
1161 errMsg.append(
1162 "IQ set does not contain a 'from' address because "
1163 "the entity is not registered with the server");
1164 }
1165 error("%s", errMsg.c_str());
1166 }
1168 else if (id.find("regcancel") != id.npos)
1169 {
1170 ElementList list = root->findElements("error");
1171 if (list.size()==0)
1172 {
1173 XmppEvent evt(XmppEvent::EVENT_REGISTRATION_CANCEL);
1174 evt.setTo(username);
1175 evt.setFrom(host);
1176 dispatchXmppEvent(evt);
1177 return true;
1178 }
1180 Element *errElem = list[0];
1181 DOMString errMsg = "Registration cancel error: ";
1182 if (errElem->findElements("bad-request").size()>0)
1183 {
1184 errMsg.append("The <remove/> element was not the only child element of the <query/> element.");
1185 }
1186 else if (errElem->findElements("forbidden").size()>0)
1187 {
1188 errMsg.append("sender does not have sufficient permissions to cancel the registration");
1189 }
1190 else if (errElem->findElements("not-allowed").size()>0)
1191 {
1192 errMsg.append("not allowed to cancel registrations in-band");
1193 }
1194 else if (errElem->findElements("registration-required").size()>0)
1195 {
1196 errMsg.append("not previously registered");
1197 }
1198 else if (errElem->findElements("unexpected-request").size()>0)
1199 {
1200 errMsg.append(
1201 "IQ set does not contain a 'from' address because "
1202 "the entity is not registered with the server");
1203 }
1204 error("%s", errMsg.c_str());
1205 }
1207 return true;
1208 }
1214 bool XmppClient::receiveAndProcess()
1215 {
1216 if (!keepGoing)
1217 return false;
1219 Parser parser;
1221 DOMString recvBuf = readStanza();
1222 recvBuf = trim(recvBuf);
1223 if (recvBuf.size() < 1)
1224 return true;
1226 //Ugly hack. Apparently the first char can be dropped on timeouts
1227 //if (recvBuf[0] != '<')
1228 // recvBuf.insert(0, "<");
1230 status("RECV: %s", recvBuf.c_str());
1231 Element *root = parser.parse(recvBuf);
1232 if (!root)
1233 {
1234 printf("Bad elem\n");
1235 return true;
1236 }
1238 //#### MESSAGE
1239 ElementList elems = root->findElements("message");
1240 if (elems.size()>0)
1241 {
1242 if (!processMessage(root))
1243 return false;
1244 }
1246 //#### PRESENCE
1247 elems = root->findElements("presence");
1248 if (elems.size()>0)
1249 {
1250 if (!processPresence(root))
1251 return false;
1252 }
1254 //#### INFO
1255 elems = root->findElements("iq");
1256 if (elems.size()>0)
1257 {
1258 if (!processIq(root))
1259 return false;
1260 }
1262 delete root;
1264 return true;
1265 }
1268 bool XmppClient::receiveAndProcessLoop()
1269 {
1270 keepGoing = true;
1271 while (true)
1272 {
1273 if (!keepGoing)
1274 {
1275 status("Abort requested");
1276 break;
1277 }
1278 if (!receiveAndProcess())
1279 return false;
1280 }
1281 return true;
1282 }
1287 //########################################################################
1288 //# SENDING
1289 //########################################################################
1292 bool XmppClient::write(const char *fmt, ...)
1293 {
1294 bool rc = true;
1295 va_list args;
1296 va_start(args,fmt);
1297 gchar * buffer = g_strdup_vprintf(fmt,args);
1298 va_end(args) ;
1299 status("SEND: %s", buffer);
1300 if (!sock->write(buffer))
1301 {
1302 error("Cannot write to socket: %s", sock->getLastError().c_str());
1303 rc = false;
1304 }
1305 g_free(buffer);
1306 return rc;
1307 }
1314 //########################################################################
1315 //# R E G I S T R A T I O N
1316 //########################################################################
1318 /**
1319 * Perform JEP-077 In-Band Registration. Performed synchronously after SSL,
1320 * before authentication
1321 */
1322 bool XmppClient::inBandRegistrationNew()
1323 {
1324 Parser parser;
1326 const char *fmt =
1327 "<iq type='get' id='regnew%d'>"
1328 "<query xmlns='jabber:iq:register'/>"
1329 "</iq>\n\n";
1330 if (!write(fmt, msgId++))
1331 return false;
1333 DOMString recbuf = readStanza();
1334 status("RECV reg: %s", recbuf.c_str());
1335 Element *elem = parser.parse(recbuf);
1336 //elem->print();
1338 //# does the entity send the newer "instructions" tag?
1339 ElementList fields = elem->findElements("field");
1340 std::vector<DOMString> fnames;
1341 for (unsigned int i=0; i<fields.size() ; i++)
1342 {
1343 DOMString fname = fields[i]->getAttribute("var");
1344 if (fname == "FORM_TYPE")
1345 continue;
1346 fnames.push_back(fname);
1347 status("field name:%s", fname.c_str());
1348 }
1350 //Do we have any fields?
1351 if (fnames.size() == 0)
1352 {
1353 //If no fields, maybe the older method was offered
1354 if (elem->findElements("username").size() == 0 ||
1355 elem->findElements("password").size() == 0)
1356 {
1357 error("server did not offer registration");
1358 delete elem;
1359 return false;
1360 }
1361 }
1363 delete elem;
1365 fmt =
1366 "<iq type='set' id='regnew%d'>"
1367 "<query xmlns='jabber:iq:register'>"
1368 "<username>%s</username>"
1369 "<password>%s</password>"
1370 "<email/><name/>"
1371 "</query>"
1372 "</iq>\n\n";
1373 if (!write(fmt, msgId++, toXml(username).c_str(),
1374 toXml(password).c_str() ))
1375 return false;
1378 recbuf = readStanza();
1379 status("RECV reg: %s", recbuf.c_str());
1380 elem = parser.parse(recbuf);
1381 //elem->print();
1383 ElementList list = elem->findElements("error");
1384 if (list.size()>0)
1385 {
1386 Element *errElem = list[0];
1387 DOMString code = errElem->getAttribute("code");
1388 DOMString errMsg = "Registration error: ";
1389 if (code == "409")
1390 {
1391 errMsg.append("conflict with existing user name");
1392 }
1393 else if (code == "406")
1394 {
1395 errMsg.append("some registration information was not provided");
1396 }
1397 error("%s", errMsg.c_str());
1398 delete elem;
1399 return false;
1400 }
1402 delete elem;
1404 XmppEvent evt(XmppEvent::EVENT_REGISTRATION_NEW);
1405 evt.setTo(username);
1406 evt.setFrom(host);
1407 dispatchXmppEvent(evt);
1409 return true;
1410 }
1413 /**
1414 * Perform JEP-077 In-Band Registration. Performed asynchronously, after login.
1415 * See processIq() for response handling.
1416 */
1417 bool XmppClient::inBandRegistrationChangePassword(const DOMString &newpassword)
1418 {
1419 Parser parser;
1421 //# Let's try it form-style to allow the common old/new password thing
1422 const char *fmt =
1423 "<iq type='set' id='regpass%d' to='%s'>"
1424 " <query xmlns='jabber:iq:register'>"
1425 " <x xmlns='jabber:x:data' type='form'>"
1426 " <field type='hidden' var='FORM_TYPE'>"
1427 " <value>jabber:iq:register:changepassword</value>"
1428 " </field>"
1429 " <field type='text-single' var='username'>"
1430 " <value>%s</value>"
1431 " </field>"
1432 " <field type='text-private' var='old_password'>"
1433 " <value>%s</value>"
1434 " </field>"
1435 " <field type='text-private' var='password'>"
1436 " <value>%s</value>"
1437 " </field>"
1438 " </x>"
1439 " </query>"
1440 "</iq>\n\n";
1442 if (!write(fmt, msgId++, host.c_str(),
1443 username.c_str(), password.c_str(), newpassword.c_str()))
1444 return false;
1446 return true;
1448 }
1451 /**
1452 * Perform JEP-077 In-Band Registration. Performed asynchronously, after login.
1453 * See processIq() for response handling.
1454 */
1455 bool XmppClient::inBandRegistrationCancel()
1456 {
1457 Parser parser;
1459 const char *fmt =
1460 "<iq type='set' id='regcancel%d'>"
1461 "<query xmlns='jabber:iq:register'><remove/></query>"
1462 "</iq>\n\n";
1463 if (!write(fmt, msgId++))
1464 return false;
1466 return true;
1467 }
1473 //########################################################################
1474 //# A U T H E N T I C A T E
1475 //########################################################################
1479 bool XmppClient::iqAuthenticate(const DOMString &streamId)
1480 {
1481 Parser parser;
1483 const char *fmt =
1484 "<iq type='get' to='%s' id='auth%d'>"
1485 "<query xmlns='jabber:iq:auth'><username>%s</username></query>"
1486 "</iq>\n";
1487 if (!write(fmt, realm.c_str(), msgId++, username.c_str()))
1488 return false;
1490 DOMString recbuf = readStanza();
1491 status("iq auth recv: '%s'\n", recbuf.c_str());
1492 Element *elem = parser.parse(recbuf);
1493 //elem->print();
1494 DOMString iqType = elem->getTagAttribute("iq", "type");
1495 //printf("##iqType:%s\n", iqType.c_str());
1496 delete elem;
1498 if (iqType != "result")
1499 {
1500 error("error:server does not allow login");
1501 return false;
1502 }
1504 bool digest = true;
1505 if (digest)
1506 {
1507 //## Digest authentication
1508 DOMString digest = streamId;
1509 digest.append(password);
1510 digest = Sha1::hashHex(digest);
1511 //printf("digest:%s\n", digest.c_str());
1512 fmt =
1513 "<iq type='set' id='auth%d'>"
1514 "<query xmlns='jabber:iq:auth'>"
1515 "<username>%s</username>"
1516 "<digest>%s</digest>"
1517 "<resource>%s</resource>"
1518 "</query>"
1519 "</iq>\n";
1520 if (!write(fmt, msgId++, username.c_str(),
1521 digest.c_str(), resource.c_str()))
1522 return false;
1523 }
1524 else
1525 {
1527 //## Plaintext authentication
1528 fmt =
1529 "<iq type='set' id='auth%d'>"
1530 "<query xmlns='jabber:iq:auth'>"
1531 "<username>%s</username>"
1532 "<password>%s</password>"
1533 "<resource>%s</resource>"
1534 "</query>"
1535 "</iq>\n";
1536 if (!write(fmt, msgId++, username.c_str(),
1537 password.c_str(), resource.c_str()))
1538 return false;
1539 }
1541 recbuf = readStanza();
1542 status("iq auth recv: '%s'\n", recbuf.c_str());
1543 elem = parser.parse(recbuf);
1544 //elem->print();
1545 iqType = elem->getTagAttribute("iq", "type");
1546 //printf("##iqType:%s\n", iqType.c_str());
1547 delete elem;
1549 if (iqType != "result")
1550 {
1551 error("server does not allow login");
1552 return false;
1553 }
1555 return true;
1556 }
1559 /**
1560 * Parse a sasl challenge to retrieve all of its key=value pairs
1561 */
1562 static bool saslParse(const DOMString &s,
1563 std::map<DOMString, DOMString> &vals)
1564 {
1566 vals.clear();
1568 int p = 0;
1569 int siz = s.size();
1571 while (p < siz)
1572 {
1573 DOMString key;
1574 DOMString value;
1575 char ch = '\0';
1577 //# Parse key
1578 while (p<siz)
1579 {
1580 ch = s[p++];
1581 if (ch == '=')
1582 break;
1583 key.push_back(ch);
1584 }
1586 //No value?
1587 if (ch != '=')
1588 break;
1590 //# Parse value
1591 bool quoted = false;
1592 while (p<siz)
1593 {
1594 ch = s[p++];
1595 if (ch == '"')
1596 quoted = !quoted;
1597 else if (ch == ',' && !quoted)
1598 break;
1599 else
1600 value.push_back(ch);
1601 }
1603 //printf("# Key: '%s' Value: '%s'\n", key.c_str(), value.c_str());
1604 vals[key] = value;
1605 if (ch != ',')
1606 break;
1607 }
1609 return true;
1610 }
1614 /**
1615 * Attempt suthentication using the MD5 SASL mechanism
1616 */
1617 bool XmppClient::saslMd5Authenticate()
1618 {
1619 Parser parser;
1620 const char *fmt =
1621 "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' "
1622 "mechanism='DIGEST-MD5'/>\n";
1623 if (!write("%s",fmt))
1624 return false;
1626 DOMString recbuf = readStanza();
1627 status("challenge received: '%s'", recbuf.c_str());
1628 Element *elem = parser.parse(recbuf);
1629 //elem->print();
1630 DOMString b64challenge = elem->getTagValue("challenge");
1631 delete elem;
1633 if (b64challenge.size() < 1)
1634 {
1635 error("login: no SASL challenge offered by server");
1636 return false;
1637 }
1638 DOMString challenge = Base64Decoder::decodeToString(b64challenge);
1639 status("md5 challenge:'%s'", challenge.c_str());
1641 std::map<DOMString, DOMString> attrs;
1642 if (!saslParse(challenge, attrs))
1643 {
1644 error("login: error parsing SASL challenge");
1645 return false;
1646 }
1648 DOMString nonce = attrs["nonce"];
1649 if (nonce.size()==0)
1650 {
1651 error("login: no SASL nonce sent by server");
1652 return false;
1653 }
1655 DOMString realm = attrs["realm"];
1656 if (realm.size()==0)
1657 {
1658 //Apparently this is not a problem
1659 //error("login: no SASL realm sent by server");
1660 //return false;
1661 }
1663 status("SASL recv nonce: '%s' realm:'%s'\n", nonce.c_str(), realm.c_str());
1665 char idBuf[14];
1666 snprintf(idBuf, 13, "%dsasl", msgId++);
1667 DOMString cnonceStr = idBuf;
1668 DOMString cnonce = Sha1::hashHex(cnonceStr);
1669 DOMString authzid = username; authzid.append("@"); authzid.append(host);
1670 DOMString digest_uri = "xmpp/"; digest_uri.append(host);
1672 //## Make A1
1673 Md5 md5;
1674 md5.append(username);
1675 md5.append(":");
1676 md5.append(realm);
1677 md5.append(":");
1678 md5.append(password);
1679 unsigned char a1tmp[16];
1680 md5.finish(a1tmp);
1681 md5.init();
1682 md5.append(a1tmp, 16);
1683 md5.append(":");
1684 md5.append(nonce);
1685 md5.append(":");
1686 md5.append(cnonce);
1687 //RFC2831 says authzid is optional. Wildfire has trouble with authzid's
1688 //md5.append(":");
1689 //md5.append(authzid);
1690 md5.append("");
1691 DOMString a1 = md5.finishHex();
1692 status("##a1:'%s'", a1.c_str());
1694 //# Make A2
1695 md5.init();
1696 md5.append("AUTHENTICATE:");
1697 md5.append(digest_uri);
1698 DOMString a2 = md5.finishHex();
1699 status("##a2:'%s'", a2.c_str());
1701 //# Now make the response
1702 md5.init();
1703 md5.append(a1);
1704 md5.append(":");
1705 md5.append(nonce);
1706 md5.append(":");
1707 md5.append("00000001");//nc
1708 md5.append(":");
1709 md5.append(cnonce);
1710 md5.append(":");
1711 md5.append("auth");//qop
1712 md5.append(":");
1713 md5.append(a2);
1714 DOMString response = md5.finishHex();
1716 DOMString resp;
1717 resp.append("username=\""); resp.append(username); resp.append("\",");
1718 resp.append("realm=\""); resp.append(realm); resp.append("\",");
1719 resp.append("nonce=\""); resp.append(nonce); resp.append("\",");
1720 resp.append("cnonce=\""); resp.append(cnonce); resp.append("\",");
1721 resp.append("nc=00000001,qop=auth,");
1722 resp.append("digest-uri=\""); resp.append(digest_uri); resp.append("\"," );
1723 //resp.append("authzid=\""); resp.append(authzid); resp.append("\",");
1724 resp.append("response="); resp.append(response); resp.append(",");
1725 resp.append("charset=utf-8");
1726 status("sending response:'%s'", resp.c_str());
1727 resp = Base64Encoder::encode(resp);
1728 status("base64 response:'%s'", resp.c_str());
1729 fmt =
1730 "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response>\n";
1731 if (!write(fmt, resp.c_str()))
1732 return false;
1734 recbuf = readStanza();
1735 status("server says: '%s'", recbuf.c_str());
1736 elem = parser.parse(recbuf);
1737 //elem->print();
1738 //# Success or failure already?
1739 if (elem->findElements("success").size() > 0)
1740 {
1741 delete elem;
1742 return true;
1743 }
1744 else
1745 {
1746 ElementList list = elem->findElements("failure");
1747 if (list.size() > 0)
1748 {
1749 DOMString errmsg = "";
1750 Element *errmsgElem = list[0]->getFirstChild();
1751 if (errmsgElem)
1752 errmsg = errmsgElem->getName();
1753 error("login: initial md5 authentication failed: %s", errmsg.c_str());
1754 delete elem;
1755 return false;
1756 }
1757 }
1758 //# Continue for one more SASL cycle
1759 b64challenge = elem->getTagValue("challenge");
1760 delete elem;
1762 if (b64challenge.size() < 1)
1763 {
1764 error("login: no second SASL challenge offered by server");
1765 return false;
1766 }
1768 challenge = Base64Decoder::decodeToString(b64challenge);
1769 status("md5 challenge: '%s'", challenge.c_str());
1771 if (!saslParse(challenge, attrs))
1772 {
1773 error("login: error parsing SASL challenge");
1774 return false;
1775 }
1777 DOMString rspauth = attrs["rspauth"];
1778 if (rspauth.size()==0)
1779 {
1780 error("login: no SASL respauth sent by server\n");
1781 return false;
1782 }
1784 fmt =
1785 "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>\n";
1786 if (!write("%s",fmt))
1787 return false;
1789 recbuf = readStanza();
1790 status("SASL recv: '%s", recbuf.c_str());
1791 elem = parser.parse(recbuf);
1792 //elem->print();
1793 b64challenge = elem->getTagValue("challenge");
1794 bool success = (elem->findElements("success").size() > 0);
1795 delete elem;
1797 return success;
1798 }
1802 /**
1803 * Attempt to authentication using the SASL PLAIN mechanism. This
1804 * is used most commonly my Google Talk.
1805 */
1806 bool XmppClient::saslPlainAuthenticate()
1807 {
1808 Parser parser;
1810 DOMString id = username;
1811 //id.append("@");
1812 //id.append(host);
1813 Base64Encoder encoder;
1814 encoder.append('\0');
1815 encoder.append(id);
1816 encoder.append('\0');
1817 encoder.append(password);
1818 DOMString base64Auth = encoder.finish();
1819 //printf("authbuf:%s\n", base64Auth.c_str());
1821 const char *fmt =
1822 "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' "
1823 "mechanism='PLAIN'>%s</auth>\n";
1824 if (!write(fmt, base64Auth.c_str()))
1825 return false;
1826 DOMString recbuf = readStanza();
1827 status("challenge received: '%s'", recbuf.c_str());
1828 Element *elem = parser.parse(recbuf);
1830 bool success = (elem->findElements("success").size() > 0);
1831 delete elem;
1833 return success;
1834 }
1838 /**
1839 * Handshake with SASL, and use one of its offered mechanisms to
1840 * authenticate.
1841 * @param streamId used for iq auth fallback is SASL not supported
1842 */
1843 bool XmppClient::saslAuthenticate(const DOMString &streamId)
1844 {
1845 Parser parser;
1847 DOMString recbuf = readStanza();
1848 status("RECV: '%s'\n", recbuf.c_str());
1849 Element *elem = parser.parse(recbuf);
1850 //elem->print();
1852 //Check for starttls
1853 bool wantStartTls = false;
1854 if (elem->findElements("starttls").size() > 0)
1855 {
1856 wantStartTls = true;
1857 if (elem->findElements("required").size() > 0)
1858 status("login: STARTTLS required");
1859 else
1860 status("login: STARTTLS available");
1861 }
1863 //# do we want TLS, are we not already running SSL, and can
1864 //# the client actually do an ssl connection?
1865 if (wantStartTls && !sock->getEnableSSL() && sock->getHaveSSL())
1866 {
1867 delete elem;
1868 const char *fmt =
1869 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n";
1870 if (!write("%s",fmt))
1871 return false;
1872 recbuf = readStanza();
1873 status("RECV: '%s'\n", recbuf.c_str());
1874 elem = parser.parse(recbuf);
1875 if (elem->getTagAttribute("proceed", "xmlns").size()<1)
1876 {
1877 error("Server rejected TLS negotiation");
1878 disconnect();
1879 return false;
1880 }
1881 delete elem;
1882 if (!sock->startTls())
1883 {
1884 DOMString tcperr = sock->getLastError();
1885 error("Could not start TLS: %s", tcperr.c_str());
1886 disconnect();
1887 return false;
1888 }
1890 fmt =
1891 "<stream:stream xmlns='jabber:client' "
1892 "xmlns:stream='http://etherx.jabber.org/streams' "
1893 "to='%s' version='1.0'>\n\n";
1894 if (!write(fmt, realm.c_str()))
1895 return false;
1897 recbuf = readStanza();
1898 status("RECVx: '%s'", recbuf.c_str());
1899 recbuf.append("</stream:stream>");
1900 elem = parser.parse(recbuf);
1901 bool success =
1902 (elem->getTagAttribute("stream:stream", "id").size()>0);
1903 if (!success)
1904 {
1905 error("STARTTLS negotiation failed");
1906 disconnect();
1907 return false;
1908 }
1909 delete elem;
1910 recbuf = readStanza();
1911 status("RECV: '%s'\n", recbuf.c_str());
1912 elem = parser.parse(recbuf);
1914 XmppEvent event(XmppEvent::EVENT_SSL_STARTED);
1915 dispatchXmppEvent(event);
1916 }
1918 //register, if user requests
1919 if (doRegister)
1920 {
1921 if (!inBandRegistrationNew())
1922 return false;
1923 }
1925 //check for sasl authentication mechanisms
1926 ElementList elems = elem->findElements("mechanism");
1927 if (elems.size() < 1)
1928 {
1929 status("login: no SASL mechanism offered by server");
1930 //fall back to iq
1931 if (iqAuthenticate(streamId))
1932 return true;
1933 return false;
1934 }
1935 bool md5Found = false;
1936 bool plainFound = false;
1937 for (unsigned int i=0 ; i<elems.size() ; i++)
1938 {
1939 DOMString mech = elems[i]->getValue();
1940 if (mech == "DIGEST-MD5")
1941 {
1942 status("MD5 authentication offered");
1943 md5Found = true;
1944 }
1945 else if (mech == "PLAIN")
1946 {
1947 status("PLAIN authentication offered");
1948 plainFound = true;
1949 }
1950 }
1951 delete elem;
1953 bool success = false;
1954 if (md5Found)
1955 {
1956 success = saslMd5Authenticate();
1957 }
1958 else if (plainFound)
1959 {
1960 success = saslPlainAuthenticate();
1961 }
1962 else
1963 {
1964 error("not able to handle sasl authentication mechanisms");
1965 return false;
1966 }
1968 if (success)
1969 status("###### SASL authentication success\n");
1970 else
1971 error("###### SASL authentication failure\n");
1973 return success;
1974 }
1981 //########################################################################
1982 //# CONNECT
1983 //########################################################################
1986 /**
1987 * Check if we are connected, and fail with an error if we are not
1988 */
1989 bool XmppClient::checkConnect()
1990 {
1991 if (!connected)
1992 {
1993 XmppEvent evt(XmppEvent::EVENT_ERROR);
1994 evt.setData("Attempted operation while disconnected");
1995 dispatchXmppEvent(evt);
1996 return false;
1997 }
1998 return true;
1999 }
2003 /**
2004 * Create an XMPP session with a server. This
2005 * is basically the transport layer of XMPP.
2006 */
2007 bool XmppClient::createSession()
2008 {
2010 Parser parser;
2011 if (port==443 || port==5223)
2012 sock->enableSSL(true);
2013 if (!sock->connect(host, port))
2014 {
2015 error("Cannot connect:%s", sock->getLastError().c_str());
2016 return false;
2017 }
2019 if (sock->getEnableSSL())
2020 {
2021 XmppEvent event(XmppEvent::EVENT_SSL_STARTED);
2022 dispatchXmppEvent(event);
2023 }
2025 const char *fmt =
2026 "<stream:stream "
2027 "to='%s' "
2028 "xmlns='jabber:client' "
2029 "xmlns:stream='http://etherx.jabber.org/streams' "
2030 "version='1.0'>\n\n";
2031 if (!write(fmt, realm.c_str()))
2032 return false;
2034 DOMString recbuf = readStanza();
2035 status("RECV: '%s'\n", recbuf.c_str());
2036 recbuf.append("</stream:stream>");
2037 Element *elem = parser.parse(recbuf);
2038 //elem->print();
2039 bool useSasl = false;
2040 DOMString streamId = elem->getTagAttribute("stream:stream", "id");
2041 //printf("### StreamID: %s\n", streamId.c_str());
2042 DOMString streamVersion = elem->getTagAttribute("stream:stream", "version");
2043 if (streamVersion == "1.0")
2044 useSasl = true;
2046 if (useSasl)
2047 {
2048 if (!saslAuthenticate(streamId))
2049 return false;
2051 fmt =
2052 "<stream:stream "
2053 "to='%s' "
2054 "xmlns='jabber:client' "
2055 "xmlns:stream='http://etherx.jabber.org/streams' "
2056 "version='1.0'>\n\n";
2058 if (!write(fmt, realm.c_str()))
2059 return false;
2060 recbuf = readStanza();
2061 recbuf.append("</stream:stream>\n");
2062 //printf("now server says:: '%s'\n", recbuf.c_str());
2063 elem = parser.parse(recbuf);
2064 //elem->print();
2065 delete elem;
2067 recbuf = readStanza();
2068 //printf("now server says:: '%s'\n", recbuf.c_str());
2069 elem = parser.parse(recbuf);
2070 bool hasBind = (elem->findElements("bind").size() > 0);
2071 //elem->print();
2072 delete elem;
2074 if (!hasBind)
2075 {
2076 error("no binding provided by server");
2077 return false;
2078 }
2081 }
2082 else // not SASL
2083 {
2084 if (!iqAuthenticate(streamId))
2085 return false;
2086 }
2089 //### Resource binding
2090 fmt =
2091 "<iq type='set' id='bind%d'>"
2092 "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
2093 "<resource>%s</resource>"
2094 "</bind></iq>\n";
2095 if (!write(fmt, msgId++, resource.c_str()))
2096 return false;
2098 recbuf = readStanza();
2099 status("bind result: '%s'", recbuf.c_str());
2100 elem = parser.parse(recbuf);
2101 //elem->print();
2102 DOMString bindType = elem->getTagAttribute("iq", "type");
2103 //printf("##bindType:%s\n", bindType.c_str());
2104 DOMString givenFullJid = elem->getTagValue("jid");
2105 delete elem;
2107 if (bindType != "result")
2108 {
2109 error("no binding with server failed");
2110 return false;
2111 }
2113 //The server sent us a JID. We need to listen.
2114 if (givenFullJid.size()>0)
2115 {
2116 DOMString givenJid, givenResource;
2117 parseJid(givenFullJid, givenJid, givenResource);
2118 status("given user: %s realm: %s, rsrc: %s",
2119 givenJid.c_str(), realm.c_str(), givenResource.c_str());
2120 setResource(givenResource);
2121 }
2124 fmt =
2125 "<iq type='set' id='sess%d'>"
2126 "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>"
2127 "</iq>\n";
2128 if (!write(fmt, msgId++))
2129 return false;
2131 recbuf = readStanza();
2132 status("session received: '%s'", recbuf.c_str());
2133 elem = parser.parse(recbuf);
2134 //elem->print();
2135 DOMString sessionType = elem->getTagAttribute("iq", "type");
2136 //printf("##sessionType:%s\n", sessionType.c_str());
2137 delete elem;
2139 if (sessionType != "result")
2140 {
2141 error("no session provided by server");
2142 return false;
2143 }
2145 //printf("########## COOL #########\n");
2146 //Now that we are bound, we have a valid JID
2147 jid = username;
2148 jid.append("@");
2149 jid.append(realm);
2150 jid.append("/");
2151 jid.append(resource);
2153 //We are now done with the synchronous handshaking. Let's go into
2154 //async mode
2156 fmt =
2157 "<iq type='get' id='roster%d'><query xmlns='jabber:iq:roster'/></iq>\n";
2158 if (!write(fmt, msgId++))
2159 return false;
2161 fmt =
2162 "<iq type='get' id='discoItems%d' to='%s'>"
2163 "<query xmlns='http://jabber.org/protocol/disco#items'/></iq>\n";
2164 if (!write(fmt, msgId++, realm.c_str()))
2165 return false;
2167 fmt =
2168 "<iq type='get' id='discoInfo%d' to='conference.%s'>"
2169 "<query xmlns='http://jabber.org/protocol/disco#info'/></iq>\n";
2170 if (!write(fmt, msgId++, realm.c_str()))
2171 return false;
2173 fmt =
2174 "<presence/>\n";
2175 if (!write("%s",fmt))
2176 return false;
2178 /*
2179 recbuf = readStanza();
2180 status("stream received: '%s'", recbuf.c_str());
2181 elem = parser.parse(recbuf);
2182 //elem->print();
2183 delete elem;
2184 */
2186 //We are now logged in
2187 status("Connected");
2188 connected = true;
2189 XmppEvent evt(XmppEvent::EVENT_CONNECTED);
2190 evt.setData(host);
2191 dispatchXmppEvent(evt);
2192 //Thread::sleep(1000000);
2194 sock->setReceiveTimeout(1000);
2195 ReceiverThread runner(*this);
2196 Thread thread(runner);
2197 thread.start();
2199 return true;
2200 }
2204 /**
2205 * Public call to connect
2206 */
2207 bool XmppClient::connect()
2208 {
2209 if (!createSession())
2210 {
2211 disconnect();
2212 return false;
2213 }
2214 return true;
2215 }
2218 /**
2219 * Public call to connect
2220 */
2221 bool XmppClient::connect(DOMString hostArg, int portArg,
2222 DOMString usernameArg,
2223 DOMString passwordArg,
2224 DOMString resourceArg)
2225 {
2226 host = hostArg;
2227 port = portArg;
2228 password = passwordArg;
2229 resource = resourceArg;
2231 //parse this one
2232 setUsername(usernameArg);
2234 bool ret = connect();
2235 return ret;
2236 }
2240 /**
2241 * Public call to disconnect
2242 */
2243 bool XmppClient::disconnect()
2244 {
2245 if (connected)
2246 {
2247 const char *fmt =
2248 "<presence type='unavailable'/>\n";
2249 write("%s",fmt);
2250 }
2251 keepGoing = false;
2252 connected = false;
2253 Thread::sleep(2000); //allow receiving thread to quit
2254 sock->disconnect();
2255 roster.clear();
2256 groupChatsClear();
2257 XmppEvent event(XmppEvent::EVENT_DISCONNECTED);
2258 event.setData(host);
2259 dispatchXmppEvent(event);
2260 return true;
2261 }
2267 //########################################################################
2268 //# ROSTER
2269 //########################################################################
2271 /**
2272 * Add an XMPP id to your roster
2273 */
2274 bool XmppClient::rosterAdd(const DOMString &rosterGroup,
2275 const DOMString &otherJid,
2276 const DOMString &name)
2277 {
2278 if (!checkConnect())
2279 return false;
2280 const char *fmt =
2281 "<iq type='set' id='roster_%d'>"
2282 "<query xmlns='jabber:iq:roster'>"
2283 "<item jid='%s' name='%s'><group>%s</group></item>"
2284 "</query></iq>\n";
2285 if (!write(fmt, msgId++, otherJid.c_str(),
2286 name.c_str(), rosterGroup.c_str()))
2287 {
2288 return false;
2289 }
2290 return true;
2291 }
2295 /**
2296 * Delete an XMPP id from your roster.
2297 */
2298 bool XmppClient::rosterDelete(const DOMString &otherJid)
2299 {
2300 if (!checkConnect())
2301 return false;
2302 const char *fmt =
2303 "<iq type='set' id='roster_%d'>"
2304 "<query xmlns='jabber:iq:roster'>"
2305 "<item jid='%s' subscription='remove'><group>%s</group></item>"
2306 "</query></iq>\n";
2307 if (!write(fmt, msgId++, otherJid.c_str()))
2308 {
2309 return false;
2310 }
2311 return true;
2312 }
2315 /**
2316 * Comparison method for sort() call below
2317 */
2318 static bool xmppRosterCompare(const XmppUser& p1, const XmppUser& p2)
2319 {
2320 DOMString s1 = p1.group;
2321 DOMString s2 = p2.group;
2322 for (unsigned int len=0 ; len<s1.size() && len<s2.size() ; len++)
2323 {
2324 int comp = tolower(s1[len]) - tolower(s2[len]);
2325 if (comp)
2326 return (comp<0);
2327 }
2329 s1 = p1.jid;
2330 s2 = p2.jid;
2331 for (unsigned int len=0 ; len<s1.size() && len<s2.size() ; len++)
2332 {
2333 int comp = tolower(s1[len]) - tolower(s2[len]);
2334 if (comp)
2335 return (comp<0);
2336 }
2337 return false;
2338 }
2342 /**
2343 * Sort and return the roster that has just been reported by
2344 * an XmppEvent::EVENT_ROSTER event.
2345 */
2346 std::vector<XmppUser> XmppClient::getRoster()
2347 {
2348 std::vector<XmppUser> ros = roster;
2349 std::sort(ros.begin(), ros.end(), xmppRosterCompare);
2350 return ros;
2351 }
2354 /**
2355 *
2356 */
2357 void XmppClient::rosterShow(const DOMString &jid, const DOMString &show)
2358 {
2359 DOMString theShow = show;
2360 if (theShow == "")
2361 theShow = "available";
2363 std::vector<XmppUser>::iterator iter;
2364 for (iter=roster.begin() ; iter != roster.end() ; iter++)
2365 {
2366 if (iter->jid == jid)
2367 iter->show = theShow;
2368 }
2369 }
2376 //########################################################################
2377 //# CHAT (individual)
2378 //########################################################################
2380 /**
2381 * Send a message to an xmpp jid
2382 */
2383 bool XmppClient::message(const DOMString &user, const DOMString &subj,
2384 const DOMString &msg)
2385 {
2386 if (!checkConnect())
2387 return false;
2389 DOMString xmlSubj = toXml(subj);
2390 DOMString xmlMsg = toXml(msg);
2392 if (xmlSubj.size() > 0)
2393 {
2394 const char *fmt =
2395 "<message to='%s' from='%s' type='chat'>"
2396 "<subject>%s</subject><body>%s</body></message>\n";
2397 if (!write(fmt, user.c_str(), jid.c_str(),
2398 xmlSubj.c_str(), xmlMsg.c_str()))
2399 return false;
2400 }
2401 else
2402 {
2403 const char *fmt =
2404 "<message to='%s' from='%s'>"
2405 "<body>%s</body></message>\n";
2406 if (!write(fmt, user.c_str(), jid.c_str(), xmlMsg.c_str()))
2407 return false;
2408 }
2409 return true;
2410 }
2414 /**
2415 *
2416 */
2417 bool XmppClient::message(const DOMString &user, const DOMString &msg)
2418 {
2419 return message(user, "", msg);
2420 }
2424 /**
2425 *
2426 */
2427 bool XmppClient::presence(const DOMString &presence)
2428 {
2429 if (!checkConnect())
2430 return false;
2432 DOMString xmlPres = toXml(presence);
2434 const char *fmt =
2435 "<presence><show>%s</show></presence>\n";
2436 if (!write(fmt, xmlPres.c_str()))
2437 return false;
2438 return true;
2439 }
2446 //########################################################################
2447 //# GROUP CHAT
2448 //########################################################################
2450 /**
2451 *
2452 */
2453 bool XmppClient::groupChatCreate(const DOMString &groupJid)
2454 {
2455 std::vector<XmppGroupChat *>::iterator iter;
2456 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2457 {
2458 if ((*iter)->getGroupJid() == groupJid)
2459 {
2460 //error("Group chat '%s' already exists", groupJid.c_str());
2461 return false;
2462 }
2463 }
2464 XmppGroupChat *chat = new XmppGroupChat(groupJid);
2465 groupChats.push_back(chat);
2466 return true;
2467 }
2471 /**
2472 *
2473 */
2474 void XmppClient::groupChatDelete(const DOMString &groupJid)
2475 {
2476 std::vector<XmppGroupChat *>::iterator iter;
2477 for (iter=groupChats.begin() ; iter!=groupChats.end() ; )
2478 {
2479 XmppGroupChat *chat = *iter;
2480 if (chat->getGroupJid() == groupJid)
2481 {
2482 iter = groupChats.erase(iter);
2483 delete chat;
2484 }
2485 else
2486 iter++;
2487 }
2488 }
2492 /**
2493 *
2494 */
2495 bool XmppClient::groupChatExists(const DOMString &groupJid)
2496 {
2497 std::vector<XmppGroupChat *>::iterator iter;
2498 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2499 if ((*iter)->getGroupJid() == groupJid)
2500 return true;
2501 return false;
2502 }
2506 /**
2507 *
2508 */
2509 void XmppClient::groupChatsClear()
2510 {
2511 std::vector<XmppGroupChat *>::iterator iter;
2512 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2513 delete (*iter);
2514 groupChats.clear();
2515 }
2520 /**
2521 *
2522 */
2523 void XmppClient::groupChatUserAdd(const DOMString &groupJid,
2524 const DOMString &nick,
2525 const DOMString &jid)
2526 {
2527 std::vector<XmppGroupChat *>::iterator iter;
2528 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2529 {
2530 if ((*iter)->getGroupJid() == groupJid)
2531 {
2532 (*iter)->userAdd(nick, jid);
2533 }
2534 }
2535 }
2539 /**
2540 *
2541 */
2542 void XmppClient::groupChatUserShow(const DOMString &groupJid,
2543 const DOMString &nick,
2544 const DOMString &show)
2545 {
2546 std::vector<XmppGroupChat *>::iterator iter;
2547 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2548 {
2549 if ((*iter)->getGroupJid() == groupJid)
2550 {
2551 (*iter)->userShow(nick, show);
2552 }
2553 }
2554 }
2559 /**
2560 *
2561 */
2562 void XmppClient::groupChatUserDelete(const DOMString &groupJid,
2563 const DOMString &nick)
2564 {
2565 std::vector<XmppGroupChat *>::iterator iter;
2566 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2567 {
2568 if ((*iter)->getGroupJid() == groupJid)
2569 {
2570 (*iter)->userDelete(nick);
2571 }
2572 }
2573 }
2577 /**
2578 * Comparison method for the sort() below
2579 */
2580 static bool xmppUserCompare(const XmppUser& p1, const XmppUser& p2)
2581 {
2582 DOMString s1 = p1.nick;
2583 DOMString s2 = p2.nick;
2584 int comp = 0;
2585 for (unsigned int len=0 ; len<s1.size() && len<s2.size() ; len++)
2586 {
2587 comp = tolower(s1[len]) - tolower(s2[len]);
2588 if (comp)
2589 break;
2590 }
2591 return (comp<0);
2592 }
2596 /**
2597 * Return the user list for the named group
2598 */
2599 std::vector<XmppUser> XmppClient::groupChatGetUserList(
2600 const DOMString &groupJid)
2601 {
2602 if (!checkConnect())
2603 {
2604 std::vector<XmppUser> dummy;
2605 return dummy;
2606 }
2608 std::vector<XmppGroupChat *>::iterator iter;
2609 for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++)
2610 {
2611 if ((*iter)->getGroupJid() == groupJid )
2612 {
2613 std::vector<XmppUser> uList = (*iter)->getUserList();
2614 std::sort(uList.begin(), uList.end(), xmppUserCompare);
2615 return uList;
2616 }
2617 }
2618 std::vector<XmppUser> dummy;
2619 return dummy;
2620 }
2625 /**
2626 * Try to join a group
2627 */
2628 bool XmppClient::groupChatJoin(const DOMString &groupJid,
2629 const DOMString &nick,
2630 const DOMString &/*pass*/)
2631 {
2632 if (!checkConnect())
2633 return false;
2635 DOMString user = nick;
2636 if (user.size()<1)
2637 user = username;
2639 const char *fmt =
2640 "<presence to='%s/%s'>"
2641 "<x xmlns='http://jabber.org/protocol/muc'/></presence>\n";
2642 if (!write(fmt, groupJid.c_str(), user.c_str()))
2643 return false;
2644 return true;
2645 }
2650 /**
2651 * Leave a group
2652 */
2653 bool XmppClient::groupChatLeave(const DOMString &groupJid,
2654 const DOMString &nick)
2655 {
2656 if (!checkConnect())
2657 return false;
2659 DOMString user = nick;
2660 if (user.size()<1)
2661 user = username;
2663 const char *fmt =
2664 "<presence to='%s/%s' type='unavailable'>"
2665 "<x xmlns='http://jabber.org/protocol/muc'/></presence>\n";
2666 if (!write(fmt, groupJid.c_str(), user.c_str()))
2667 return false;
2668 return true;
2669 }
2674 /**
2675 * Send a message to a group
2676 */
2677 bool XmppClient::groupChatMessage(const DOMString &groupJid,
2678 const DOMString &msg)
2679 {
2680 if (!checkConnect())
2681 {
2682 return false;
2683 }
2685 DOMString xmlMsg = toXml(msg);
2687 const char *fmt =
2688 "<message from='%s' to='%s' type='groupchat'>"
2689 "<body>%s</body></message>\n";
2690 if (!write(fmt, jid.c_str(), groupJid.c_str(), xmlMsg.c_str()))
2691 return false;
2692 /*
2693 const char *fmt =
2694 "<message to='%s' type='groupchat'>"
2695 "<body>%s</body></message>\n";
2696 if (!write(fmt, groupJid.c_str(), xmlMsg.c_str()))
2697 return false;
2698 */
2699 return true;
2700 }
2705 /**
2706 * Send a message to an individual in a group
2707 */
2708 bool XmppClient::groupChatPrivateMessage(const DOMString &groupJid,
2709 const DOMString &toNick,
2710 const DOMString &msg)
2711 {
2712 if (!checkConnect())
2713 return false;
2715 DOMString xmlMsg = toXml(msg);
2717 /*
2718 const char *fmt =
2719 "<message from='%s' to='%s/%s' type='chat'>"
2720 "<body>%s</body></message>\n";
2721 if (!write(fmt, jid.c_str(), groupJid.c_str(),
2722 toNick.c_str(), xmlMsg.c_str()))
2723 return false;
2724 */
2725 const char *fmt =
2726 "<message to='%s/%s' type='chat'>"
2727 "<body>%s</body></message>\n";
2728 if (!write(fmt, groupJid.c_str(),
2729 toNick.c_str(), xmlMsg.c_str()))
2730 return false;
2731 return true;
2732 }
2737 /**
2738 * Change your presence within a group
2739 */
2740 bool XmppClient::groupChatPresence(const DOMString &groupJid,
2741 const DOMString &myNick,
2742 const DOMString &presence)
2743 {
2744 if (!checkConnect())
2745 return false;
2747 DOMString user = myNick;
2748 if (user.size()<1)
2749 user = username;
2751 DOMString xmlPresence = toXml(presence);
2753 const char *fmt =
2754 "<presence to='%s/%s' type='%s'>"
2755 "<x xmlns='http://jabber.org/protocol/muc'/></presence>\n";
2756 if (!write(fmt, groupJid.c_str(),
2757 user.c_str(), xmlPresence.c_str()))
2758 return true;
2759 return true;
2760 }
2766 //########################################################################
2767 //# S T R E A M S
2768 //########################################################################
2771 bool XmppClient::processInBandByteStreamMessage(Element *root)
2772 {
2773 DOMString from = root->getAttribute("from");
2774 DOMString id = root->getAttribute("id");
2775 DOMString type = root->getAttribute("type");
2777 //### Incoming stream requests
2778 //Input streams are id's by stream id
2779 DOMString ibbNamespace = "http://jabber.org/protocol/ibb";
2781 if (root->getTagAttribute("open", "xmlns") == ibbNamespace)
2782 {
2783 DOMString streamId = root->getTagAttribute("open", "sid");
2784 XmppEvent event(XmppEvent::XmppEvent::EVENT_STREAM_RECEIVE_INIT);
2785 dispatchXmppEvent(event);
2786 std::map<DOMString, XmppStream *>::iterator iter =
2787 inputStreams.find(streamId);
2788 if (iter != inputStreams.end())
2789 {
2790 XmppStream *ins = iter->second;
2791 ins->setState(STREAM_OPENING);
2792 ins->setMessageId(id);
2793 return true;
2794 }
2795 return true;
2796 }
2798 else if (root->getTagAttribute("close", "xmlns") == ibbNamespace)
2799 {
2800 XmppEvent event(XmppEvent::XmppEvent::EVENT_STREAM_RECEIVE_CLOSE);
2801 dispatchXmppEvent(event);
2802 DOMString streamId = root->getTagAttribute("close", "sid");
2803 std::map<DOMString, XmppStream *>::iterator iter =
2804 inputStreams.find(streamId);
2805 if (iter != inputStreams.end())
2806 {
2807 XmppStream *ins = iter->second;
2808 if (from == ins->getPeerId())
2809 {
2810 ins->setState(STREAM_CLOSING);
2811 ins->setMessageId(id);
2812 return true;
2813 }
2814 }
2815 return true;
2816 }
2818 else if (root->getTagAttribute("data", "xmlns") == ibbNamespace)
2819 {
2820 DOMString streamId = root->getTagAttribute("data", "sid");
2821 std::map<DOMString, XmppStream *>::iterator iter =
2822 inputStreams.find(streamId);
2823 if (iter != inputStreams.end())
2824 {
2825 XmppStream *ins = iter->second;
2826 if (ins->getState() != STREAM_OPEN)
2827 {
2828 XmppEvent event(XmppEvent::EVENT_ERROR);
2829 event.setFrom(from);
2830 event.setData("received unrequested stream data");
2831 dispatchXmppEvent(event);
2832 return true;
2833 }
2834 DOMString data = root->getTagValue("data");
2835 std::vector<unsigned char>binData =
2836 Base64Decoder::decode(data);
2837 ins->receiveData(binData);
2838 }
2839 }
2841 //### Responses to outgoing requests
2842 //Output streams are id's by message id
2843 std::map<DOMString, XmppStream *>::iterator iter =
2844 outputStreams.find(id);
2845 if (iter != outputStreams.end())
2846 {
2847 XmppStream *outs = iter->second;
2848 if (type == "error")
2849 {
2850 outs->setState(STREAM_ERROR);
2851 return true;
2852 }
2853 else if (type == "result")
2854 {
2855 if (outs->getState() == STREAM_OPENING)
2856 {
2857 outs->setState(STREAM_OPEN);
2858 }
2859 else if (outs->getState() == STREAM_CLOSING)
2860 {
2861 outs->setState(STREAM_CLOSED);
2862 }
2863 return true;
2864 }
2865 }
2867 return false;
2868 }
2871 /**
2872 *
2873 */
2874 bool XmppClient::outputStreamOpen(const DOMString &destId,
2875 const DOMString &streamIdArg)
2876 {
2877 char buf[32];
2878 snprintf(buf, 31, "inband%d", getMsgId());
2879 DOMString messageId = buf;
2881 //Output streams are id's by message id
2882 XmppStream *outs = new XmppStream();
2883 outputStreams[messageId] = outs;
2885 outs->setState(STREAM_OPENING);
2887 DOMString streamId = streamIdArg;
2888 if (streamId.size()<1)
2889 {
2890 snprintf(buf, 31, "stream%d", getMsgId());
2891 DOMString streamId = buf;
2892 }
2893 outs->setMessageId(messageId);
2894 outs->setStreamId(streamId);
2895 outs->setPeerId(destId);
2898 const char *fmt =
2899 "<%s type='set' to='%s' id='%s'>"
2900 "<open sid='%s' block-size='4096'"
2901 " xmlns='http://jabber.org/protocol/ibb'/></%s>\n";
2902 if (!write(fmt,
2903 streamPacket.c_str(),
2904 destId.c_str(), messageId.c_str(),
2905 streamId.c_str(),
2906 streamPacket.c_str()))
2907 {
2908 outs->reset();
2909 return -1;
2910 }
2912 int state = outs->getState();
2913 for (int tim=0 ; tim<20 ; tim++)
2914 {
2915 if (state == STREAM_OPEN)
2916 break;
2917 else if (state == STREAM_ERROR)
2918 {
2919 printf("ERROR\n");
2920 outs->reset();
2921 return false;
2922 }
2923 Thread::sleep(1000);
2924 state = outs->getState();
2925 }
2926 if (state != STREAM_OPEN)
2927 {
2928 printf("TIMEOUT ERROR\n");
2929 outs->reset();
2930 return -1;
2931 }
2933 return true;
2934 }
2936 /**
2937 *
2938 */
2939 bool XmppClient::outputStreamWrite(const DOMString &streamId,
2940 const std::vector<unsigned char> &buf)
2941 {
2942 std::map<DOMString, XmppStream *>::iterator iter =
2943 outputStreams.find(streamId);
2944 if (iter == outputStreams.end())
2945 return false;
2946 XmppStream *outs = iter->second;
2948 unsigned int len = buf.size();
2949 unsigned int pos = 0;
2951 while (pos < len)
2952 {
2953 unsigned int pos2 = pos + 1024;
2954 if (pos2>len)
2955 pos2 = len;
2957 Base64Encoder encoder;
2958 for (unsigned int i=pos ; i<pos2 ; i++)
2959 encoder.append(buf[i]);
2960 DOMString b64data = encoder.finish();
2963 const char *fmt =
2964 "<message to='%s' id='msg%d'>"
2965 "<data xmlns='http://jabber.org/protocol/ibb' sid='%s' seq='%d'>"
2966 "%s"
2967 "</data>"
2968 "<amp xmlns='http://jabber.org/protocol/amp'>"
2969 "<rule condition='deliver-at' value='stored' action='error'/>"
2970 "<rule condition='match-resource' value='exact' action='error'/>"
2971 "</amp>"
2972 "</message>\n";
2973 if (!write(fmt,
2974 outs->getPeerId().c_str(),
2975 getMsgId(),
2976 outs->getStreamId().c_str(),
2977 outs->getSeqNr(),
2978 b64data.c_str()))
2979 {
2980 outs->reset();
2981 return false;
2982 }
2983 pause(5000);
2985 pos = pos2;
2986 }
2988 return true;
2989 }
2991 /**
2992 *
2993 */
2994 bool XmppClient::outputStreamClose(const DOMString &streamId)
2995 {
2996 std::map<DOMString, XmppStream *>::iterator iter =
2997 outputStreams.find(streamId);
2998 if (iter == outputStreams.end())
2999 return false;
3000 XmppStream *outs = iter->second;
3002 char buf[32];
3003 snprintf(buf, 31, "inband%d", getMsgId());
3004 DOMString messageId = buf;
3005 outs->setMessageId(messageId);
3007 outs->setState(STREAM_CLOSING);
3008 const char *fmt =
3009 "<%s type='set' to='%s' id='%s'>"
3010 "<close sid='%s' xmlns='http://jabber.org/protocol/ibb'/></%s>\n";
3011 if (!write(fmt,
3012 streamPacket.c_str(),
3013 outs->getPeerId().c_str(),
3014 messageId.c_str(),
3015 outs->getStreamId().c_str(),
3016 streamPacket.c_str()
3017 ))
3018 return false;
3020 int state = outs->getState();
3021 for (int tim=0 ; tim<20 ; tim++)
3022 {
3023 if (state == STREAM_CLOSED)
3024 break;
3025 else if (state == STREAM_ERROR)
3026 {
3027 printf("ERROR\n");
3028 outs->reset();
3029 return false;
3030 }
3031 Thread::sleep(1000);
3032 state = outs->getState();
3033 }
3034 if (state != STREAM_CLOSED)
3035 {
3036 printf("TIMEOUT ERROR\n");
3037 outs->reset();
3038 return false;
3039 }
3041 delete outs;
3042 outputStreams.erase(streamId);
3044 return true;
3045 }
3048 /**
3049 *
3050 */
3051 bool XmppClient::inputStreamOpen(const DOMString &fromJid,
3052 const DOMString &streamId,
3053 const DOMString &/*iqId*/)
3054 {
3055 XmppStream *ins = new XmppStream();
3057 inputStreams[streamId] = ins;
3058 ins->reset();
3059 ins->setPeerId(fromJid);
3060 ins->setState(STREAM_CLOSED);
3061 ins->setStreamId(streamId);
3063 int state = ins->getState();
3064 for (int tim=0 ; tim<20 ; tim++)
3065 {
3066 if (state == STREAM_OPENING)
3067 break;
3068 else if (state == STREAM_ERROR)
3069 {
3070 printf("ERROR\n");
3071 ins->reset();
3072 return false;
3073 }
3074 Thread::sleep(1000);
3075 state = ins->getState();
3076 }
3077 if (state != STREAM_OPENING)
3078 {
3079 printf("TIMEOUT ERROR\n");
3080 ins->reset();
3081 return false;
3082 }
3083 const char *fmt =
3084 "<%s type='result' to='%s' id='%s'/>\n";
3085 if (!write(fmt, streamPacket.c_str(),
3086 fromJid.c_str(), ins->getMessageId().c_str()))
3087 {
3088 return false;
3089 }
3091 ins->setState(STREAM_OPEN);
3092 return true;
3093 }
3097 /**
3098 *
3099 */
3100 bool XmppClient::inputStreamClose(const DOMString &streamId)
3101 {
3102 std::map<DOMString, XmppStream *>::iterator iter =
3103 inputStreams.find(streamId);
3104 if (iter == inputStreams.end())
3105 return false;
3106 XmppStream *ins = iter->second;
3108 if (ins->getState() == STREAM_CLOSING)
3109 {
3110 const char *fmt =
3111 "<iq type='result' to='%s' id='%s'/>\n";
3112 if (!write(fmt, ins->getPeerId().c_str(),
3113 ins->getMessageId().c_str()))
3114 {
3115 return false;
3116 }
3117 }
3118 inputStreams.erase(streamId);
3119 delete ins;
3121 return true;
3122 }
3129 //########################################################################
3130 //# FILE TRANSFERS
3131 //########################################################################
3134 bool XmppClient::processFileMessage(Element *root)
3135 {
3136 DOMString siNamespace = "http://jabber.org/protocol/si";
3137 if (root->getTagAttribute("si", "xmlns") != siNamespace)
3138 return false;
3141 Element *mainElement = root->getFirstChild();
3142 if (!mainElement)
3143 return false;
3145 DOMString from = mainElement->getAttribute("from");
3146 DOMString id = mainElement->getAttribute("id");
3147 DOMString type = mainElement->getAttribute("type");
3149 status("received file message from %s", from.c_str());
3151 if (type == "set")
3152 {
3153 DOMString streamId = root->getTagAttribute("si", "id");
3154 DOMString fname = root->getTagAttribute("file", "name");
3155 DOMString sizeStr = root->getTagAttribute("file", "size");
3156 DOMString hash = root->getTagAttribute("file", "hash");
3157 XmppEvent event(XmppEvent::XmppEvent::EVENT_FILE_RECEIVE);
3158 event.setFrom(from);
3159 event.setIqId(id);
3160 event.setStreamId(streamId);
3161 event.setFileName(fname);
3162 event.setFileHash(hash);
3163 event.setFileSize(atol(sizeStr.c_str()));
3164 dispatchXmppEvent(event);
3165 return true;
3166 }
3168 //##expecting result or error
3169 //file sends id'd by message id's
3170 std::map<DOMString, XmppStream *>::iterator iter =
3171 fileSends.find(id);
3172 if (iter != fileSends.end())
3173 {
3174 XmppStream *outf = iter->second;
3175 if (from != outf->getPeerId())
3176 return true;
3177 if (type == "error")
3178 {
3179 outf->setState(STREAM_ERROR);
3180 error("user '%s' rejected file", from.c_str());
3181 return true;
3182 }
3183 else if (type == "result")
3184 {
3185 if (outf->getState() == STREAM_OPENING)
3186 {
3187 XmppEvent event(XmppEvent::XmppEvent::EVENT_FILE_ACCEPTED);
3188 event.setFrom(from);
3189 dispatchXmppEvent(event);
3190 outf->setState(STREAM_OPEN);
3191 }
3192 else if (outf->getState() == STREAM_CLOSING)
3193 {
3194 outf->setState(STREAM_CLOSED);
3195 }
3196 return true;
3197 }
3198 }
3200 return true;
3201 }
3208 /**
3209 *
3210 */
3211 bool XmppClient::fileSend(const DOMString &destJidArg,
3212 const DOMString &offeredNameArg,
3213 const DOMString &fileNameArg,
3214 const DOMString &descriptionArg)
3215 {
3216 DOMString destJid = destJidArg;
3217 DOMString offeredName = offeredNameArg;
3218 DOMString fileName = fileNameArg;
3219 DOMString description = descriptionArg;
3221 struct stat finfo;
3222 if (stat(fileName.c_str(), &finfo)<0)
3223 {
3224 error("Cannot stat file '%s' for sending", fileName.c_str());
3225 return false;
3226 }
3227 long fileLen = finfo.st_size;
3228 if (!fileLen > 1000000)
3229 {
3230 error("'%s' too large", fileName.c_str());
3231 return false;
3232 }
3233 if (!S_ISREG(finfo.st_mode))
3234 {
3235 error("'%s' is not a regular file", fileName.c_str());
3236 return false;
3237 }
3238 FILE *f = fopen(fileName.c_str(), "rb");
3239 if (!f)
3240 {
3241 error("cannot open '%s' for sending", fileName.c_str());
3242 return false;
3243 }
3244 std::vector<unsigned char> sendBuf;
3245 Md5 md5hash;
3246 for (long i=0 ; i<fileLen && !feof(f); i++)
3247 {
3248 int ch = fgetc(f);
3249 if (ch<0)
3250 break;
3251 md5hash.append((unsigned char)ch);
3252 sendBuf.push_back((unsigned char)ch);
3253 }
3254 fclose(f);
3255 DOMString hash = md5hash.finishHex();
3256 printf("Hash:%s\n", hash.c_str());
3259 //## get the last path segment from the whole path
3260 if (offeredName.size()<1)
3261 {
3262 int slashPos = -1;
3263 for (unsigned int i=0 ; i<fileName.size() ; i++)
3264 {
3265 int ch = fileName[i];
3266 if (ch == '/' || ch == '\\')
3267 slashPos = i;
3268 }
3269 if (slashPos>=0 && slashPos<=(int)(fileName.size()-1))
3270 {
3271 offeredName = fileName.substr(slashPos+1,
3272 fileName.size()-slashPos-1);
3273 printf("offeredName:%s\n", offeredName.c_str());
3274 }
3275 }
3277 char buf[32];
3278 snprintf(buf, 31, "file%d", getMsgId());
3279 DOMString messageId = buf;
3281 XmppStream *outf = new XmppStream();
3283 outf->setState(STREAM_OPENING);
3284 outf->setMessageId(messageId);
3285 fileSends[messageId] = outf;
3287 snprintf(buf, 31, "stream%d", getMsgId());
3288 DOMString streamId = buf;
3289 //outf->setStreamId(streamId);
3291 outf->setPeerId(destJid);
3293 char dtgBuf[81];
3294 struct tm *timeVal = gmtime(&(finfo.st_mtime));
3295 strftime(dtgBuf, 80, "%Y-%m-%dT%H:%M:%Sz", timeVal);
3297 const char *fmt =
3298 "<%s type='set' id='%s' to='%s'>"
3299 "<si xmlns='http://jabber.org/protocol/si' id='%s'"
3300 " mime-type='text/plain'"
3301 " profile='http://jabber.org/protocol/si/profile/file-transfer'>"
3302 "<file xmlns='http://jabber.org/protocol/si/profile/file-transfer'"
3303 " name='%s' size='%d' hash='%s' date='%s'><desc>%s</desc></file>"
3304 "<feature xmlns='http://jabber.org/protocol/feature-neg'>"
3305 "<x xmlns='jabber:x:data' type='form'>"
3306 "<field var='stream-method' type='list-single'>"
3307 //"<option><value>http://jabber.org/protocol/bytestreams</value></option>"
3308 "<option><value>http://jabber.org/protocol/ibb</value></option>"
3309 "</field></x></feature></si></%s>\n";
3310 if (!write(fmt, streamPacket.c_str(),
3311 messageId.c_str(), destJid.c_str(),
3312 streamId.c_str(), offeredName.c_str(), fileLen,
3313 hash.c_str(), dtgBuf, description.c_str(),
3314 streamPacket.c_str()))
3315 {
3316 return false;
3317 }
3319 int ret = true;
3320 int state = outf->getState();
3321 for (int tim=0 ; tim<20 ; tim++)
3322 {
3323 printf("##### waiting for open\n");
3324 if (state == STREAM_OPEN)
3325 {
3326 outf->reset();
3327 break;
3328 }
3329 else if (state == STREAM_ERROR)
3330 {
3331 printf("ERROR\n");
3332 outf->reset();
3333 ret = false;
3334 }
3335 Thread::sleep(1000);
3336 state = outf->getState();
3337 }
3338 if (state != STREAM_OPEN)
3339 {
3340 printf("TIMEOUT ERROR\n");
3341 ret = false;
3342 }
3344 //free up this resource
3345 fileSends.erase(messageId);
3346 delete outf;
3348 if (!outputStreamOpen(destJid, streamId))
3349 {
3350 error("cannot open output stream %s", streamId.c_str());
3351 return false;
3352 }
3354 if (!outputStreamWrite(streamId, sendBuf))
3355 {
3356 }
3358 if (!outputStreamClose(streamId))
3359 {
3360 }
3362 return true;
3363 }
3366 class FileSendThread : public Thread
3367 {
3368 public:
3370 FileSendThread(XmppClient &par,
3371 const DOMString &destJidArg,
3372 const DOMString &offeredNameArg,
3373 const DOMString &fileNameArg,
3374 const DOMString &descriptionArg) : client(par)
3375 {
3376 destJid = destJidArg;
3377 offeredName = offeredNameArg;
3378 fileName = fileNameArg;
3379 description = descriptionArg;
3380 }
3382 virtual ~FileSendThread() {}
3384 void run()
3385 {
3386 client.fileSend(destJid, offeredName,
3387 fileName, description);
3388 }
3390 private:
3392 XmppClient &client;
3393 DOMString destJid;
3394 DOMString offeredName;
3395 DOMString fileName;
3396 DOMString description;
3397 };
3399 /**
3400 *
3401 */
3402 bool XmppClient::fileSendBackground(const DOMString &destJid,
3403 const DOMString &offeredName,
3404 const DOMString &fileName,
3405 const DOMString &description)
3406 {
3407 FileSendThread thread(*this, destJid, offeredName,
3408 fileName, description);
3409 thread.start();
3410 return true;
3411 }
3414 /**
3415 *
3416 */
3417 bool XmppClient::fileReceive(const DOMString &fromJid,
3418 const DOMString &iqId,
3419 const DOMString &streamId,
3420 const DOMString &fileName,
3421 long /*fileSize*/,
3422 const DOMString &/*fileHash*/)
3423 {
3424 const char *fmt =
3425 "<%s type='result' to='%s' id='%s'>"
3426 "<si xmlns='http://jabber.org/protocol/si'>"
3427 "<file xmlns='http://jabber.org/protocol/si/profile/file-transfer'/>"
3428 "<feature xmlns='http://jabber.org/protocol/feature-neg'>"
3429 "<x xmlns='jabber:x:data' type='submit'>"
3430 "<field var='stream-method'>"
3431 "<value>http://jabber.org/protocol/ibb</value>"
3432 "</field></x></feature></si></%s>\n";
3433 if (!write(fmt, streamPacket.c_str(),
3434 fromJid.c_str(), iqId.c_str(),
3435 streamPacket.c_str()))
3436 {
3437 return false;
3438 }
3440 if (!inputStreamOpen(fromJid, streamId, iqId))
3441 {
3442 return false;
3443 }
3445 XmppStream *ins = inputStreams[streamId];
3447 Md5 md5;
3448 FILE *f = fopen(fileName.c_str(), "wb");
3449 if (!f)
3450 {
3451 return false;
3452 }
3454 while (true)
3455 {
3456 if (ins->available()<1)
3457 {
3458 if (ins->getState() == STREAM_CLOSING)
3459 break;
3460 pause(100);
3461 continue;
3462 }
3463 std::vector<unsigned char> ret = ins->read();
3464 std::vector<unsigned char>::iterator iter;
3465 for (iter=ret.begin() ; iter!=ret.end() ; iter++)
3466 {
3467 unsigned char ch = *iter;
3468 md5.append(&ch, 1);
3469 fwrite(&ch, 1, 1, f);
3470 }
3471 }
3473 inputStreamClose(streamId);
3474 fclose(f);
3476 DOMString hash = md5.finishHex();
3477 printf("received file hash:%s\n", hash.c_str());
3479 return true;
3480 }
3484 class FileReceiveThread : public Thread
3485 {
3486 public:
3488 FileReceiveThread(XmppClient &par,
3489 const DOMString &fromJidArg,
3490 const DOMString &iqIdArg,
3491 const DOMString &streamIdArg,
3492 const DOMString &fileNameArg,
3493 long fileSizeArg,
3494 const DOMString &fileHashArg) : client(par)
3495 {
3496 fromJid = fromJidArg;
3497 iqId = iqIdArg;
3498 streamId = streamIdArg;
3499 fileName = fileNameArg;
3500 fileSize = fileSizeArg;
3501 fileHash = fileHashArg;
3502 }
3504 virtual ~FileReceiveThread() {}
3506 void run()
3507 {
3508 client.fileReceive(fromJid, iqId, streamId,
3509 fileName, fileSize, fileHash);
3510 }
3512 private:
3514 XmppClient &client;
3515 DOMString fromJid;
3516 DOMString iqId;
3517 DOMString streamId;
3518 DOMString fileName;
3519 long fileSize;
3520 DOMString fileHash;
3521 };
3523 /**
3524 *
3525 */
3526 bool XmppClient::fileReceiveBackground(const DOMString &fromJid,
3527 const DOMString &iqId,
3528 const DOMString &streamId,
3529 const DOMString &fileName,
3530 long fileSize,
3531 const DOMString &fileHash)
3532 {
3533 FileReceiveThread thread(*this, fromJid, iqId, streamId,
3534 fileName, fileSize, fileHash);
3535 thread.start();
3536 return true;
3537 }
3541 //########################################################################
3542 //# X M P P G R O U P C H A T
3543 //########################################################################
3545 /**
3546 *
3547 */
3548 XmppGroupChat::XmppGroupChat(const DOMString &groupJidArg)
3549 {
3550 groupJid = groupJidArg;
3551 }
3553 /**
3554 *
3555 */
3556 XmppGroupChat::XmppGroupChat(const XmppGroupChat &other)
3557 {
3558 groupJid = other.groupJid;
3559 userList = other.userList;
3560 }
3562 /**
3563 *
3564 */
3565 XmppGroupChat::~XmppGroupChat()
3566 {
3567 }
3570 /**
3571 *
3572 */
3573 DOMString XmppGroupChat::getGroupJid()
3574 {
3575 return groupJid;
3576 }
3579 void XmppGroupChat::userAdd(const DOMString &nick,
3580 const DOMString &jid)
3581 {
3582 std::vector<XmppUser>::iterator iter;
3583 for (iter= userList.begin() ; iter!=userList.end() ; iter++)
3584 {
3585 if (iter->nick == nick)
3586 return;
3587 }
3588 XmppUser user(jid, nick);
3589 userList.push_back(user);
3590 }
3592 void XmppGroupChat::userShow(const DOMString &nick,
3593 const DOMString &show)
3594 {
3595 DOMString theShow = show;
3596 if (theShow == "")
3597 theShow = "available"; // a join message will now have a show
3598 std::vector<XmppUser>::iterator iter;
3599 for (iter= userList.begin() ; iter!=userList.end() ; iter++)
3600 {
3601 if (iter->nick == nick)
3602 iter->show = theShow;
3603 }
3604 }
3606 void XmppGroupChat::userDelete(const DOMString &nick)
3607 {
3608 std::vector<XmppUser>::iterator iter;
3609 for (iter= userList.begin() ; iter!=userList.end() ; )
3610 {
3611 if (iter->nick == nick)
3612 iter = userList.erase(iter);
3613 else
3614 iter++;
3615 }
3616 }
3618 std::vector<XmppUser> XmppGroupChat::getUserList() const
3619 {
3620 return userList;
3621 }
3631 } //namespace Pedro
3632 //########################################################################
3633 //# E N D O F F I L E
3634 //########################################################################