diff options
Diffstat (limited to 'src/pedro/pedroxmpp.cpp')
| -rw-r--r-- | src/pedro/pedroxmpp.cpp | 4842 |
1 files changed, 4842 insertions, 0 deletions
diff --git a/src/pedro/pedroxmpp.cpp b/src/pedro/pedroxmpp.cpp new file mode 100644 index 000000000..f5ce69e0b --- /dev/null +++ b/src/pedro/pedroxmpp.cpp @@ -0,0 +1,4842 @@ +/* + * Implementation the Pedro mini-XMPP client + * + * Authors: + * Bob Jamison + * + * Copyright (C) 2005 Bob Jamison + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include <stdio.h> +#include <stdarg.h> + +#include <sys/stat.h> + +#include "pedroxmpp.h" +#include "pedrodom.h" + +#ifdef __WIN32__ + +#include <windows.h> + +#else /* UNIX */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <unistd.h> +#include <sys/ioctl.h> + +#include <pthread.h> + +#endif + +#ifdef HAVE_SSL +#include <openssl/ssl.h> +#include <openssl/err.h> +#endif + + +namespace Pedro +{ + +//######################################################################## +//######################################################################## +//### U T I L I T Y +//######################################################################## +//######################################################################## + + +//######################################################################## +//# B A S E 6 4 +//######################################################################## + +//################# +//# ENCODER +//################# + + +static char *base64encode = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * This class is for Base-64 encoding + */ +class Base64Encoder +{ + +public: + + Base64Encoder() + { + reset(); + } + + virtual ~Base64Encoder() + {} + + virtual void reset() + { + outBuf = 0L; + bitCount = 0; + buf = ""; + } + + virtual void append(int ch); + + virtual void append(char *str); + + virtual void append(unsigned char *str, int len); + + virtual void append(const DOMString &str); + + virtual DOMString finish(); + + static DOMString encode(const DOMString &str); + + +private: + + + unsigned long outBuf; + + int bitCount; + + DOMString buf; + +}; + + + +/** + * Writes the specified byte to the output buffer + */ +void Base64Encoder::append(int ch) +{ + outBuf <<= 8; + outBuf |= (ch & 0xff); + bitCount += 8; + if (bitCount >= 24) + { + int indx = (int)((outBuf & 0x00fc0000L) >> 18); + int obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + indx = (int)((outBuf & 0x0003f000L) >> 12); + obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + indx = (int)((outBuf & 0x00000fc0L) >> 6); + obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + indx = (int)((outBuf & 0x0000003fL) ); + obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + bitCount = 0; + outBuf = 0L; + } +} + +/** + * Writes the specified string to the output buffer + */ +void Base64Encoder::append(char *str) +{ + while (*str) + append((int)*str++); +} + +/** + * Writes the specified string to the output buffer + */ +void Base64Encoder::append(unsigned char *str, int len) +{ + while (len>0) + { + append((int)*str++); + len--; + } +} + +/** + * Writes the specified string to the output buffer + */ +void Base64Encoder::append(const DOMString &str) +{ + append((char *)str.c_str()); +} + +/** + * Closes this output stream and releases any system resources + * associated with this stream. + */ +DOMString Base64Encoder::finish() +{ + //get any last bytes (1 or 2) out of the buffer + if (bitCount == 16) + { + outBuf <<= 2; //pad to make 18 bits + + int indx = (int)((outBuf & 0x0003f000L) >> 12); + int obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + indx = (int)((outBuf & 0x00000fc0L) >> 6); + obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + indx = (int)((outBuf & 0x0000003fL) ); + obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + buf.push_back('='); + } + else if (bitCount == 8) + { + outBuf <<= 4; //pad to make 12 bits + + int indx = (int)((outBuf & 0x00000fc0L) >> 6); + int obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + indx = (int)((outBuf & 0x0000003fL) ); + obyte = (int)base64encode[indx & 63]; + buf.push_back(obyte); + + buf.push_back('='); + buf.push_back('='); + } + + DOMString ret = buf; + reset(); + return ret; +} + + +DOMString Base64Encoder::encode(const DOMString &str) +{ + Base64Encoder encoder; + encoder.append(str); + DOMString ret = encoder.finish(); + return ret; +} + + + +//################# +//# DECODER +//################# + +static int base64decode[] = +{ +/*00*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*08*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*10*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*18*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*20*/ -1, -1, -1, -1, -1, -1, -1, -1, +/*28*/ -1, -1, -1, 62, -1, -1, -1, 63, +/*30*/ 52, 53, 54, 55, 56, 57, 58, 59, +/*38*/ 60, 61, -1, -1, -1, -1, -1, -1, +/*40*/ -1, 0, 1, 2, 3, 4, 5, 6, +/*48*/ 7, 8, 9, 10, 11, 12, 13, 14, +/*50*/ 15, 16, 17, 18, 19, 20, 21, 22, +/*58*/ 23, 24, 25, -1, -1, -1, -1, -1, +/*60*/ -1, 26, 27, 28, 29, 30, 31, 32, +/*68*/ 33, 34, 35, 36, 37, 38, 39, 40, +/*70*/ 41, 42, 43, 44, 45, 46, 47, 48, +/*78*/ 49, 50, 51, -1, -1, -1, -1, -1 +}; + +class Base64Decoder +{ +public: + Base64Decoder() + { + reset(); + } + + virtual ~Base64Decoder() + {} + + virtual void reset() + { + inCount = 0; + buf.clear(); + } + + + virtual void append(int ch); + + virtual void append(char *str); + + virtual void append(const DOMString &str); + + std::vector<unsigned char> finish(); + + static std::vector<unsigned char> decode(const DOMString &str); + + static DOMString decodeToString(const DOMString &str); + +private: + + int inBytes[4]; + int inCount; + std::vector<unsigned char> buf; +}; + +/** + * Appends one char to the decoder + */ +void Base64Decoder::append(int ch) +{ + if (isspace(ch)) + return; + else if (ch == '=') //padding + { + inBytes[inCount++] = 0; + } + else + { + int byteVal = base64decode[ch & 0x7f]; + //printf("char:%c %d\n", ch, byteVal); + if (byteVal < 0) + { + //Bad lookup value + } + inBytes[inCount++] = byteVal; + } + + if (inCount >=4 ) + { + unsigned char b0 = ((inBytes[0]<<2) & 0xfc) | ((inBytes[1]>>4) & 0x03); + unsigned char b1 = ((inBytes[1]<<4) & 0xf0) | ((inBytes[2]>>2) & 0x0f); + unsigned char b2 = ((inBytes[2]<<6) & 0xc0) | ((inBytes[3] ) & 0x3f); + buf.push_back(b0); + buf.push_back(b1); + buf.push_back(b2); + inCount = 0; + } + +} + +void Base64Decoder::append(char *str) +{ + while (*str) + append((int)*str++); +} + +void Base64Decoder::append(const DOMString &str) +{ + append((char *)str.c_str()); +} + +std::vector<unsigned char> Base64Decoder::finish() +{ + std::vector<unsigned char> ret = buf; + reset(); + return ret; +} + +std::vector<unsigned char> Base64Decoder::decode(const DOMString &str) +{ + Base64Decoder decoder; + decoder.append(str); + std::vector<unsigned char> ret = decoder.finish(); + return ret; +} + +DOMString Base64Decoder::decodeToString(const DOMString &str) +{ + Base64Decoder decoder; + decoder.append(str); + std::vector<unsigned char> ret = decoder.finish(); + DOMString buf; + for (unsigned int i=0 ; i<ret.size() ; i++) + buf.push_back(ret[i]); + return buf; +} + + + +//######################################################################## +//# S H A 1 +//######################################################################## + +class Sha1 +{ +public: + + /** + * + */ + Sha1() + { init(); } + + /** + * + */ + virtual ~Sha1() + {} + + + /** + * Static convenience method. This would be the most commonly used + * version; + * @parm digest points to a bufer of 20 unsigned chars + */ + static void hash(unsigned char *dataIn, int len, unsigned char *digest); + + /** + * Static convenience method. This will fill a string with the hex + * codex string. + */ + static DOMString hashHex(unsigned char *dataIn, int len); + + /** + * Initialize the context (also zeroizes contents) + */ + virtual void init(); + + /** + * + */ + virtual void append(unsigned char *dataIn, int len); + + /** + * + * @parm digest points to a bufer of 20 unsigned chars + */ + virtual void finish(unsigned char *digest); + + +private: + + void hashblock(); + + unsigned long H[5]; + unsigned long W[80]; + unsigned long sizeHi,sizeLo; + int lenW; + +}; + + + +void Sha1::hash(unsigned char *dataIn, int len, unsigned char *digest) +{ + Sha1 sha1; + sha1.append(dataIn, len); + sha1.finish(digest); +} + +static char *sha1hex = "0123456789abcdef"; + +DOMString Sha1::hashHex(unsigned char *dataIn, int len) +{ + unsigned char hashout[20]; + hash(dataIn, len, hashout); + DOMString ret; + for (int i=0 ; i<20 ; i++) + { + unsigned char ch = hashout[i]; + ret.push_back(sha1hex[ (ch>>4) & 15 ]); + ret.push_back(sha1hex[ ch & 15 ]); + } + return ret; +} + + +void Sha1::init() +{ + + lenW = 0; + sizeHi = 0; + sizeLo = 0; + + // Initialize H with the magic constants (see FIPS180 for constants) + H[0] = 0x67452301L; + H[1] = 0xefcdab89L; + H[2] = 0x98badcfeL; + H[3] = 0x10325476L; + H[4] = 0xc3d2e1f0L; + + for (int i = 0; i < 80; i++) + W[i] = 0; +} + + +void Sha1::append(unsigned char *dataIn, int len) +{ + // Read the data into W and process blocks as they get full + for (int i = 0; i < len; i++) + { + W[lenW / 4] <<= 8; + W[lenW / 4] |= (unsigned long)dataIn[i]; + if ((++lenW) % 64 == 0) + { + hashblock(); + lenW = 0; + } + sizeLo += 8; + sizeHi += (sizeLo < 8); + } +} + + +void Sha1::finish(unsigned char hashout[20]) +{ + unsigned char pad0x80 = 0x80; + unsigned char pad0x00 = 0x00; + unsigned char padlen[8]; + + // Pad with a binary 1 (e.g. 0x80), then zeroes, then length + padlen[0] = (unsigned char)((sizeHi >> 24) & 255); + padlen[1] = (unsigned char)((sizeHi >> 16) & 255); + padlen[2] = (unsigned char)((sizeHi >> 8) & 255); + padlen[3] = (unsigned char)((sizeHi >> 0) & 255); + padlen[4] = (unsigned char)((sizeLo >> 24) & 255); + padlen[5] = (unsigned char)((sizeLo >> 16) & 255); + padlen[6] = (unsigned char)((sizeLo >> 8) & 255); + padlen[7] = (unsigned char)((sizeLo >> 0) & 255); + + append(&pad0x80, 1); + + while (lenW != 56) + append(&pad0x00, 1); + append(padlen, 8); + + // Output hash + for (int i = 0; i < 20; i++) + { + hashout[i] = (unsigned char)(H[i / 4] >> 24); + H[i / 4] <<= 8; + } + + // Re-initialize the context (also zeroizes contents) + init(); +} + + +#define SHA_ROTL(X,n) ((((X) << (n)) | ((X) >> (32-(n)))) & 0xffffffffL) + +void Sha1::hashblock() +{ + + for (int t = 16; t <= 79; t++) + W[t] = SHA_ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1); + + unsigned long A = H[0]; + unsigned long B = H[1]; + unsigned long C = H[2]; + unsigned long D = H[3]; + unsigned long E = H[4]; + + unsigned long TEMP; + + for (int t = 0; t <= 19; t++) + { + TEMP = (SHA_ROTL(A,5) + (((C^D)&B)^D) + + E + W[t] + 0x5a827999L) & 0xffffffffL; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + for (int t = 20; t <= 39; t++) + { + TEMP = (SHA_ROTL(A,5) + (B^C^D) + + E + W[t] + 0x6ed9eba1L) & 0xffffffffL; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + for (int t = 40; t <= 59; t++) + { + TEMP = (SHA_ROTL(A,5) + ((B&C)|(D&(B|C))) + + E + W[t] + 0x8f1bbcdcL) & 0xffffffffL; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + for (int t = 60; t <= 79; t++) + { + TEMP = (SHA_ROTL(A,5) + (B^C^D) + + E + W[t] + 0xca62c1d6L) & 0xffffffffL; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + + H[0] += A; + H[1] += B; + H[2] += C; + H[3] += D; + H[4] += E; +} + + + + + +//######################################################################## +//# M D 5 +//######################################################################## + +class Md5 +{ +public: + + /** + * + */ + Md5() + { init(); } + + /** + * + */ + virtual ~Md5() + {} + + /** + * Static convenience method. + * @parm digest points to an buffer of 16 unsigned chars + */ + static void hash(unsigned char *dataIn, + unsigned long len, unsigned char *digest); + + static DOMString Md5::hashHex(unsigned char *dataIn, unsigned long len); + + /** + * Initialize the context (also zeroizes contents) + */ + virtual void init(); + + /** + * + */ + virtual void append(unsigned char *dataIn, unsigned long len); + + /** + * + */ + virtual void append(const DOMString &str); + + /** + * Finalize and output the hash. + * @parm digest points to an buffer of 16 unsigned chars + */ + virtual void finish(unsigned char *digest); + + + /** + * Same as above , but hex to an output String + */ + virtual DOMString finishHex(); + +private: + + void transform(unsigned long *buf, unsigned long *in); + + unsigned long buf[4]; + unsigned long bits[2]; + unsigned char in[64]; + +}; + + + + +void Md5::hash(unsigned char *dataIn, unsigned long len, unsigned char *digest) +{ + Md5 md5; + md5.append(dataIn, len); + md5.finish(digest); +} + +DOMString Md5::hashHex(unsigned char *dataIn, unsigned long len) +{ + Md5 md5; + md5.append(dataIn, len); + DOMString ret = md5.finishHex(); + return ret; +} + + + +/* + * Note: this code is harmless on little-endian machines. + */ +/* +static void byteReverse(unsigned char *buf, unsigned long longs) +{ + do + { + unsigned long t = (unsigned long) + ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(unsigned long *) buf = t; + buf += 4; + } while (--longs); +} +*/ + +static void md5_memcpy(void *dest, void *src, int n) +{ + unsigned char *s1 = (unsigned char *)dest; + unsigned char *s2 = (unsigned char *)src; + while (n--) + *s1++ = *s2++; +} + +static void md5_memset(void *dest, char v, int n) +{ + unsigned char *s = (unsigned char *)dest; + while (n--) + *s++ = v; +} + +/** + * Initialize MD5 polynomials and storage + */ +void Md5::init() +{ + buf[0] = 0x67452301; + buf[1] = 0xefcdab89; + buf[2] = 0x98badcfe; + buf[3] = 0x10325476; + + bits[0] = 0; + bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void Md5::append(unsigned char *source, unsigned long len) +{ + + // Update bitcount + unsigned long t = bits[0]; + if ((bits[0] = t + ((unsigned long) len << 3)) < t) + bits[1]++;// Carry from low to high + bits[1] += len >> 29; + + //Bytes already in shsInfo->data + t = (t >> 3) & 0x3f; + + + // Handle any leading odd-sized chunks + if (t) + { + unsigned char *p = (unsigned char *) in + t; + t = 64 - t; + if (len < t) + { + md5_memcpy(p, source, len); + return; + } + md5_memcpy(p, source, t); + //byteReverse(in, 16); + transform(buf, (unsigned long *) in); + source += t; + len -= t; + } + + // Process data in 64-byte chunks + while (len >= 64) + { + md5_memcpy(in, source, 64); + //byteReverse(in, 16); + transform(buf, (unsigned long *) in); + source += 64; + len -= 64; + } + + // Handle any remaining bytes of data. + md5_memcpy(in, source, len); +} + +/* + * Update context to reflect the concatenation of another string + */ +void Md5::append(const DOMString &str) +{ + append((unsigned char *)str.c_str(), str.size()); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void Md5::finish(unsigned char *digest) +{ + // Compute number of bytes mod 64 + unsigned int count = (bits[0] >> 3) & 0x3F; + + // Set the first char of padding to 0x80. + // This is safe since there is always at least one byte free + unsigned char *p = in + count; + *p++ = 0x80; + + // Bytes of padding needed to make 64 bytes + count = 64 - 1 - count; + + // Pad out to 56 mod 64 + if (count < 8) + { + // Two lots of padding: Pad the first block to 64 bytes + md5_memset(p, 0, count); + //byteReverse(in, 16); + transform(buf, (unsigned long *) in); + + // Now fill the next block with 56 bytes + md5_memset(in, 0, 56); + } + else + { + // Pad block to 56 bytes + md5_memset(p, 0, count - 8); + } + //byteReverse(in, 14); + + // Append length in bits and transform + ((unsigned long *) in)[14] = bits[0]; + ((unsigned long *) in)[15] = bits[1]; + + transform(buf, (unsigned long *) in); + //byteReverse((unsigned char *) buf, 4); + md5_memcpy(digest, buf, 16); + init(); // Security! ;-) +} + +static char *md5hex = "0123456789abcdef"; + +DOMString Md5::finishHex() +{ + unsigned char hashout[16]; + finish(hashout); + DOMString ret; + for (int i=0 ; i<16 ; i++) + { + unsigned char ch = hashout[i]; + ret.push_back(md5hex[ (ch>>4) & 15 ]); + ret.push_back(md5hex[ ch & 15 ]); + } + return ret; +} + + + +//# The four core functions - F1 is optimized somewhat + +// #define F1(x, y, z) (x & y | ~x & z) +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +// ## This is the central step in the MD5 algorithm. +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + * @parm buf points to an array of 4 unsigned longs + * @parm in points to an array of 16 unsigned longs + */ +void Md5::transform(unsigned long *buf, unsigned long *in) +{ + unsigned long a = buf[0]; + unsigned long b = buf[1]; + unsigned long c = buf[2]; + unsigned long d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[ 2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[ 7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[ 5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[ 3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[ 1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[ 8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[ 6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[ 4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[ 2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[ 9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + + + + + + + +//######################################################################## +//######################################################################## +//### T H R E A D +//######################################################################## +//######################################################################## + + +//######################################################################## +//### T H R E A D +//######################################################################## + +/** + * This is the interface for a delegate class which can + * be run by a Thread. + * Thread thread(runnable); + * thread.start(); + */ +class Runnable +{ +public: + + Runnable() + {} + virtual ~Runnable() + {} + + /** + * The method of a delegate class which can + * be run by a Thread. Thread is completed when this + * method is done. + */ + virtual void run() = 0; + +}; + + + +/** + * A simple wrapper of native threads in a portable class. + * It can be used either to execute its own run() method, or + * delegate to a Runnable class's run() method. + */ +class Thread +{ +public: + + /** + * Create a thread which will execute its own run() method. + */ + Thread() + { runnable = NULL ; started = false; } + + /** + * Create a thread which will run a Runnable class's run() method. + */ + Thread(const Runnable &runner) + { runnable = (Runnable *)&runner; started = false; } + + /** + * This does not kill a spawned thread. + */ + virtual ~Thread() + {} + + /** + * Static method to pause the current thread for a given + * number of milliseconds. + */ + static void sleep(unsigned long millis); + + /** + * This method will be executed if the Thread was created with + * no delegated Runnable class. The thread is completed when + * the method is done. + */ + virtual void run() + {} + + /** + * Starts the thread. + */ + virtual void start(); + + /** + * Calls either this class's run() method, or that of a Runnable. + * A user would normally not call this directly. + */ + virtual void execute() + { + started = true; + if (runnable) + runnable->run(); + else + run(); + } + +private: + + Runnable *runnable; + + bool started; + +}; + + + + + +#ifdef __WIN32__ + + +static DWORD WINAPI WinThreadFunction(LPVOID context) +{ + Thread *thread = (Thread *)context; + thread->execute(); + return 0; +} + + +void Thread::start() +{ + DWORD dwThreadId; + HANDLE hThread = CreateThread(NULL, 0, WinThreadFunction, + (LPVOID)this, 0, &dwThreadId); + //Make sure the thread is started before 'this' is deallocated + while (!started) + sleep(10); + CloseHandle(hThread); +} + +void Thread::sleep(unsigned long millis) +{ + Sleep(millis); +} + +#else /* UNIX */ + + +void *PthreadThreadFunction(void *context) +{ + Thread *thread = (Thread *)context; + thread->execute(); + return NULL; +} + + +void Thread::start() +{ + pthread_t thread; + + int ret = pthread_create(&thread, NULL, + PthreadThreadFunction, (void *)this); + if (ret != 0) + printf("Thread::start: thread creation failed: %s\n", strerror(ret)); + + //Make sure the thread is started before 'this' is deallocated + while (!started) + sleep(10); + +} + +void Thread::sleep(unsigned long millis) +{ + timespec requested; + requested.tv_sec = millis / 1000; + requested.tv_nsec = (millis % 1000 ) * 1000000L; + nanosleep(&requested, NULL); +} + +#endif + + +//######################################################################## +//######################################################################## +//### S O C K E T +//######################################################################## +//######################################################################## + + + + + +class TcpSocket +{ +public: + + TcpSocket(); + + TcpSocket(const std::string &hostname, int port); + + TcpSocket(const char *hostname, int port); + + TcpSocket(const TcpSocket &other); + + virtual ~TcpSocket(); + + bool isConnected(); + + void enableSSL(bool val); + + bool connect(const std::string &hostname, int portno); + + bool connect(const char *hostname, int portno); + + bool startTls(); + + bool connect(); + + bool disconnect(); + + bool setReceiveTimeout(unsigned long millis); + + long available(); + + bool write(int ch); + + bool write(char *str); + + bool write(const std::string &str); + + int read(); + + std::string readLine(); + +private: + void init(); + + std::string hostname; + int portno; + int sock; + bool connected; + + bool sslEnabled; + + unsigned long receiveTimeout; + +#ifdef HAVE_SSL + SSL_CTX *sslContext; + SSL *sslStream; +#endif + +}; + + + +//######################################################################### +//# U T I L I T Y +//######################################################################### + +static void mybzero(void *s, size_t n) +{ + unsigned char *p = (unsigned char *)s; + while (n > 0) + { + *p++ = (unsigned char)0; + n--; + } +} + +static void mybcopy(void *src, void *dest, size_t n) +{ + unsigned char *p = (unsigned char *)dest; + unsigned char *q = (unsigned char *)src; + while (n > 0) + { + *p++ = *q++; + n--; + } +} + + + +//######################################################################### +//# T C P C O N N E C T I O N +//######################################################################### + +TcpSocket::TcpSocket() +{ + init(); +} + + +TcpSocket::TcpSocket(const char *hostnameArg, int port) +{ + init(); + hostname = hostnameArg; + portno = port; +} + +TcpSocket::TcpSocket(const std::string &hostnameArg, int port) +{ + init(); + hostname = hostnameArg; + portno = port; +} + + +#ifdef HAVE_SSL + +static void cryptoLockCallback(int mode, int type, const char *file, int line) +{ + //printf("########### LOCK\n"); + static int modes[CRYPTO_NUM_LOCKS]; /* = {0, 0, ... } */ + const char *errstr = NULL; + + int rw = mode & (CRYPTO_READ|CRYPTO_WRITE); + if (!((rw == CRYPTO_READ) || (rw == CRYPTO_WRITE))) + { + errstr = "invalid mode"; + goto err; + } + + if (type < 0 || type >= CRYPTO_NUM_LOCKS) + { + errstr = "type out of bounds"; + goto err; + } + + if (mode & CRYPTO_LOCK) + { + if (modes[type]) + { + errstr = "already locked"; + /* must not happen in a single-threaded program + * (would deadlock) + */ + goto err; + } + + modes[type] = rw; + } + else if (mode & CRYPTO_UNLOCK) + { + if (!modes[type]) + { + errstr = "not locked"; + goto err; + } + + if (modes[type] != rw) + { + errstr = (rw == CRYPTO_READ) ? + "CRYPTO_r_unlock on write lock" : + "CRYPTO_w_unlock on read lock"; + } + + modes[type] = 0; + } + else + { + errstr = "invalid mode"; + goto err; + } + + err: + if (errstr) + { + /* we cannot use bio_err here */ + fprintf(stderr, "openssl (lock_dbg_cb): %s (mode=%d, type=%d) at %s:%d\n", + errstr, mode, type, file, line); + } +} + +static unsigned long cryptoIdCallback() +{ +#ifdef __WIN32__ + unsigned long ret = (unsigned long) GetCurrentThreadId(); +#else + unsigned long ret = (unsigned long) pthread_self(); +#endif + return ret; +} + +#endif + + +TcpSocket::TcpSocket(const TcpSocket &other) +{ + init(); + sock = other.sock; + hostname = other.hostname; + portno = other.portno; +} + +static bool tcp_socket_inited = false; + +void TcpSocket::init() +{ + if (!tcp_socket_inited) + { +#ifdef __WIN32__ + WORD wVersionRequested = MAKEWORD( 2, 2 ); + WSADATA wsaData; + WSAStartup( wVersionRequested, &wsaData ); +#endif +#ifdef HAVE_SSL + sslStream = NULL; + sslContext = NULL; + CRYPTO_set_locking_callback(cryptoLockCallback); + CRYPTO_set_id_callback(cryptoIdCallback); + SSL_library_init(); + SSL_load_error_strings(); +#endif + tcp_socket_inited = true; + } + sock = -1; + connected = false; + hostname = ""; + portno = -1; + sslEnabled = false; + receiveTimeout = 0; +} + +TcpSocket::~TcpSocket() +{ + disconnect(); +} + +bool TcpSocket::isConnected() +{ + if (!connected || sock < 0) + return false; + return true; +} + +void TcpSocket::enableSSL(bool val) +{ + sslEnabled = val; +} + + +bool TcpSocket::connect(const char *hostnameArg, int portnoArg) +{ + hostname = hostnameArg; + portno = portnoArg; + return connect(); +} + +bool TcpSocket::connect(const std::string &hostnameArg, int portnoArg) +{ + hostname = hostnameArg; + portno = portnoArg; + return connect(); +} + + + +#ifdef HAVE_SSL +/* +static int password_cb(char *buf, int bufLen, int rwflag, void *userdata) +{ + char *password = "password"; + if (bufLen < (int)(strlen(password)+1)) + return 0; + + strcpy(buf,password); + int ret = strlen(password); + return ret; +} + +static void infoCallback(const SSL *ssl, int where, int ret) +{ + switch (where) + { + case SSL_CB_ALERT: + { + printf("## %d SSL ALERT: %s\n", where, SSL_alert_desc_string_long(ret)); + break; + } + default: + { + printf("## %d SSL: %s\n", where, SSL_state_string_long(ssl)); + break; + } + } +} +*/ +#endif + + +bool TcpSocket::startTls() +{ +#ifdef HAVE_SSL + sslStream = NULL; + sslContext = NULL; + + //SSL_METHOD *meth = SSLv23_method(); + //SSL_METHOD *meth = SSLv3_client_method(); + SSL_METHOD *meth = TLSv1_client_method(); + sslContext = SSL_CTX_new(meth); + //SSL_CTX_set_info_callback(sslContext, infoCallback); + +#if 0 + char *keyFile = "client.pem"; + char *caList = "root.pem"; + /* Load our keys and certificates*/ + if (!(SSL_CTX_use_certificate_chain_file(sslContext, keyFile))) + { + fprintf(stderr, "Can't read certificate file\n"); + disconnect(); + return false; + } + + SSL_CTX_set_default_passwd_cb(sslContext, password_cb); + + if (!(SSL_CTX_use_PrivateKey_file(sslContext, keyFile, SSL_FILETYPE_PEM))) + { + fprintf(stderr, "Can't read key file\n"); + disconnect(); + return false; + } + + /* Load the CAs we trust*/ + if (!(SSL_CTX_load_verify_locations(sslContext, caList, 0))) + { + fprintf(stderr, "Can't read CA list\n"); + disconnect(); + return false; + } +#endif + + /* Connect the SSL socket */ + sslStream = SSL_new(sslContext); + SSL_set_fd(sslStream, sock); + + if (SSL_connect(sslStream)<=0) + { + fprintf(stderr, "SSL connect error\n"); + disconnect(); + return false; + } + + sslEnabled = true; +#endif /*HAVE_SSL*/ + return true; +} + + +bool TcpSocket::connect() +{ + if (hostname.size()<1) + { + printf("open: null hostname\n"); + return false; + } + + if (portno<1) + { + printf("open: bad port number\n"); + return false; + } + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock < 0) + { + printf("open: error creating socket\n"); + return false; + } + + char *c_hostname = (char *)hostname.c_str(); + struct hostent *server = gethostbyname(c_hostname); + if (!server) + { + printf("open: could not locate host '%s'\n", c_hostname); + return false; + } + + struct sockaddr_in serv_addr; + mybzero((char *) &serv_addr, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + mybcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, + server->h_length); + serv_addr.sin_port = htons(portno); + + int ret = ::connect(sock, (const sockaddr *)&serv_addr, sizeof(serv_addr)); + if (ret < 0) + { + printf("open: could not connect to host '%s'\n", c_hostname); + return false; + } + + if (sslEnabled) + { + if (!startTls()) + return false; + } + connected = true; + return true; +} + +bool TcpSocket::disconnect() +{ + bool ret = true; + connected = false; +#ifdef HAVE_SSL + if (sslEnabled) + { + if (sslStream) + { + int r = SSL_shutdown(sslStream); + switch(r) + { + case 1: + break; /* Success */ + case 0: + case -1: + default: + //printf("Shutdown failed"); + ret = false; + } + SSL_free(sslStream); + } + if (sslContext) + SSL_CTX_free(sslContext); + } + sslStream = NULL; + sslContext = NULL; +#endif /*HAVE_SSL*/ + +#ifdef __WIN32__ + closesocket(sock); +#else + ::close(sock); +#endif + sock = -1; + sslEnabled = false; + + return ret; +} + + + +bool TcpSocket::setReceiveTimeout(unsigned long millis) +{ + receiveTimeout = millis; + return true; +} + +/** + * For normal sockets, return the number of bytes waiting to be received. + * For SSL, just return >0 when something is ready to be read. + */ +long TcpSocket::available() +{ + if (!isConnected()) + return -1; + + long count = 0; +#ifdef __WIN32__ + if (ioctlsocket(sock, FIONREAD, (unsigned long *)&count) != 0) + return -1; +#else + if (ioctl(sock, FIONREAD, &count) != 0) + return -1; +#endif + if (count<=0 && sslEnabled) + { +#ifdef HAVE_SSL + return SSL_pending(sslStream); +#endif + } + return count; +} + + + +bool TcpSocket::write(int ch) +{ + if (!isConnected()) + { + printf("write: socket closed\n"); + return false; + } + unsigned char c = (unsigned char)ch; + + if (sslEnabled) + { +#ifdef HAVE_SSL + int r = SSL_write(sslStream, &c, 1); + if (r<=0) + { + switch(SSL_get_error(sslStream, r)) + { + default: + printf("SSL write problem"); + return -1; + } + } +#endif + } + else + { + if (send(sock, (const char *)&c, 1, 0) < 0) + //if (send(sock, &c, 1, 0) < 0) + { + printf("write: could not send data\n"); + return false; + } + } + return true; +} + +bool TcpSocket::write(char *str) +{ + if (!isConnected()) + { + printf("write(str): socket closed\n"); + return false; + } + int len = strlen(str); + + if (sslEnabled) + { +#ifdef HAVE_SSL + int r = SSL_write(sslStream, (unsigned char *)str, len); + if (r<=0) + { + switch(SSL_get_error(sslStream, r)) + { + default: + printf("SSL write problem"); + return -1; + } + } +#endif + } + else + { + if (send(sock, str, len, 0) < 0) + //if (send(sock, &c, 1, 0) < 0) + { + printf("write: could not send data\n"); + return false; + } + } + return true; +} + +bool TcpSocket::write(const std::string &str) +{ + return write((char *)str.c_str()); +} + +int TcpSocket::read() +{ + if (!isConnected()) + return -1; + + //We'll use this loop for timeouts, so that SSL and plain sockets + //will behave the same way + if (receiveTimeout > 0) + { + unsigned long tim = 0; + while (true) + { + int avail = available(); + if (avail > 0) + break; + if (tim >= receiveTimeout) + return -2; + Thread::sleep(20); + tim += 20; + } + } + + //check again + if (!isConnected()) + return -1; + + unsigned char ch; + if (sslEnabled) + { +#ifdef HAVE_SSL + if (!sslStream) + return -1; + int r = SSL_read(sslStream, &ch, 1); + unsigned long err = SSL_get_error(sslStream, r); + switch (err) + { + case SSL_ERROR_NONE: + break; + case SSL_ERROR_ZERO_RETURN: + return -1; + case SSL_ERROR_SYSCALL: + printf("SSL read problem(syscall) %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return -1; + default: + printf("SSL read problem %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return -1; + } +#endif + } + else + { + if (recv(sock, (char *)&ch, 1, 0) <= 0) + { + printf("read: could not receive data\n"); + disconnect(); + return -1; + } + } + return (int)ch; +} + +std::string TcpSocket::readLine() +{ + std::string ret; + + while (isConnected()) + { + int ch = read(); + if (ch<0) + return ret; + if (ch=='\r' || ch=='\n') + return ret; + ret.push_back((char)ch); + } + + return ret; +} + + +//######################################################################## +//######################################################################## +//### X M P P +//######################################################################## +//######################################################################## + + + + +//######################################################################## +//# X M P P E V E N T +//######################################################################## + + +XmppEvent::XmppEvent(int type) +{ + eventType = type; + presence = false; + dom = NULL; +} + +XmppEvent::XmppEvent(const XmppEvent &other) +{ + assign(other); +} + +XmppEvent &XmppEvent::operator=(const XmppEvent &other) +{ + assign(other); + return (*this); +} + +XmppEvent::~XmppEvent() +{ + if (dom) + delete dom; +} + +void XmppEvent::assign(const XmppEvent &other) +{ + eventType = other.eventType; + presence = other.presence; + status = other.status; + show = other.show; + to = other.to; + from = other.from; + group = other.group; + data = other.data; + fileName = other.fileName; + fileDesc = other.fileDesc; + fileSize = other.fileSize; + fileHash = other.fileHash; + setDOM(other.dom); +} + +int XmppEvent::getType() const +{ + return eventType; +} + +DOMString XmppEvent::getIqId() const +{ + return iqId; +} + +void XmppEvent::setIqId(const DOMString &val) +{ + iqId = val; +} + +DOMString XmppEvent::getStreamId() const +{ + return streamId; +} + +void XmppEvent::setStreamId(const DOMString &val) +{ + streamId = val; +} + +bool XmppEvent::getPresence() const +{ + return presence; +} + +void XmppEvent::setPresence(bool val) +{ + presence = val; +} + +DOMString XmppEvent::getShow() const +{ + return show; +} + +void XmppEvent::setShow(const DOMString &val) +{ + show = val; +} + +DOMString XmppEvent::getStatus() const +{ + return status; +} + +void XmppEvent::setStatus(const DOMString &val) +{ + status = val; +} + +DOMString XmppEvent::getTo() const +{ + return to; +} + +void XmppEvent::setTo(const DOMString &val) +{ + to = val; +} + +DOMString XmppEvent::getFrom() const +{ + return from; +} + +void XmppEvent::setFrom(const DOMString &val) +{ + from = val; +} + +DOMString XmppEvent::getGroup() const +{ + return group; +} + +void XmppEvent::setGroup(const DOMString &val) +{ + group = val; +} + +DOMString XmppEvent::getData() const +{ + return data; +} + +void XmppEvent::setData(const DOMString &val) +{ + data = val; +} + +DOMString XmppEvent::getFileName() const +{ + return fileName; +} + +void XmppEvent::setFileName(const DOMString &val) +{ + fileName = val; +} + +DOMString XmppEvent::getFileDesc() const +{ + return fileDesc; +} + +void XmppEvent::setFileDesc(const DOMString &val) +{ + fileDesc = val; +} + +long XmppEvent::getFileSize() const +{ + return fileSize; +} + +void XmppEvent::setFileSize(long val) +{ + fileSize = val; +} + +DOMString XmppEvent::getFileHash() const +{ + return fileHash; +} + +void XmppEvent::setFileHash(const DOMString &val) +{ + fileHash = val; +} + +Element *XmppEvent::getDOM() const +{ + return dom; +} + +void XmppEvent::setDOM(const Element *val) +{ + if (!val) + dom = NULL; + else + dom = ((Element *)val)->clone(); +} + + +std::vector<XmppUser> XmppEvent::getUserList() const +{ + return userList; +} + +void XmppEvent::setUserList(const std::vector<XmppUser> &val) +{ + userList = val; +} + +//######################################################################## +//# X M P P E V E N T T A R G E T +//######################################################################## + +//########################### +//# CONSTRUCTORS +//########################### + +XmppEventTarget::XmppEventTarget() +{ + eventQueueEnabled = false; +} + + +XmppEventTarget::XmppEventTarget(const XmppEventTarget &other) +{ + listeners = other.listeners; + eventQueueEnabled = other.eventQueueEnabled; +} + +XmppEventTarget::~XmppEventTarget() +{ +} + + +//########################### +//# M E S S A G E S +//########################### + +void XmppEventTarget::error(char *fmt, ...) +{ + va_list args; + va_start(args,fmt); + vsnprintf(targetWriteBuf, targetWriteBufLen, fmt, args); + va_end(args) ; + printf("Error:%s\n", targetWriteBuf); + XmppEvent evt(XmppEvent::EVENT_ERROR); + evt.setData(targetWriteBuf); + dispatchXmppEvent(evt); +} + +void XmppEventTarget::status(char *fmt, ...) +{ + va_list args; + va_start(args,fmt); + vsnprintf(targetWriteBuf, targetWriteBufLen, fmt, args); + va_end(args) ; + //printf("Status:%s\n", targetWriteBuf); + XmppEvent evt(XmppEvent::EVENT_STATUS); + evt.setData(targetWriteBuf); + dispatchXmppEvent(evt); +} + + + +//########################### +//# L I S T E N E R S +//########################### + +void XmppEventTarget::dispatchXmppEvent(const XmppEvent &event) +{ + std::vector<XmppEventListener *>::iterator iter; + for (iter = listeners.begin(); iter != listeners.end() ; iter++) + (*iter)->processXmppEvent(event); + if (eventQueueEnabled) + eventQueue.push_back(event); +} + +void XmppEventTarget::addXmppEventListener(const XmppEventListener &listener) +{ + XmppEventListener *lsnr = (XmppEventListener *)&listener; + std::vector<XmppEventListener *>::iterator iter; + for (iter = listeners.begin(); iter != listeners.end() ; iter++) + if (*iter == lsnr) + return; + listeners.push_back(lsnr); +} + +void XmppEventTarget::removeXmppEventListener(const XmppEventListener &listener) +{ + XmppEventListener *lsnr = (XmppEventListener *)&listener; + std::vector<XmppEventListener *>::iterator iter; + for (iter = listeners.begin(); iter != listeners.end() ; iter++) + if (*iter == lsnr) + listeners.erase(iter); +} + +void XmppEventTarget::clearXmppEventListeners() +{ + listeners.clear(); +} + + +//########################### +//# E V E N T Q U E U E +//########################### + +void XmppEventTarget::eventQueueEnable(bool val) +{ + eventQueueEnabled = val; + if (!eventQueueEnabled) + eventQueue.clear(); +} + +int XmppEventTarget::eventQueueAvailable() +{ + return eventQueue.size(); +} + +XmppEvent XmppEventTarget::eventQueuePop() +{ + if (!eventQueueEnabled || eventQueue.size()<1) + { + XmppEvent dummy(XmppEvent::EVENT_NONE); + return dummy; + } + XmppEvent event = *(eventQueue.begin()); + eventQueue.erase(eventQueue.begin()); + return event; +} + + +//######################################################################## +//# X M P P S T R E A M +//######################################################################## + +/** + * + */ +class XmppStream +{ +public: + + /** + * + */ + XmppStream(); + + /** + * + */ + virtual ~XmppStream(); + + /** + * + */ + virtual void reset(); + + /** + * + */ + virtual int getState(); + + /** + * + */ + virtual void setState(int val); + + /** + * + */ + virtual DOMString getStreamId(); + + /** + * + */ + void setStreamId(const DOMString &val); + + /** + * + */ + virtual DOMString getIqId(); + + /** + * + */ + void setIqId(const DOMString &val); + + /** + * + */ + virtual int getSeqNr(); + + /** + * + */ + virtual DOMString getPeerId(); + + /** + * + */ + virtual void setPeerId(const DOMString &val); + + /** + * + */ + int available(); + + /** + * + */ + void receiveData(std::vector<unsigned char> &newData); + + /** + * + */ + std::vector<unsigned char> read(); + +private: + + + DOMString streamId; + + DOMString iqId; + + DOMString sourceId; + + int state; + + long seqNr; + + std::vector<unsigned char> data; +}; + + +/** + * + */ +XmppStream::XmppStream() +{ + reset(); +} + +/** + * + */ +XmppStream::~XmppStream() +{ + reset(); +} + +/** + * + */ +void XmppStream::reset() +{ + state = XmppClient::STREAM_AVAILABLE; + seqNr = 0; + data.clear(); +} + +/** + * + */ +int XmppStream::getState() +{ + return state; +} + +/** + * + */ +void XmppStream::setState(int val) +{ + state = val; +} + +/** + * + */ +DOMString XmppStream::getStreamId() +{ + return streamId; +} + +/** + * + */ +void XmppStream::setStreamId(const DOMString &val) +{ + streamId = val; +} + +/** + * + */ +DOMString XmppStream::getIqId() +{ + return iqId; +} + + +/** + * + */ +void XmppStream::setIqId(const DOMString &val) +{ + iqId = val; +} + +/** + * Source or destination JID + */ +void XmppStream::setPeerId(const DOMString &val) +{ + sourceId = val; +} + +/** + * Source or destination JID + */ +DOMString XmppStream::getPeerId() +{ + return sourceId; +} + +/** + * Stream packet sequence number + */ +int XmppStream::getSeqNr() +{ + seqNr++; + if (seqNr >= 65535) + seqNr = 0; + return seqNr; +} + +/** + * + */ +int XmppStream::available() +{ + return data.size(); +} + +/** + * + */ +void XmppStream::receiveData(std::vector<unsigned char> &newData) +{ + std::vector<unsigned char>::iterator iter; + for (iter=newData.begin() ; iter!=newData.end() ; iter++) + data.push_back(*iter); +} + +/** + * + */ +std::vector<unsigned char> XmppStream::read() +{ + if (state != XmppClient::STREAM_OPEN) + { + std::vector<unsigned char>dummy; + return dummy; + } + std::vector<unsigned char> ret = data; + data.clear(); + return ret; +} + + + + + + +//######################################################################## +//# X M P P C L I E N T +//######################################################################## +class ReceiverThread : public Runnable +{ +public: + + ReceiverThread(XmppClient &par) : client(par) {} + + virtual ~ReceiverThread() {} + + void run() + { client.receiveAndProcessLoop(); } + +private: + + XmppClient &client; +}; + + +//########################### +//# CONSTRUCTORS +//########################### + +XmppClient::XmppClient() +{ + init(); +} + + +XmppClient::XmppClient(const XmppClient &other) : XmppEventTarget(other) +{ + init(); + assign(other); +} + +void XmppClient::assign(const XmppClient &other) +{ + msgId = other.msgId; + host = other.host; + realm = other.realm; + port = other.port; + username = other.username; + password = other.password; + resource = other.resource; + connected = other.connected; + groupChats = other.groupChats; +} + + +void XmppClient::init() +{ + sock = new TcpSocket(); + msgId = 0; + connected = false; + + for (int i=0 ; i<outputStreamCount ; i++) + { + outputStreams[i] = new XmppStream(); + } + for (int i=0 ; i<inputStreamCount ; i++) + { + inputStreams[i] = new XmppStream(); + } + for (int i=0 ; i<fileSendCount ; i++) + { + fileSends[i] = new XmppStream(); + } +} + +XmppClient::~XmppClient() +{ + disconnect(); + delete sock; + for (int i=0 ; i<outputStreamCount ; i++) + { + delete outputStreams[i]; + } + for (int i=0 ; i<inputStreamCount ; i++) + { + delete inputStreams[i]; + } + for (int i=0 ; i<fileSendCount ; i++) + { + delete fileSends[i]; + } + groupChatsClear(); +} + +//############################################## +//# UTILILY +//############################################## + +/** + * + */ +bool XmppClient::pause(unsigned long millis) +{ + Thread::sleep(millis); + return true; +} + + +static int strIndex(const DOMString &str, char *key) +{ + unsigned int p = str.find(key); + if (p == DOMString::npos) + return -1; + return p; +} + + +DOMString XmppClient::toXml(const DOMString &str) +{ + return Parser::encode(str); +} + +static DOMString trim(const DOMString &str) +{ + unsigned int i; + for (i=0 ; i<str.size() ; i++) + if (!isspace(str[i])) + break; + int start = i; + for (i=str.size() ; i>0 ; i--) + if (!isspace(str[i-1])) + break; + int end = i; + if (start>=end) + return ""; + return str.substr(start, end); +} + +//############################################## +//# VARIABLES (ones that need special handling) +//############################################## +/** + * + */ +DOMString XmppClient::getUsername() +{ + return username; +} + +/** + * + */ +void XmppClient::setUsername(const DOMString &val) +{ + int p = strIndex(val, "@"); + if (p > 0) + { + username = val.substr(0, p); + realm = val.substr(p+1, jid.size()-p-1); + } + else + { + realm = host; + username = val; + } +} + +//############################################## +//# CONNECTION +//############################################## + +//####################### +//# RECEIVING +//####################### +DOMString XmppClient::readStanza() +{ + int openCount = 0; + bool inTag = false; + bool slashSeen = false; + bool trivialTag = false; + bool querySeen = false; + bool inQuote = false; + bool textSeen = false; + DOMString buf; + + while (true) + { + int ch = sock->read(); + //printf("%c", ch); fflush(stdout); + if (ch<0) + { + if (ch == -2) //a simple timeout, not an error + { + //Since we are timed out, let's assume that we + //are between chunks of text. Let's reset all states. + //printf("-----#### Timeout\n"); + continue; + } + else + { + keepGoing = false; + if (!sock->isConnected()) + { + disconnect(); + return ""; + } + else + { + error("socket read error"); + disconnect(); + return ""; + } + } + } + buf.push_back(ch); + if (ch == '<') + { + inTag = true; + slashSeen = false; + querySeen = false; + inQuote = false; + textSeen = false; + trivialTag = false; + } + else if (ch == '>') + { + if (!inTag) //unescaped '>' in pcdata? horror + continue; + inTag = false; + if (!trivialTag && !querySeen) + { + if (slashSeen) + openCount--; + else + openCount++; + } + //printf("# openCount:%d t:%d q:%d\n", + // openCount, trivialTag, querySeen); + //check if we are 'balanced', but not a <?version?> tag + if (openCount <= 0 && !querySeen) + { + break; + } + //we know that this one will be open-ended + if (strIndex(buf, "<stream:stream") >= 0) + { + buf.append("</stream:stream>"); + break; + } + } + else if (ch == '/') + { + if (inTag && !inQuote) + { + slashSeen = true; + if (textSeen) // <tagName/> <--looks like this + trivialTag = true; + } + } + else if (ch == '?') + { + if (inTag && !inQuote) + querySeen = true; + } + else if (ch == '"' || ch == '\'') + { + if (inTag) + inQuote = !inQuote; + } + else + { + if (inTag && !inQuote && !isspace(ch)) + textSeen = true; + } + } + return buf; +} + + + +static bool isGroupChat(Element *root) +{ + if (!root) + return false; + std::vector<Element *>elems = root->findElements("x"); + for (unsigned int i=0 ; i<elems.size() ; i++) + { + DOMString xmlns = elems[i]->getAttribute("xmlns"); + //printf("### XMLNS ### %s\n", xmlns.c_str()); + if (strIndex(xmlns, "http://jabber.org/protocol/muc") >=0 ) + return true; + } + return false; +} + + + + +static bool parseJid(const DOMString &fullJid, + DOMString &jid, DOMString &resource) +{ + int p = strIndex(fullJid, "/"); + if (p < 0) + { + jid = fullJid; + resource = ""; + return true; + } + jid = fullJid.substr(0, p); + resource = fullJid.substr(p+1, fullJid.size()-p-1); + return true; +} + + + + +bool XmppClient::processMessage(Element *root) +{ + DOMString from = root->getTagAttribute("message", "from"); + DOMString to = root->getTagAttribute("message", "to"); + DOMString type = root->getTagAttribute("message", "type"); + + //####Check for embedded namespaces here + //# IN BAND BYTESTREAMS + DOMString ibbNamespace = "http://jabber.org/protocol/ibb"; + if (root->getTagAttribute("data", "xmlns") == ibbNamespace) + { + DOMString streamId = root->getTagAttribute("data", "sid"); + if (streamId.size() > 0) + { + for (int i=0 ; i<inputStreamCount ; i++) + { + XmppStream *ins = inputStreams[i]; + //printf("##ins:%s streamid:%s\n", + // ins->getStreamId().c_str(), streamId.c_str()); + if (ins->getStreamId() == streamId) + { + //# We have a winner + if (ins->getState() != STREAM_OPEN) + { + XmppEvent event(XmppEvent::EVENT_ERROR); + event.setFrom(from); + event.setData("received unrequested stream data"); + dispatchXmppEvent(event); + return true; + } + DOMString data = root->getTagValue("data"); + std::vector<unsigned char>binData = + Base64Decoder::decode(data); + ins->receiveData(binData); + } + } + } + } + + + //#### NORMAL MESSAGES + DOMString subject = root->getTagValue("subject"); + DOMString body = root->getTagValue("body"); + DOMString thread = root->getTagValue("thread"); + //##rfc 3921, para 2.4. ignore if no recognizable info + if (subject.size() < 1 && body.size()<1 && thread.size()<1) + return true; + + if (type == "groupchat") + { + DOMString fromGid; + DOMString fromNick; + parseJid(from, fromGid, fromNick); + //printf("fromGid:%s fromNick:%s\n", + // fromGid.c_str(), fromNick.c_str()); + DOMString toGid; + DOMString toNick; + parseJid(to, toGid, toNick); + //printf("toGid:%s toNick:%s\n", + // toGid.c_str(), toNick.c_str()); + + if (fromNick.size() > 0)//normal group message + { + XmppEvent event(XmppEvent::EVENT_MUC_MESSAGE); + event.setGroup(fromGid); + event.setFrom(fromNick); + event.setData(body); + event.setDOM(root); + dispatchXmppEvent(event); + } + else // from the server itself + { + //note the space before, so it doesnt match 'unlocked' + if (strIndex(body, " locked") >= 0) + { + printf("LOCKED!! ;)\n"); + char *fmt = + "<iq from='%s' id='create%d' to='%s' type='set'>" + "<query xmlns='http://jabber.org/protocol/muc#owner'>" + "<x xmlns='jabber:x:data' type='submit'/>" + "</query></iq>\n"; + if (!write(fmt, jid.c_str(), msgId++, fromGid.c_str())) + return false; + } + } + } + else + { + XmppEvent event(XmppEvent::EVENT_MESSAGE); + event.setFrom(from); + event.setData(body); + event.setDOM(root); + dispatchXmppEvent(event); + } + + return true; +} + + + + +bool XmppClient::processPresence(Element *root) +{ + + DOMString fullJid = root->getTagAttribute("presence", "from"); + DOMString to = root->getTagAttribute("presence", "to"); + DOMString presenceStr = root->getTagAttribute("presence", "type"); + bool presence = true; + if (presenceStr == "unavailable") + presence = false; + DOMString status = root->getTagValue("status"); + DOMString show = root->getTagValue("show"); + + if (isGroupChat(root)) + { + DOMString fromGid; + DOMString fromNick; + parseJid(fullJid, fromGid, fromNick); + //printf("fromGid:%s fromNick:%s\n", + // fromGid.c_str(), fromNick.c_str()); + DOMString item_jid = root->getTagAttribute("item", "jid"); + if (item_jid == jid) //Me + { + if (presence) + { + groupChatCreate(fromGid); + groupChatUserAdd(fromGid, fromNick, ""); + groupChatUserShow(fromGid, fromNick, "available"); + + XmppEvent event(XmppEvent::EVENT_MUC_JOIN); + event.setGroup(fromGid); + event.setFrom(fromNick); + event.setPresence(presence); + event.setShow(show); + event.setStatus(status); + dispatchXmppEvent(event); + } + else + { + groupChatDelete(fromGid); + groupChatUserDelete(fromGid, fromNick); + + XmppEvent event(XmppEvent::EVENT_MUC_LEAVE); + event.setGroup(fromGid); + event.setFrom(fromNick); + event.setPresence(presence); + event.setShow(show); + event.setStatus(status); + dispatchXmppEvent(event); + } + } + else // someone else + { + if (presence) + { + groupChatUserAdd(fromGid, fromNick, ""); + } + else + groupChatUserDelete(fromGid, fromNick); + groupChatUserShow(fromGid, fromNick, show); + XmppEvent event(XmppEvent::EVENT_MUC_PRESENCE); + event.setGroup(fromGid); + event.setFrom(fromNick); + event.setPresence(presence); + event.setShow(show); + event.setStatus(status); + dispatchXmppEvent(event); + } + } + else + { + DOMString shortJid; + DOMString dummy; + parseJid(fullJid, shortJid, dummy); + rosterShow(shortJid, show); //users in roster do not have resource + + XmppEvent event(XmppEvent::EVENT_PRESENCE); + event.setFrom(fullJid); + event.setPresence(presence); + event.setShow(show); + event.setStatus(status); + dispatchXmppEvent(event); + } + + return true; +} + + + +bool XmppClient::processIq(Element *root) +{ + DOMString from = root->getTagAttribute("iq", "from"); + DOMString id = root->getTagAttribute("iq", "id"); + DOMString type = root->getTagAttribute("iq", "type"); + DOMString xmlns = root->getTagAttribute("query", "xmlns"); + + if (id.size()<1) + return true; + + //Group chat + if (strIndex(xmlns, "http://jabber.org/protocol/muc") >=0 ) + { + printf("results of MUC query\n"); + } + //printf("###IQ xmlns:%s\n", xmlns.c_str()); + + //### FILE TRANSFERS + DOMString siNamespace = "http://jabber.org/protocol/si"; + if (root->getTagAttribute("si", "xmlns") == siNamespace) + { + if (type == "set") + { + DOMString streamId = root->getTagAttribute("si", "id"); + DOMString fname = root->getTagAttribute("file", "name"); + DOMString sizeStr = root->getTagAttribute("file", "size"); + DOMString hash = root->getTagAttribute("file", "hash"); + XmppEvent event(XmppEvent::XmppEvent::EVENT_FILE_RECEIVE); + event.setFrom(from); + event.setIqId(id); + event.setStreamId(streamId); + event.setFileName(fname); + event.setFileHash(hash); + event.setFileSize(atol(sizeStr.c_str())); + dispatchXmppEvent(event); + } + else //result + { + printf("Got result\n"); + for (int i=0 ; i<fileSendCount ; i++) + { + XmppStream *outf = fileSends[i]; + if (outf->getIqId() == id && + from == outf->getPeerId()) + { + if (type == "error") + { + outf->setState(STREAM_ERROR); + error("user '%s' rejected file", from.c_str()); + return true; + } + else if (type == "result") + { + if (outf->getState() == STREAM_OPENING) + { + XmppEvent event(XmppEvent::XmppEvent::EVENT_FILE_ACCEPTED); + event.setFrom(from); + dispatchXmppEvent(event); + outf->setState(STREAM_OPEN); + } + else if (outf->getState() == STREAM_CLOSING) + { + outf->setState(STREAM_CLOSED); + } + return true; + } + } + } + } + return true; + } + + + //### IN-BAND BYTESTREAMS + //### Incoming stream requests + DOMString ibbNamespace = "http://jabber.org/protocol/ibb"; + if (root->getTagAttribute("open", "xmlns") == ibbNamespace) + { + DOMString streamId = root->getTagAttribute("open", "sid"); + XmppEvent event(XmppEvent::XmppEvent::EVENT_STREAM_RECEIVE_INIT); + dispatchXmppEvent(event); + if (streamId.size()>0) + { + for (int i=0 ; i<inputStreamCount ; i++) + { + XmppStream *ins = inputStreams[i]; + if (ins->getStreamId() == streamId) + { + ins->setState(STREAM_OPENING); + ins->setIqId(id); + return true; + } + } + } + return true; + } + else if (root->getTagAttribute("close", "xmlns") == ibbNamespace) + { + XmppEvent event(XmppEvent::XmppEvent::EVENT_STREAM_RECEIVE_CLOSE); + dispatchXmppEvent(event); + DOMString streamId = root->getTagAttribute("close", "sid"); + if (streamId.size()>0) + { + for (int i=0 ; i<inputStreamCount ; i++) + { + XmppStream *ins = inputStreams[i]; + if (ins->getStreamId() == streamId && + from == ins->getPeerId()) + { + ins->setState(STREAM_CLOSING); + ins->setIqId(id); + return true; + } + } + } + return true; + } + //### Responses to outgoing requests + for (int i=0 ; i<outputStreamCount ; i++) + { + XmppStream *outs = outputStreams[i]; + if (outs->getIqId() == id) + { + if (type == "error") + { + outs->setState(STREAM_ERROR); + return true; + } + else if (type == "result") + { + if (outs->getState() == STREAM_OPENING) + { + outs->setState(STREAM_OPEN); + } + else if (outs->getState() == STREAM_CLOSING) + { + outs->setState(STREAM_CLOSED); + } + return true; + } + } + } + + //###Normal Roster stuff + if (root->getTagAttribute("query", "xmlns") == "jabber:iq:roster") + { + roster.clear(); + std::vector<Element *>elems = root->findElements("item"); + for (unsigned int i=0 ; i<elems.size() ; i++) + { + Element *item = elems[i]; + DOMString userJid = item->getAttribute("jid"); + DOMString name = item->getAttribute("name"); + DOMString subscription = item->getAttribute("subscription"); + DOMString group = item->getTagValue("group"); + //printf("jid:%s name:%s sub:%s group:%s\n", userJid.c_str(), name.c_str(), + // subscription.c_str(), group.c_str()); + XmppUser user(userJid, name, subscription, group); + roster.push_back(user); + } + XmppEvent event(XmppEvent::XmppEvent::EVENT_ROSTER); + dispatchXmppEvent(event); + } + + return true; +} + + + +bool XmppClient::receiveAndProcess() +{ + if (!keepGoing) + return false; + + Parser parser; + + DOMString recvBuf = readStanza(); + recvBuf = trim(recvBuf); + if (recvBuf.size() < 1) + return true; + + //Ugly hack. Apparently the first char can be dropped on timeouts + //if (recvBuf[0] != '<') + // recvBuf.insert(0, "<"); + + status("RECV: %s", recvBuf.c_str()); + Element *root = parser.parse(recvBuf); + if (!root) + { + printf("Bad elem\n"); + return true; + } + + //#### MESSAGE + std::vector<Element *>elems = root->findElements("message"); + if (elems.size()>0) + { + if (!processMessage(root)) + return false; + } + + //#### PRESENCE + elems = root->findElements("presence"); + if (elems.size()>0) + { + if (!processPresence(root)) + return false; + } + + //#### INFO + elems = root->findElements("iq"); + if (elems.size()>0) + { + if (!processIq(root)) + return false; + } + + delete root; + + return true; +} + + +bool XmppClient::receiveAndProcessLoop() +{ + keepGoing = true; + while (true) + { + if (!keepGoing) + { + printf("Abort requested\n"); + break; + } + if (!receiveAndProcess()) + return false; + } + return true; +} + +//####################### +//# SENDING +//####################### + +bool XmppClient::write(char *fmt, ...) +{ + va_list args; + va_start(args,fmt); + vsnprintf((char *)writeBuf, writeBufLen, fmt,args); + va_end(args) ; + status("SEND: %s", writeBuf); + if (!sock->write((char *)writeBuf)) + { + error("Cannot write to socket"); + return false; + } + return true; +} + +//####################### +//# CONNECT +//####################### + +bool XmppClient::checkConnect() +{ + if (!connected) + { + XmppEvent evt(XmppEvent::EVENT_ERROR); + evt.setData("Attempted operation while disconnected"); + dispatchXmppEvent(evt); + return false; + } + return true; +} + +bool XmppClient::iqAuthenticate(const DOMString &streamId) +{ + Parser parser; + + char *fmt = + "<iq type='get' to='%s' id='auth%d'>" + "<query xmlns='jabber:iq:auth'><username>%s</username></query>" + "</iq>\n"; + if (!write(fmt, realm.c_str(), msgId++, username.c_str())) + return false; + + DOMString recbuf = readStanza(); + //printf("iq received: '%s'\n", recbuf.c_str()); + Element *elem = parser.parse(recbuf); + //elem->print(); + DOMString iqType = elem->getTagAttribute("iq", "type"); + //printf("##iqType:%s\n", iqType.c_str()); + delete elem; + + if (iqType != "result") + { + error("error:server does not allow login"); + return false; + } + + bool digest = true; + if (digest) + { + //## Digest authentication + DOMString digest = streamId; + digest.append(password); + digest = Sha1::hashHex((unsigned char *)digest.c_str(), digest.size()); + //printf("digest:%s\n", digest.c_str()); + fmt = + "<iq type='set' id='auth%d'>" + "<query xmlns='jabber:iq:auth'>" + "<username>%s</username>" + "<digest>%s</digest>" + "<resource>%s</resource>" + "</query>" + "</iq>\n"; + if (!write(fmt, msgId++, username.c_str(), + digest.c_str(), resource.c_str())) + return false; + } + else + { + + //## Plaintext authentication + fmt = + "<iq type='set' id='auth%d'>" + "<query xmlns='jabber:iq:auth'>" + "<username>%s</username>" + "<password>%s</password>" + "<resource>%s</resource>" + "</query>" + "</iq>\n"; + if (!write(fmt, msgId++, username.c_str(), + password.c_str(), resource.c_str())) + return false; + } + + recbuf = readStanza(); + //printf("iq received: '%s'\n", recbuf.c_str()); + elem = parser.parse(recbuf); + //elem->print(); + iqType = elem->getTagAttribute("iq", "type"); + //printf("##iqType:%s\n", iqType.c_str()); + delete elem; + + if (iqType != "result") + { + error("server does not allow login"); + return false; + } + + return true; +} + + + +bool XmppClient::saslMd5Authenticate() +{ + Parser parser; + char *fmt = + "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' " + "mechanism='DIGEST-MD5'/>\n"; + if (!write(fmt)) + return false; + + DOMString recbuf = readStanza(); + status("challenge received: '%s'", recbuf.c_str()); + Element *elem = parser.parse(recbuf); + //elem->print(); + DOMString b64challenge = elem->getTagValue("challenge"); + delete elem; + + if (b64challenge.size() < 1) + { + error("login: no SASL challenge offered by server"); + return false; + } + DOMString challenge = Base64Decoder::decodeToString(b64challenge); + status("challenge:'%s'", challenge.c_str()); + + unsigned int p1 = challenge.find("nonce=\""); + if (p1 == DOMString::npos) + { + error("login: no SASL nonce sent by server"); + return false; + } + p1 += 7; + unsigned int p2 = challenge.find("\"", p1); + if (p2 == DOMString::npos) + { + error("login: unterminated SASL nonce sent by server"); + return false; + } + DOMString nonce = challenge.substr(p1, p2-p1); + //printf("nonce: '%s'\n", nonce.c_str()); + char idBuf[7]; + snprintf(idBuf, 6, "%dsasl", msgId++); + DOMString cnonce = Sha1::hashHex((unsigned char *)idBuf, 7); + DOMString authzid = username; authzid.append("@"); authzid.append(host); + DOMString digest_uri = "xmpp/"; digest_uri.append(host); + + //## Make A1 + Md5 md5; + md5.append(username); + md5.append(":"); + md5.append(realm); + md5.append(":"); + md5.append(password); + unsigned char a1tmp[16]; + md5.finish(a1tmp); + md5.init(); + md5.append(a1tmp, 16); + md5.append(":"); + md5.append(nonce); + md5.append(":"); + md5.append(cnonce); + md5.append(":"); + md5.append(authzid); + DOMString a1 = md5.finishHex(); + status("##a1:'%s'", a1.c_str()); + + //# Make A2 + md5.init(); + md5.append("AUTHENTICATE:"); + md5.append(digest_uri); + DOMString a2 = md5.finishHex(); + status("##a2:'%s'", a2.c_str()); + + //# Now make the response + md5.init(); + md5.append(a1); + md5.append(":"); + md5.append(nonce); + md5.append(":"); + md5.append("00000001");//nc + md5.append(":"); + md5.append(cnonce); + md5.append(":"); + md5.append("auth");//qop + md5.append(":"); + md5.append(a2); + DOMString response = md5.finishHex(); + + DOMString resp; + resp.append("username=\""); resp.append(username); resp.append("\","); + resp.append("realm=\""); resp.append(realm); resp.append("\","); + resp.append("nonce=\""); resp.append(nonce); resp.append("\","); + resp.append("cnonce=\""); resp.append(cnonce); resp.append("\","); + resp.append("nc=00000001,qop=auth,"); + resp.append("digest-uri=\""); resp.append(digest_uri); resp.append("\"," ); + resp.append("authzid=\""); resp.append(authzid); resp.append("\","); + resp.append("response=\""); resp.append(response); resp.append("\","); + resp.append("charset=utf-8"); + status("sending response:'%s'", resp.c_str()); + resp = Base64Encoder::encode(resp); + status("base64 response:'%s'", resp.c_str()); + fmt = + "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response>\n"; + if (!write(fmt, resp.c_str())) + return false; + + recbuf = readStanza(); + status("server says:: '%s'", recbuf.c_str()); + elem = parser.parse(recbuf); + //elem->print(); + b64challenge = elem->getTagValue("challenge"); + delete elem; + + if (b64challenge.size() < 1) + { + error("login: no second SASL challenge offered by server"); + return false; + } + + challenge = Base64Decoder::decodeToString(b64challenge); + status("challenge: '%s'", challenge.c_str()); + p1 = challenge.find("rspauth="); + if (p1 == DOMString::npos) + { + error("login: no SASL respauth sent by server\n"); + return false; + } + + fmt = + "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>\n"; + if (!write(fmt)) + return false; + + recbuf = readStanza(); + status("server says: '%s", recbuf.c_str()); + elem = parser.parse(recbuf); + //elem->print(); + b64challenge = elem->getTagValue("challenge"); + bool success = (elem->findElements("success").size() > 0); + delete elem; + + return success; +} + +bool XmppClient::saslPlainAuthenticate() +{ + Parser parser; + + DOMString id = username; + //id.append("@"); + //id.append(host); + Base64Encoder encoder; + encoder.append('\0'); + encoder.append(id); + encoder.append('\0'); + encoder.append(password); + DOMString base64Auth = encoder.finish(); + //printf("authbuf:%s\n", base64Auth.c_str()); + + char *fmt = + "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' " + "mechanism='PLAIN'>%s</auth>\n"; + if (!write(fmt, base64Auth.c_str())) + return false; + DOMString recbuf = readStanza(); + status("challenge received: '%s'", recbuf.c_str()); + Element *elem = parser.parse(recbuf); + + bool success = (elem->findElements("success").size() > 0); + delete elem; + + return success; +} + + + +bool XmppClient::saslAuthenticate() +{ + Parser parser; + + DOMString recbuf = readStanza(); + status("RECV: '%s'\n", recbuf.c_str()); + Element *elem = parser.parse(recbuf); + //elem->print(); + + //Check for starttls + bool wantStartTls = false; + if (elem->findElements("starttls").size() > 0) + { + wantStartTls = true; + if (elem->findElements("required").size() > 0) + status("login: STARTTLS required"); + else + status("login: STARTTLS available"); + } + + if (wantStartTls) + { + delete elem; + char *fmt = + "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n"; + if (!write(fmt)) + return false; + recbuf = readStanza(); + status("RECV: '%s'\n", recbuf.c_str()); + elem = parser.parse(recbuf); + if (elem->getTagAttribute("proceed", "xmlns").size()<1) + { + error("Server rejected TLS negotiation"); + disconnect(); + return false; + } + delete elem; + if (!sock->startTls()) + { + error("Could not start TLS"); + disconnect(); + return false; + } + + fmt = + "<stream:stream xmlns='jabber:client' " + "xmlns:stream='http://etherx.jabber.org/streams' " + "to='%s' version='1.0'>\n\n"; + if (!write(fmt, realm.c_str())) + return false; + + recbuf = readStanza(); + status("RECVx: '%s'", recbuf.c_str()); + recbuf.append("</stream:stream>"); + elem = parser.parse(recbuf); + bool success = + (elem->getTagAttribute("stream:stream", "id").size()>0); + if (!success) + { + error("STARTTLS negotiation failed"); + disconnect(); + return false; + } + delete elem; + recbuf = readStanza(); + status("RECV: '%s'\n", recbuf.c_str()); + elem = parser.parse(recbuf); + } + + //check for sasl authentication mechanisms + std::vector<Element *> elems = + elem->findElements("mechanism"); + if (elems.size() < 1) + { + error("login: no SASL mechanism offered by server"); + return false; + } + bool md5Found = false; + bool plainFound = false; + for (unsigned int i=0 ; i<elems.size() ; i++) + { + DOMString mech = elems[i]->getValue(); + if (mech == "DIGEST-MD5") + { + status("MD5 authentication offered"); + md5Found = true; + } + else if (mech == "PLAIN") + { + status("PLAIN authentication offered"); + plainFound = true; + } + } + delete elem; + + bool success = false; + if (md5Found) + { + success = saslMd5Authenticate(); + } + else if (plainFound) + { + success = saslPlainAuthenticate(); + } + else + { + error("not able to handle sasl authentication mechanisms"); + return false; + } + + if (success) + status("###### SASL authentication success\n"); + else + error("###### SASL authentication failure\n"); + + return success; +} + + + + + +bool XmppClient::createSession() +{ + + Parser parser; + if (port==443 || port==5223) + sock->enableSSL(true); + if (!sock->connect(host, port)) + { + return false; + } + + char *fmt = + "<stream:stream " + "to='%s' " + "xmlns='jabber:client' " + "xmlns:stream='http://etherx.jabber.org/streams' " + "version='1.0'>\n\n"; + if (!write(fmt, realm.c_str())) + return false; + + DOMString recbuf = readStanza(); + //printf("received: '%s'\n", recbuf.c_str()); + recbuf.append("</stream:stream>"); + Element *elem = parser.parse(recbuf); + //elem->print(); + bool useSasl = false; + DOMString streamId = elem->getTagAttribute("stream:stream", "id"); + //printf("### StreamID: %s\n", streamId.c_str()); + DOMString streamVersion = elem->getTagAttribute("stream:stream", "version"); + if (streamVersion == "1.0") + useSasl = true; + + if (useSasl) + { + if (!saslAuthenticate()) + return false; + fmt = + "<stream:stream " + "to='%s' " + "xmlns='jabber:client' " + "xmlns:stream='http://etherx.jabber.org/streams' " + "version='1.0'>\n\n"; + + if (!write(fmt, realm.c_str())) + return false; + recbuf = readStanza(); + recbuf.append("</stream:stream>\n"); + //printf("now server says:: '%s'\n", recbuf.c_str()); + elem = parser.parse(recbuf); + //elem->print(); + delete elem; + + recbuf = readStanza(); + //printf("now server says:: '%s'\n", recbuf.c_str()); + elem = parser.parse(recbuf); + bool hasBind = (elem->findElements("bind").size() > 0); + //elem->print(); + delete elem; + + if (!hasBind) + { + error("no binding provided by server"); + return false; + } + + + } + else // not SASL + { + if (!iqAuthenticate(streamId)) + return false; + } + + + //### Resource binding + fmt = + "<iq type='set' id='bind%d'>" + "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>" + "<resource>%s</resource>" + "</bind></iq>\n"; + if (!write(fmt, msgId++, resource.c_str())) + return false; + + recbuf = readStanza(); + status("bind result: '%s'", recbuf.c_str()); + elem = parser.parse(recbuf); + //elem->print(); + DOMString bindType = elem->getTagAttribute("iq", "type"); + //printf("##bindType:%s\n", bindType.c_str()); + delete elem; + + if (bindType != "result") + { + error("no binding with server failed"); + return false; + } + + fmt = + "<iq type='set' id='sess%d'>" + "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>" + "</iq>\n"; + if (!write(fmt, msgId++)) + return false; + + recbuf = readStanza(); + status("session received: '%s'", recbuf.c_str()); + elem = parser.parse(recbuf); + //elem->print(); + DOMString sessionType = elem->getTagAttribute("iq", "type"); + //printf("##sessionType:%s\n", sessionType.c_str()); + delete elem; + + if (sessionType != "result") + { + error("no session provided by server"); + return false; + } + + //printf("########## COOL #########\n"); + //Now that we are bound, we have a valid JID + jid = username; + jid.append("@"); + jid.append(realm); + jid.append("/"); + jid.append(resource); + + //We are now done with the synchronous handshaking. Let's go into + //async mode + + fmt = + "<iq type='get' id='roster%d'><query xmlns='jabber:iq:roster'/></iq>\n"; + if (!write(fmt, msgId++)) + return false; + + fmt = + "<iq type='get' id='discoItems%d' to='%s'>" + "<query xmlns='http://jabber.org/protocol/disco#items'/></iq>\n"; + if (!write(fmt, msgId++, realm.c_str())) + return false; + + fmt = + "<iq type='get' id='discoInfo%d' to='conference.%s'>" + "<query xmlns='http://jabber.org/protocol/disco#info'/></iq>\n"; + if (!write(fmt, msgId++, realm.c_str())) + return false; + + fmt = + "<presence/>\n"; + if (!write(fmt)) + return false; + + /* + recbuf = readStanza(); + status("stream received: '%s'", recbuf.c_str()); + elem = parser.parse(recbuf); + //elem->print(); + delete elem; + */ + + //We are now logged in + status("Connected"); + connected = true; + XmppEvent evt(XmppEvent::EVENT_CONNECTED); + dispatchXmppEvent(evt); + //Thread::sleep(1000000); + + sock->setReceiveTimeout(1000); + ReceiverThread runner(*this); + Thread thread(runner); + thread.start(); + + return true; +} + +bool XmppClient::connect() +{ + if (!createSession()) + { + disconnect(); + return false; + } + return true; +} + + +bool XmppClient::connect(DOMString hostArg, int portArg, + DOMString usernameArg, + DOMString passwordArg, + DOMString resourceArg) +{ + host = hostArg; + port = portArg; + password = passwordArg; + resource = resourceArg; + + //parse this one + setUsername(usernameArg); + + bool ret = connect(); + return ret; +} + +bool XmppClient::disconnect() +{ + if (connected) + { + char *fmt = + "<presence from='%s' type='unavailable'/>\n"; + write(fmt, jid.c_str()); + } + keepGoing = false; + connected = false; + Thread::sleep(3000); //allow receiving thread to quit + sock->disconnect(); + roster.clear(); + groupChatsClear(); + XmppEvent event(XmppEvent::EVENT_DISCONNECTED); + dispatchXmppEvent(event); + return true; +} + +//####################### +//# ROSTER +//####################### + +bool XmppClient::rosterAdd(const DOMString &rosterGroup, + const DOMString &otherJid, + const DOMString &name) +{ + if (!checkConnect()) + return false; + char *fmt = + "<iq from='%s' type='set' id='roster_%d'>" + "<query xmlns='jabber:iq:roster'>" + "<item jid='%s' name='%s'><group>%s</group></item>" + "</query></iq>\n"; + if (!write(fmt, jid.c_str(), msgId++, otherJid.c_str(), + name.c_str(), rosterGroup.c_str())) + { + return false; + } + return true; +} + +bool XmppClient::rosterDelete(const DOMString &otherJid) +{ + if (!checkConnect()) + return false; + char *fmt = + "<iq from='%s' type='set' id='roster_%d'>" + "<query xmlns='jabber:iq:roster'>" + "<item jid='%s' subscription='remove'><group>%s</group></item>" + "</query></iq>\n"; + if (!write(fmt, jid.c_str(), msgId++, otherJid.c_str())) + { + return false; + } + return true; +} + + +static bool xmppRosterCompare(const XmppUser& p1, const XmppUser& p2) +{ + DOMString s1 = p1.group; + DOMString s2 = p2.group; + for (unsigned int len=0 ; len<s1.size() && len<s2.size() ; len++) + { + int comp = tolower(s1[len]) - tolower(s2[len]); + if (comp) + return (comp<0); + } + + s1 = p1.jid; + s2 = p2.jid; + for (unsigned int len=0 ; len<s1.size() && len<s2.size() ; len++) + { + int comp = tolower(s1[len]) - tolower(s2[len]); + if (comp) + return (comp<0); + } + return false; +} + +std::vector<XmppUser> XmppClient::getRoster() +{ + std::vector<XmppUser> ros = roster; + std::sort(ros.begin(), ros.end(), xmppRosterCompare); + return ros; +} + +void XmppClient::rosterShow(const DOMString &jid, const DOMString &show) +{ + DOMString theShow = show; + if (theShow == "") + theShow = "available"; + + std::vector<XmppUser>::iterator iter; + for (iter=roster.begin() ; iter != roster.end() ; iter++) + { + if (iter->jid == jid) + iter->show = theShow; + } +} + +//####################### +//# CHAT (individual) +//####################### + +bool XmppClient::message(const DOMString &user, const DOMString &subj, + const DOMString &msg) +{ + if (!checkConnect()) + return false; + + DOMString xmlSubj = toXml(subj); + DOMString xmlMsg = toXml(msg); + + if (xmlSubj.size() > 0) + { + char *fmt = + "<message from='%s' to='%s' type='chat'>" + "<subject>%s</subject><body>%s</body></message>\n"; + if (!write(fmt, jid.c_str(), user.c_str(), + xmlSubj.c_str(), xmlMsg.c_str())) + return false; + } + else + { + char *fmt = + "<message from='%s' to='%s'>" + "<body>%s</body></message>\n"; + if (!write(fmt, jid.c_str(), user.c_str(), xmlMsg.c_str())) + return false; + } + return true; +} + + + +bool XmppClient::message(const DOMString &user, const DOMString &msg) +{ + return message(user, "", msg); +} + + + +bool XmppClient::presence(const DOMString &presence) +{ + if (!checkConnect()) + return false; + + DOMString xmlPres = toXml(presence); + + char *fmt = + "<presence from='%s'><show>%s</show></presence>\n"; + if (!write(fmt, jid.c_str(), xmlPres.c_str())) + return false; + return true; +} + +//####################### +//# GROUP CHAT +//####################### + +bool XmppClient::groupChatCreate(const DOMString &groupJid) +{ + std::vector<XmppGroupChat *>::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++) + { + if ((*iter)->getGroupJid() == groupJid) + { + error("Group chat '%s' already exists", groupJid.c_str()); + return false; + } + } + XmppGroupChat *chat = new XmppGroupChat(groupJid); + groupChats.push_back(chat); + return true; +} + +/** + * + */ +void XmppClient::groupChatDelete(const DOMString &groupJid) +{ + std::vector<XmppGroupChat *>::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; ) + { + XmppGroupChat *chat = *iter; + if (chat->getGroupJid() == groupJid) + { + iter = groupChats.erase(iter); + delete chat; + } + else + iter++; + } +} + +/** + * + */ +bool XmppClient::groupChatExists(const DOMString &groupJid) +{ + std::vector<XmppGroupChat *>::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++) + if ((*iter)->getGroupJid() == groupJid) + return true; + return false; +} + +/** + * + */ +void XmppClient::groupChatsClear() +{ + std::vector<XmppGroupChat *>::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++) + delete (*iter); + groupChats.clear(); +} + + +/** + * + */ +void XmppClient::groupChatUserAdd(const DOMString &groupJid, + const DOMString &nick, + const DOMString &jid) +{ + std::vector<XmppGroupChat *>::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++) + { + if ((*iter)->getGroupJid() == groupJid) + { + (*iter)->userAdd(nick, jid); + } + } +} + +/** + * + */ +void XmppClient::groupChatUserShow(const DOMString &groupJid, + const DOMString &nick, + const DOMString &show) +{ + std::vector<XmppGroupChat *>::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++) + { + if ((*iter)->getGroupJid() == groupJid) + { + (*iter)->userShow(nick, show); + } + } +} + +/** + * + */ +void XmppClient::groupChatUserDelete(const DOMString &groupJid, + const DOMString &nick) +{ + std::vector<XmppGroupChat *>::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++) + { + if ((*iter)->getGroupJid() == groupJid) + { + (*iter)->userDelete(nick); + } + } +} + +static bool xmppUserCompare(const XmppUser& p1, const XmppUser& p2) +{ + DOMString s1 = p1.nick; + DOMString s2 = p2.nick; + int comp = 0; + for (unsigned int len=0 ; len<s1.size() && len<s2.size() ; len++) + { + comp = tolower(s1[len]) - tolower(s2[len]); + if (comp) + break; + } + return (comp<0); +} + + +std::vector<XmppUser> XmppClient::groupChatGetUserList( + const DOMString &groupJid) +{ + if (!checkConnect()) + { + std::vector<XmppUser> dummy; + return dummy; + } + + std::vector<XmppGroupChat *>::iterator iter; + for (iter=groupChats.begin() ; iter!=groupChats.end() ; iter++) + { + if ((*iter)->getGroupJid() == groupJid ) + { + std::vector<XmppUser> uList = (*iter)->getUserList(); + std::sort(uList.begin(), uList.end(), xmppUserCompare); + return uList; + } + } + std::vector<XmppUser> dummy; + return dummy; +} + +bool XmppClient::groupChatJoin(const DOMString &groupJid, + const DOMString &nick, + const DOMString &pass) +{ + if (!checkConnect()) + return false; + + DOMString user = nick; + if (user.size()<1) + user = username; + + char *fmt = + "<presence to='%s/%s'>" + "<x xmlns='http://jabber.org/protocol/muc'/></presence>\n"; + if (!write(fmt, groupJid.c_str(), user.c_str())) + return false; + return true; +} + + +bool XmppClient::groupChatLeave(const DOMString &groupJid, + const DOMString &nick) +{ + if (!checkConnect()) + return false; + + DOMString user = nick; + if (user.size()<1) + user = username; + + char *fmt = + "<presence to='%s/%s' type='unavailable'>" + "<x xmlns='http://jabber.org/protocol/muc'/></presence>\n"; + if (!write(fmt, groupJid.c_str(), user.c_str())) + return false; + return true; +} + + +bool XmppClient::groupChatMessage(const DOMString &groupJid, + const DOMString &msg) +{ + if (!checkConnect()) + { + return false; + } + + DOMString xmlMsg = toXml(msg); + + char *fmt = + "<message from='%s' to='%s' type='groupchat'>" + "<body>%s</body></message>\n"; + if (!write(fmt, jid.c_str(), groupJid.c_str(), xmlMsg.c_str())) + return false; + return true; +} + +bool XmppClient::groupChatPrivateMessage(const DOMString &groupJid, + const DOMString &toNick, + const DOMString &msg) +{ + if (!checkConnect()) + return false; + + DOMString xmlMsg = toXml(msg); + + char *fmt = + "<message from='%s' to='%s/%s' type='chat'>" + "<body>%s</body></message>\n"; + if (!write(fmt, jid.c_str(), groupJid.c_str(), + toNick.c_str(), xmlMsg.c_str())) + return false; + return true; +} + +bool XmppClient::groupChatPresence(const DOMString &groupJid, + const DOMString &myNick, + const DOMString &presence) +{ + if (!checkConnect()) + return false; + + DOMString user = myNick; + if (user.size()<1) + user = username; + + DOMString xmlPresence = toXml(presence); + + char *fmt = + "<presence from='%s' to='%s/%s' type='unavailable'>" + "<x xmlns='http://jabber.org/protocol/muc'/></presence>\n"; + if (!write(fmt, jid.c_str(), groupJid.c_str(), user.c_str(), xmlPresence.c_str())) + return true; + return true; +} + + + +//####################### +//# S T R E A M S +//####################### + + +/** + * + */ +int XmppClient::outputStreamOpen(const DOMString &destId, + const DOMString &streamIdArg) +{ + int i; + for (i=0; i<outputStreamCount ; i++) + if (outputStreams[i]->getState() == STREAM_AVAILABLE) + break; + if (i>=outputStreamCount) + { + error("No available output streams"); + return -1; + } + int streamNr = i; + XmppStream *outs = outputStreams[streamNr]; + + outs->setState(STREAM_OPENING); + + char buf[32]; + snprintf(buf, 31, "inband%d", getMsgId()); + DOMString iqId = buf; + + DOMString streamId = streamIdArg; + if (streamId.size()<1) + { + snprintf(buf, 31, "stream%d", getMsgId()); + DOMString streamId = buf; + } + outs->setIqId(iqId); + outs->setStreamId(streamId); + outs->setPeerId(destId); + + char *fmt = + "<iq type='set' from='%s' to='%s' id='%s'>" + "<open sid='%s' block-size='4096'" + " xmlns='http://jabber.org/protocol/ibb'/></iq>\n"; + if (!write(fmt, jid.c_str(), + destId.c_str(), iqId.c_str(), + streamId.c_str())) + { + outs->reset(); + return -1; + } + + int state = outs->getState(); + for (int tim=0 ; tim<20 ; tim++) + { + if (state == STREAM_OPEN) + break; + else if (state == STREAM_ERROR) + { + printf("ERROR\n"); + outs->reset(); + return -1; + } + Thread::sleep(1000); + state = outs->getState(); + } + if (state != STREAM_OPEN) + { + printf("TIMEOUT ERROR\n"); + outs->reset(); + return -1; + } + + return streamNr; +} + +/** + * + */ +int XmppClient::outputStreamWrite(int streamNr, + const unsigned char *buf, unsigned long len) +{ + XmppStream *outs = outputStreams[streamNr]; + + unsigned long outLen = 0; + unsigned char *p = (unsigned char *)buf; + + while (outLen < len) + { + unsigned long chunksize = 1024; + if (chunksize + outLen > len) + chunksize = len - outLen; + + Base64Encoder encoder; + encoder.append(p, chunksize); + DOMString b64data = encoder.finish(); + p += chunksize; + outLen += chunksize; + + char *fmt = + "<message from='%s' to='%s' id='msg%d'>" + "<data xmlns='http://jabber.org/protocol/ibb' sid='%s' seq='%d'>" + "%s" + "</data>" + "<amp xmlns='http://jabber.org/protocol/amp'>" + "<rule condition='deliver-at' value='stored' action='error'/>" + "<rule condition='match-resource' value='exact' action='error'/>" + "</amp>" + "</message>\n"; + if (!write(fmt, jid.c_str(), + outs->getPeerId().c_str(), + getMsgId(), + outs->getStreamId().c_str(), + outs->getSeqNr(), + b64data.c_str())) + { + outs->reset(); + return -1; + } + pause(5000); + } + return outLen; +} + +/** + * + */ +int XmppClient::outputStreamClose(int streamNr) +{ + XmppStream *outs = outputStreams[streamNr]; + + char buf[32]; + snprintf(buf, 31, "inband%d", getMsgId()); + DOMString iqId = buf; + outs->setIqId(iqId); + + outs->setState(STREAM_CLOSING); + char *fmt = + "<iq type='set' from='%s' to='%s' id='%s'>" + "<close sid='%s' xmlns='http://jabber.org/protocol/ibb'/></iq>\n"; + if (!write(fmt, jid.c_str(), + outs->getPeerId().c_str(), + iqId.c_str(), + outs->getStreamId().c_str())) + return false; + + int state = outs->getState(); + for (int tim=0 ; tim<20 ; tim++) + { + if (state == STREAM_CLOSED) + break; + else if (state == STREAM_ERROR) + { + printf("ERROR\n"); + outs->reset(); + return -1; + } + Thread::sleep(1000); + state = outs->getState(); + } + if (state != STREAM_CLOSED) + { + printf("TIMEOUT ERROR\n"); + outs->reset(); + return -1; + } + + outs->reset(); + return 1; +} + + +/** + * + */ +int XmppClient::inputStreamOpen(const DOMString &fromJid, const DOMString &streamId, + const DOMString &iqId) +{ + int i; + for (i=0 ; i<inputStreamCount ; i++) + { + if (inputStreams[i]->getState() == STREAM_AVAILABLE) + break; + } + if (i>=inputStreamCount) + { + error("No available input streams"); + return -1; + } + int streamNr = i; + XmppStream *ins = inputStreams[streamNr]; + ins->reset(); + ins->setPeerId(fromJid); + ins->setState(STREAM_CLOSED); + ins->setStreamId(streamId); + + int state = ins->getState(); + for (int tim=0 ; tim<20 ; tim++) + { + if (state == STREAM_OPENING) + break; + else if (state == STREAM_ERROR) + { + printf("ERROR\n"); + ins->reset(); + return -1; + } + Thread::sleep(1000); + state = ins->getState(); + } + if (state != STREAM_OPENING) + { + printf("TIMEOUT ERROR\n"); + ins->reset(); + return -1; + } + char *fmt = + "<iq type='result' from='%s' to='%s' id='%s'/>\n"; + if (!write(fmt, jid.c_str(), fromJid.c_str(), ins->getIqId().c_str())) + { + return -1; + } + + ins->setState(STREAM_OPEN); + return streamNr; +} + +/** + * + */ +int XmppClient::inputStreamAvailable(int streamNr) +{ + XmppStream *ins = inputStreams[streamNr]; + return ins->available(); +} + +/** + * + */ +std::vector<unsigned char> XmppClient::inputStreamRead(int streamNr) +{ + XmppStream *ins = inputStreams[streamNr]; + return ins->read(); +} + +/** + * + */ +bool XmppClient::inputStreamClosing(int streamNr) +{ + XmppStream *ins = inputStreams[streamNr]; + if (ins->getState() == STREAM_CLOSING) + return true; + return false; +} + + +/** + * + */ +int XmppClient::inputStreamClose(int streamNr) +{ + int ret=1; + XmppStream *ins = inputStreams[streamNr]; + if (ins->getState() == STREAM_CLOSING) + { + char *fmt = + "<iq type='result' from='%s' to='%s' id='%s'/>\n"; + if (!write(fmt, jid.c_str(), ins->getPeerId().c_str(), + ins->getIqId().c_str())) + { + ret = -1; + } + } + ins->reset(); + return ret; +} + + + + +//####################### +//# FILE TRANSFERS +//####################### + + +/** + * + */ +bool XmppClient::fileSend(const DOMString &destJidArg, + const DOMString &offeredNameArg, + const DOMString &fileNameArg, + const DOMString &descriptionArg) +{ + DOMString destJid = destJidArg; + DOMString offeredName = offeredNameArg; + DOMString fileName = fileNameArg; + DOMString description = descriptionArg; + + int i; + for (i=0; i<fileSendCount ; i++) + if (fileSends[i]->getState() == STREAM_AVAILABLE) + break; + if (i>=fileSendCount) + { + error("No available file send streams"); + return false; + } + int fileSendNr = i; + XmppStream *outf = fileSends[fileSendNr]; + + outf->setState(STREAM_OPENING); + + struct stat finfo; + if (stat(fileName.c_str(), &finfo)<0) + { + error("Cannot stat file '%s' for sending", fileName.c_str()); + return false; + } + long fileLen = finfo.st_size; + if (!fileLen > 1000000) + { + error("'%s' too large", fileName.c_str()); + return false; + } + if (!S_ISREG(finfo.st_mode)) + { + error("'%s' is not a regular file", fileName.c_str()); + return false; + } + FILE *f = fopen(fileName.c_str(), "rb"); + if (!f) + { + error("cannot open '%s' for sending", fileName.c_str()); + return false; + } + unsigned char *sendBuf = (unsigned char *)malloc(fileLen+1); + if (!sendBuf) + { + error("cannot cannot allocate send buffer for %s", fileName.c_str()); + return false; + } + for (long i=0 ; i<fileLen && !feof(f); i++) + { + sendBuf[i] = fgetc(f); + } + fclose(f); + + //## get the last path segment from the whole path + if (offeredName.size()<1) + { + int slashPos = -1; + for (unsigned int i=0 ; i<fileName.size() ; i++) + { + int ch = fileName[i]; + if (ch == '/' || ch == '\\') + slashPos = i; + } + if (slashPos>=0 && slashPos<=(int)(fileName.size()-1)) + { + offeredName = fileName.substr(slashPos+1, + fileName.size()-slashPos-1); + printf("offeredName:%s\n", offeredName.c_str()); + } + } + + char buf[32]; + snprintf(buf, 31, "file%d", getMsgId()); + DOMString iqId = buf; + outf->setIqId(iqId); + + snprintf(buf, 31, "stream%d", getMsgId()); + DOMString streamId = buf; + //outf->setStreamId(streamId); + + DOMString hash = Md5::hashHex(sendBuf, fileLen); + printf("Hash:%s\n", hash.c_str()); + + outf->setPeerId(destJid); + + char dtgBuf[81]; + struct tm *timeVal = gmtime(&(finfo.st_mtime)); + strftime(dtgBuf, 80, "%Y-%m-%dT%H:%M:%Sz", timeVal); + + char *fmt = + "<iq type='set' id='%s' to='%s'>" + "<si xmlns='http://jabber.org/protocol/si' id='%s'" + " mime-type='text/plain'" + " profile='http://jabber.org/protocol/si/profile/file-transfer'>" + "<file xmlns='http://jabber.org/protocol/si/profile/file-transfer'" + " name='%s' size='%d' hash='%s' date='%s'><desc>%s</desc></file>" + "<feature xmlns='http://jabber.org/protocol/feature-neg'>" + "<x xmlns='jabber:x:data' type='form'>" + "<field var='stream-method' type='list-single'>" + //"<option><value>http://jabber.org/protocol/bytestreams</value></option>" + "<option><value>http://jabber.org/protocol/ibb</value></option>" + "</field></x></feature></si></iq>\n"; + if (!write(fmt, iqId.c_str(), destJid.c_str(), + streamId.c_str(), offeredName.c_str(), fileLen, + hash.c_str(), dtgBuf, description.c_str())) + { + free(sendBuf); + return false; + } + + int state = outf->getState(); + for (int tim=0 ; tim<20 ; tim++) + { + printf("##### waiting for open\n"); + if (state == STREAM_OPEN) + { + outf->reset(); + break; + } + else if (state == STREAM_ERROR) + { + printf("ERROR\n"); + outf->reset(); + return false; + } + Thread::sleep(1000); + state = outf->getState(); + } + if (state != STREAM_OPEN) + { + printf("TIMEOUT ERROR\n"); + outf->reset(); + return false; + } + + //free up this reqource + outf->reset(); + + int streamNr = outputStreamOpen(destJid, streamId); + if (streamNr<0) + { + error("cannot open output stream %s", streamId.c_str()); + outf->reset(); + return false; + } + + int ret = outputStreamWrite(streamNr, sendBuf, fileLen); + + if (ret<0) + { + } + + outputStreamClose(streamNr); + + free(sendBuf); + return true; +} + + +class FileSendThread : public Thread +{ +public: + + FileSendThread(XmppClient &par, + const DOMString &destJidArg, + const DOMString &offeredNameArg, + const DOMString &fileNameArg, + const DOMString &descriptionArg) : client(par) + { + destJid = destJidArg; + offeredName = offeredNameArg; + fileName = fileNameArg; + description = descriptionArg; + } + + virtual ~FileSendThread() {} + + void run() + { + client.fileSend(destJid, offeredName, + fileName, description); + } + +private: + + XmppClient &client; + DOMString destJid; + DOMString offeredName; + DOMString fileName; + DOMString description; +}; + +/** + * + */ +bool XmppClient::fileSendBackground(const DOMString &destJid, + const DOMString &offeredName, + const DOMString &fileName, + const DOMString &description) +{ + FileSendThread thread(*this, destJid, offeredName, + fileName, description); + thread.start(); + return true; +} + + +/** + * + */ +bool XmppClient::fileReceive(const DOMString &fromJid, + const DOMString &iqId, + const DOMString &streamId, + const DOMString &fileName, + long fileSize, + const DOMString &fileHash) +{ + char *fmt = + "<iq type='result' to='%s' id='%s'>" + "<si xmlns='http://jabber.org/protocol/si'>" + "<file xmlns='http://jabber.org/protocol/si/profile/file-transfer'/>" + "<feature xmlns='http://jabber.org/protocol/feature-neg'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field var='stream-method'>" + "<value>http://jabber.org/protocol/ibb</value>" + "</field></x></feature></si></iq>\n"; + if (!write(fmt, fromJid.c_str(), iqId.c_str())) + { + return false; + } + + int streamNr = inputStreamOpen(fromJid, streamId, iqId); + if (streamNr < 0) + { + return false; + } + + + Md5 md5; + FILE *f = fopen(fileName.c_str(), "wb"); + if (!f) + { + return false; + } + + while (true) + { + if (inputStreamAvailable(streamNr)<1) + { + if (inputStreamClosing(streamNr)) + break; + pause(100); + continue; + } + std::vector<unsigned char> ret = inputStreamRead(streamNr); + std::vector<unsigned char>::iterator iter; + for (iter=ret.begin() ; iter!=ret.end() ; iter++) + { + unsigned char ch = *iter; + md5.append(&ch, 1); + fwrite(&ch, 1, 1, f); + } + } + + inputStreamClose(streamNr); + fclose(f); + + DOMString hash = md5.finishHex(); + printf("received file hash:%s\n", hash.c_str()); + + return true; +} + + + +class FileReceiveThread : public Thread +{ +public: + + FileReceiveThread(XmppClient &par, + const DOMString &fromJidArg, + const DOMString &iqIdArg, + const DOMString &streamIdArg, + const DOMString &fileNameArg, + long fileSizeArg, + const DOMString &fileHashArg) : client(par) + { + fromJid = fromJidArg; + iqId = iqIdArg; + streamId = streamIdArg; + fileName = fileNameArg; + fileSize = fileSizeArg; + fileHash = fileHashArg; + } + + virtual ~FileReceiveThread() {} + + void run() + { + client.fileReceive(fromJid, iqId, streamId, + fileName, fileSize, fileHash); + } + +private: + + XmppClient &client; + DOMString fromJid; + DOMString iqId; + DOMString streamId; + DOMString fileName; + long fileSize; + DOMString fileHash; +}; + +/** + * + */ +bool XmppClient::fileReceiveBackground(const DOMString &fromJid, + const DOMString &iqId, + const DOMString &streamId, + const DOMString &fileName, + long fileSize, + const DOMString &fileHash) +{ + FileReceiveThread thread(*this, fromJid, iqId, streamId, + fileName, fileSize, fileHash); + thread.start(); + return true; +} + + + +//######################################################################## +//# X M P P G R O U P C H A T +//######################################################################## + +/** + * + */ +XmppGroupChat::XmppGroupChat(const DOMString &groupJidArg) +{ + groupJid = groupJidArg; +} + +/** + * + */ +XmppGroupChat::XmppGroupChat(const XmppGroupChat &other) +{ + groupJid = other.groupJid; + userList = other.userList; +} + +/** + * + */ +XmppGroupChat::~XmppGroupChat() +{ +} + + +/** + * + */ +DOMString XmppGroupChat::getGroupJid() +{ + return groupJid; +} + + +void XmppGroupChat::userAdd(const DOMString &nick, + const DOMString &jid) +{ + std::vector<XmppUser>::iterator iter; + for (iter= userList.begin() ; iter!=userList.end() ; iter++) + { + if (iter->nick == nick) + return; + } + XmppUser user(jid, nick); + userList.push_back(user); +} + +void XmppGroupChat::userShow(const DOMString &nick, + const DOMString &show) +{ + DOMString theShow = show; + if (theShow == "") + theShow = "available"; // a join message will now have a show + std::vector<XmppUser>::iterator iter; + for (iter= userList.begin() ; iter!=userList.end() ; iter++) + { + if (iter->nick == nick) + iter->show = theShow; + } +} + +void XmppGroupChat::userDelete(const DOMString &nick) +{ + std::vector<XmppUser>::iterator iter; + for (iter= userList.begin() ; iter!=userList.end() ; ) + { + if (iter->nick == nick) + iter = userList.erase(iter); + else + iter++; + } +} + +std::vector<XmppUser> XmppGroupChat::getUserList() const +{ + return userList; +} + + + + + + +} //namespace Pedro +//######################################################################## +//# E N D O F F I L E +//######################################################################## + + + + + + + + + + + + + + + |
