commit d01f0a9e1307a4b83d69a68123616b77207d391f Author: Payden Sutherland Date: Fri Oct 5 20:46:37 2012 -0400 Initial commit of libwsclient Only got the handshake and basic text frame sending done. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1e3eff --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# http://www.gnu.org/software/automake + +Makefile.in +Makefile +# http://www.gnu.org/software/autoconf +*.la +/autom4te.cache +/aclocal.m4 +/compile +/configure +/depcomp +/install-sh +/missing +test +*~ +.*~ +.cproject +.project +.settings +*.o +*.lo +.*.swp +.libs +.deps + +*.html +/autom4te.cache +/stamp-h1 +Makefile +/config.status +/config.log +/config.h +/aclocal.m4 +Makefile.in +/config/ +/configure +*~ +m4/*.m4 +/build*/ +ltmain.sh +libtool +libwebsock.la diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..e69de29 diff --git a/COPYING b/COPYING new file mode 120000 index 0000000..6168a39 --- /dev/null +++ b/COPYING @@ -0,0 +1 @@ +/usr/share/automake-1.11/COPYING \ No newline at end of file diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/INSTALL b/INSTALL new file mode 120000 index 0000000..cbd1c80 --- /dev/null +++ b/INSTALL @@ -0,0 +1 @@ +/usr/share/automake-1.11/INSTALL \ No newline at end of file diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..b37e14b --- /dev/null +++ b/Makefile.am @@ -0,0 +1,5 @@ +lib_LTLIBRARIES=libwsclient.la +libwsclient_la_SOURCES = wsclient.c base64.c sha1.c +library_includedir=$(includedir)/wsclient +library_include_HEADERS = wsclient.h +ACLOCAL_AMFLAGS = -I m4 diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..deae53b --- /dev/null +++ b/autogen.sh @@ -0,0 +1,5 @@ +#! /bin/sh +libtoolize +aclocal \ +&& automake --add-missing \ +&& autoconf diff --git a/base64.c b/base64.c new file mode 100644 index 0000000..d2f5c10 --- /dev/null +++ b/base64.c @@ -0,0 +1,215 @@ +#include +#include +#include + + + +/** + * characters used for Base64 encoding + */ +const char *BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * encode three bytes using base64 (RFC 3548) + * + * @param triple three bytes that should be encoded + * @param result buffer of four characters where the result is stored + */ +void _base64_encode_triple(unsigned char triple[3], char result[4]) + { + int tripleValue, i; + + tripleValue = triple[0]; + tripleValue *= 256; + tripleValue += triple[1]; + tripleValue *= 256; + tripleValue += triple[2]; + + for (i=0; i<4; i++) + { + result[3-i] = BASE64_CHARS[tripleValue%64]; + tripleValue /= 64; + } +} + +/** + * encode an array of bytes using Base64 (RFC 3548) + * + * @param source the source buffer + * @param sourcelen the length of the source buffer + * @param target the target buffer + * @param targetlen the length of the target buffer + * @return 1 on success, 0 otherwise + */ +int base64_encode(unsigned char *source, size_t sourcelen, char *target, size_t targetlen) + { + /* check if the result will fit in the target buffer */ + if ((sourcelen+2)/3*4 > targetlen-1) + return 0; + + /* encode all full triples */ + while (sourcelen >= 3) + { + _base64_encode_triple(source, target); + sourcelen -= 3; + source += 3; + target += 4; + } + + /* encode the last one or two characters */ + if (sourcelen > 0) + { + unsigned char temp[3]; + memset(temp, 0, sizeof(temp)); + memcpy(temp, source, sourcelen); + _base64_encode_triple(temp, target); + target[3] = '='; + if (sourcelen == 1) + target[2] = '='; + + target += 4; + } + + /* terminate the string */ + target[0] = 0; + + return 1; +} + +/** + * determine the value of a base64 encoding character + * + * @param base64char the character of which the value is searched + * @return the value in case of success (0-63), -1 on failure + */ +int _base64_char_value(char base64char) + { + if (base64char >= 'A' && base64char <= 'Z') + return base64char-'A'; + if (base64char >= 'a' && base64char <= 'z') + return base64char-'a'+26; + if (base64char >= '0' && base64char <= '9') + return base64char-'0'+2*26; + if (base64char == '+') + return 2*26+10; + if (base64char == '/') + return 2*26+11; + return -1; +} + +/** + * decode a 4 char base64 encoded byte triple + * + * @param quadruple the 4 characters that should be decoded + * @param result the decoded data + * @return lenth of the result (1, 2 or 3), 0 on failure + */ +int _base64_decode_triple(char quadruple[4], unsigned char *result) + { + int i, triple_value, bytes_to_decode = 3, only_equals_yet = 1; + int char_value[4]; + + for (i=0; i<4; i++) + char_value[i] = _base64_char_value(quadruple[i]); + + /* check if the characters are valid */ + for (i=3; i>=0; i--) + { + if (char_value[i]<0) + { + if (only_equals_yet && quadruple[i]=='=') + { + /* we will ignore this character anyway, make it something + * that does not break our calculations */ + char_value[i]=0; + bytes_to_decode--; + continue; + } + return 0; + } + /* after we got a real character, no other '=' are allowed anymore */ + only_equals_yet = 0; + } + + /* if we got "====" as input, bytes_to_decode is -1 */ + if (bytes_to_decode < 0) + bytes_to_decode = 0; + + /* make one big value out of the partial values */ + triple_value = char_value[0]; + triple_value *= 64; + triple_value += char_value[1]; + triple_value *= 64; + triple_value += char_value[2]; + triple_value *= 64; + triple_value += char_value[3]; + + /* break the big value into bytes */ + for (i=bytes_to_decode; i<3; i++) + triple_value /= 256; + for (i=bytes_to_decode-1; i>=0; i--) + { + result[i] = triple_value%256; + triple_value /= 256; + } + + return bytes_to_decode; +} + +/** + * decode base64 encoded data + * + * @param source the encoded data (zero terminated) + * @param target pointer to the target buffer + * @param targetlen length of the target buffer + * @return length of converted data on success, -1 otherwise + */ +size_t base64_decode(char *source, unsigned char *target, size_t targetlen) + { + char *src, *tmpptr; + char quadruple[4], tmpresult[3]; + int i, tmplen = 3; + size_t converted = 0; + + /* concatinate '===' to the source to handle unpadded base64 data */ + src = (char *)malloc(strlen(source)+5); + if (src == NULL) + return -1; + strcpy(src, source); + strcat(src, "===="); + tmpptr = src; + + /* convert as long as we get a full result */ + while (tmplen == 3) + { + /* get 4 characters to convert */ + for (i=0; i<4; i++) + { + /* skip invalid characters - we won't reach the end */ + while (*tmpptr != '=' && _base64_char_value(*tmpptr)<0) + tmpptr++; + + quadruple[i] = *(tmpptr++); + } + + /* convert the characters */ + tmplen = _base64_decode_triple(quadruple, tmpresult); + + /* check if the fit in the result buffer */ + if (targetlen < tmplen) + { + free(src); + return -1; + } + + /* put the partial result in the result buffer */ + memcpy(target, tmpresult, tmplen); + target += tmplen; + targetlen -= tmplen; + converted += tmplen; + } + + free(src); + return converted; +} + diff --git a/config.guess b/config.guess new file mode 120000 index 0000000..405bc32 --- /dev/null +++ b/config.guess @@ -0,0 +1 @@ +/usr/share/automake-1.11/config.guess \ No newline at end of file diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..bac670e --- /dev/null +++ b/config.h.in @@ -0,0 +1,97 @@ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Define to 1 if you have the header file. */ +#undef HAVE_DLFCN_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if your system has a GNU libc compatible `malloc' function, and + to 0 otherwise. */ +#undef HAVE_MALLOC + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the `memset' function. */ +#undef HAVE_MEMSET + +/* Define to 1 if you have the header file. */ +#undef HAVE_NETDB_H + +/* Define to 1 if your system has a GNU libc compatible `realloc' function, + and to 0 otherwise. */ +#undef HAVE_REALLOC + +/* Define to 1 if you have the `socket' function. */ +#undef HAVE_SOCKET + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the `strchr' function. */ +#undef HAVE_STRCHR + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strstr' function. */ +#undef HAVE_STRSTR + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_SOCKET_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#undef LT_OBJDIR + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Version number of package */ +#undef VERSION + +/* Define to rpl_malloc if the replacement function should be used. */ +#undef malloc + +/* Define to rpl_realloc if the replacement function should be used. */ +#undef realloc + +/* Define to `unsigned int' if does not define. */ +#undef size_t diff --git a/config.sub b/config.sub new file mode 120000 index 0000000..4d47fbc --- /dev/null +++ b/config.sub @@ -0,0 +1 @@ +/usr/share/automake-1.11/config.sub \ No newline at end of file diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..e2bbf1d --- /dev/null +++ b/configure.ac @@ -0,0 +1,28 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.60]) +AC_INIT([libwsclient], [1.0.1], [payden@paydensutherland.com]) +AM_INIT_AUTOMAKE +LT_INIT([disable-static]) +AC_CONFIG_SRCDIR([wsclient.c]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) +# Checks for programs. +AC_PROG_CC + +# Checks for libraries. + +# Checks for header files. +AC_CHECK_HEADERS([netdb.h stdlib.h string.h sys/socket.h unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_SIZE_T + +# Checks for library functions. +AC_FUNC_MALLOC +AC_FUNC_REALLOC +AC_CHECK_FUNCS([memset socket strstr strchr]) + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/sha1.c b/sha1.c new file mode 100644 index 0000000..d87c7f4 --- /dev/null +++ b/sha1.c @@ -0,0 +1,371 @@ +/* + * sha1.c + * + * Copyright (C) 1998, 2009 + * Paul E. Jones + * All Rights Reserved + * + ***************************************************************************** + * $Id: sha1.c 12 2009-06-22 19:34:25Z paulej $ + ***************************************************************************** + * + * Description: + * This file implements the Secure Hashing Standard as defined + * in FIPS PUB 180-1 published April 17, 1995. + * + * The Secure Hashing Standard, which uses the Secure Hashing + * Algorithm (SHA), produces a 160-bit message digest for a + * given data stream. In theory, it is highly improbable that + * two messages will produce the same message digest. Therefore, + * this algorithm can serve as a means of providing a "fingerprint" + * for a message. + * + * Portability Issues: + * SHA-1 is defined in terms of 32-bit "words". This code was + * written with the expectation that the processor has at least + * a 32-bit machine word size. If the machine word size is larger, + * the code should still function properly. One caveat to that + * is that the input functions taking characters and character + * arrays assume that only 8 bits of information are stored in each + * character. + * + * Caveats: + * SHA-1 is designed to work with messages less than 2^64 bits + * long. Although SHA-1 allows a message digest to be generated for + * messages of any number of bits less than 2^64, this + * implementation only works with messages with a length that is a + * multiple of the size of an 8-bit character. + * + */ + +#include "sha1.h" + +/* + * Define the circular shift macro + */ +#define SHA1CircularShift(bits,word) \ + ((((word) << (bits)) & 0xFFFFFFFF) | \ + ((word) >> (32-(bits)))) + +/* Function prototypes */ +void SHA1ProcessMessageBlock(SHA1Context *); +void SHA1PadMessage(SHA1Context *); + +/* + * SHA1Reset + * + * Description: + * This function will initialize the SHA1Context in preparation + * for computing a new message digest. + * + * Parameters: + * context: [in/out] + * The context to reset. + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1Reset(SHA1Context *context) +{ + context->Length_Low = 0; + context->Length_High = 0; + context->Message_Block_Index = 0; + + context->Message_Digest[0] = 0x67452301; + context->Message_Digest[1] = 0xEFCDAB89; + context->Message_Digest[2] = 0x98BADCFE; + context->Message_Digest[3] = 0x10325476; + context->Message_Digest[4] = 0xC3D2E1F0; + + context->Computed = 0; + context->Corrupted = 0; +} + +/* + * SHA1Result + * + * Description: + * This function will return the 160-bit message digest into the + * Message_Digest array within the SHA1Context provided + * + * Parameters: + * context: [in/out] + * The context to use to calculate the SHA-1 hash. + * + * Returns: + * 1 if successful, 0 if it failed. + * + * Comments: + * + */ +int SHA1Result(SHA1Context *context) +{ + + if (context->Corrupted) + { + return 0; + } + + if (!context->Computed) + { + SHA1PadMessage(context); + context->Computed = 1; + } + + return 1; +} + +/* + * SHA1Input + * + * Description: + * This function accepts an array of octets as the next portion of + * the message. + * + * Parameters: + * context: [in/out] + * The SHA-1 context to update + * message_array: [in] + * An array of characters representing the next portion of the + * message. + * length: [in] + * The length of the message in message_array + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1Input( SHA1Context *context, + const unsigned char *message_array, + unsigned length) +{ + if (!length) + { + return; + } + + if (context->Computed || context->Corrupted) + { + context->Corrupted = 1; + return; + } + + while(length-- && !context->Corrupted) + { + context->Message_Block[context->Message_Block_Index++] = + (*message_array & 0xFF); + + context->Length_Low += 8; + /* Force it to 32 bits */ + context->Length_Low &= 0xFFFFFFFF; + if (context->Length_Low == 0) + { + context->Length_High++; + /* Force it to 32 bits */ + context->Length_High &= 0xFFFFFFFF; + if (context->Length_High == 0) + { + /* Message is too long */ + context->Corrupted = 1; + } + } + + if (context->Message_Block_Index == 64) + { + SHA1ProcessMessageBlock(context); + } + + message_array++; + } +} + +/* + * SHA1ProcessMessageBlock + * + * Description: + * This function will process the next 512 bits of the message + * stored in the Message_Block array. + * + * Parameters: + * None. + * + * Returns: + * Nothing. + * + * Comments: + * Many of the variable names in the SHAContext, especially the + * single character names, were used because those were the names + * used in the publication. + * + * + */ +void SHA1ProcessMessageBlock(SHA1Context *context) +{ + const unsigned K[] = /* Constants defined in SHA-1 */ + { + 0x5A827999, + 0x6ED9EBA1, + 0x8F1BBCDC, + 0xCA62C1D6 + }; + int t; /* Loop counter */ + unsigned temp; /* Temporary word value */ + unsigned W[80]; /* Word sequence */ + unsigned A, B, C, D, E; /* Word buffers */ + + /* + * Initialize the first 16 words in the array W + */ + for(t = 0; t < 16; t++) + { + W[t] = ((unsigned) context->Message_Block[t * 4]) << 24; + W[t] |= ((unsigned) context->Message_Block[t * 4 + 1]) << 16; + W[t] |= ((unsigned) context->Message_Block[t * 4 + 2]) << 8; + W[t] |= ((unsigned) context->Message_Block[t * 4 + 3]); + } + + for(t = 16; t < 80; t++) + { + W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); + } + + A = context->Message_Digest[0]; + B = context->Message_Digest[1]; + C = context->Message_Digest[2]; + D = context->Message_Digest[3]; + E = context->Message_Digest[4]; + + for(t = 0; t < 20; t++) + { + temp = SHA1CircularShift(5,A) + + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 20; t < 40; t++) + { + temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 40; t < 60; t++) + { + temp = SHA1CircularShift(5,A) + + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 60; t < 80; t++) + { + temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + context->Message_Digest[0] = + (context->Message_Digest[0] + A) & 0xFFFFFFFF; + context->Message_Digest[1] = + (context->Message_Digest[1] + B) & 0xFFFFFFFF; + context->Message_Digest[2] = + (context->Message_Digest[2] + C) & 0xFFFFFFFF; + context->Message_Digest[3] = + (context->Message_Digest[3] + D) & 0xFFFFFFFF; + context->Message_Digest[4] = + (context->Message_Digest[4] + E) & 0xFFFFFFFF; + + context->Message_Block_Index = 0; +} + +/* + * SHA1PadMessage + * + * Description: + * According to the standard, the message must be padded to an even + * 512 bits. The first padding bit must be a '1'. The last 64 + * bits represent the length of the original message. All bits in + * between should be 0. This function will pad the message + * according to those rules by filling the Message_Block array + * accordingly. It will also call SHA1ProcessMessageBlock() + * appropriately. When it returns, it can be assumed that the + * message digest has been computed. + * + * Parameters: + * context: [in/out] + * The context to pad + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1PadMessage(SHA1Context *context) +{ + /* + * Check to see if the current message block is too small to hold + * the initial padding bits and length. If so, we will pad the + * block, process it, and then continue padding into a second + * block. + */ + if (context->Message_Block_Index > 55) + { + context->Message_Block[context->Message_Block_Index++] = 0x80; + while(context->Message_Block_Index < 64) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + + SHA1ProcessMessageBlock(context); + + while(context->Message_Block_Index < 56) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + } + else + { + context->Message_Block[context->Message_Block_Index++] = 0x80; + while(context->Message_Block_Index < 56) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + } + + /* + * Store the message length as the last 8 octets + */ + context->Message_Block[56] = (context->Length_High >> 24) & 0xFF; + context->Message_Block[57] = (context->Length_High >> 16) & 0xFF; + context->Message_Block[58] = (context->Length_High >> 8) & 0xFF; + context->Message_Block[59] = (context->Length_High) & 0xFF; + context->Message_Block[60] = (context->Length_Low >> 24) & 0xFF; + context->Message_Block[61] = (context->Length_Low >> 16) & 0xFF; + context->Message_Block[62] = (context->Length_Low >> 8) & 0xFF; + context->Message_Block[63] = (context->Length_Low) & 0xFF; + + SHA1ProcessMessageBlock(context); +} diff --git a/sha1.h b/sha1.h new file mode 100644 index 0000000..1ca4b10 --- /dev/null +++ b/sha1.h @@ -0,0 +1,54 @@ +/* + * sha1.h + * + * Copyright (C) 1998, 2009 + * Paul E. Jones + * All Rights Reserved + * + ***************************************************************************** + * $Id: sha1.h 12 2009-06-22 19:34:25Z paulej $ + ***************************************************************************** + * + * Description: + * This class implements the Secure Hashing Standard as defined + * in FIPS PUB 180-1 published April 17, 1995. + * + * Many of the variable names in the SHA1Context, especially the + * single character names, were used because those were the names + * used in the publication. + * + * Please read the file sha1.c for more information. + * + */ + +#ifndef _SHA1_H_ +#define _SHA1_H_ + +/* + * This structure will hold context information for the hashing + * operation + */ +typedef struct SHA1Context +{ + unsigned Message_Digest[5]; /* Message Digest (output) */ + + unsigned Length_Low; /* Message length in bits */ + unsigned Length_High; /* Message length in bits */ + + unsigned char Message_Block[64]; /* 512-bit message blocks */ + int Message_Block_Index; /* Index into message block array */ + + int Computed; /* Is the digest computed? */ + int Corrupted; /* Is the message digest corruped? */ +} SHA1Context; + +/* + * Function Prototypes + */ +void SHA1Reset(SHA1Context *); +int SHA1Result(SHA1Context *); +void SHA1Input( SHA1Context *, + const unsigned char *, + unsigned); + +#endif diff --git a/test.c b/test.c new file mode 100644 index 0000000..ea4bb87 --- /dev/null +++ b/test.c @@ -0,0 +1,29 @@ +#include +#include +#include + +#include + +int onopen(void) { + fprintf(stderr, "Connection opened.\n"); + return 0; +} + +int onmessage(char *msg, int64_t length) { + fprintf(stderr, "Received message (%ull): %s\n", length, msg); + return 0; +} + +int main(int argc, char **argv) { + wsclient *client = libwsclient_new("ws://localhost:3333/mtgox"); + if(!client) { + fprintf(stderr, "Unable to initialize new WS client.\n"); + exit(1); + } + client->onopen = &onopen; + client->onmessage = &onmessage; + libwsclient_send(client, "Testing"); + libwsclient_run(client); + return 0; +} + diff --git a/wsclient.c b/wsclient.c new file mode 100644 index 0000000..e6fe39c --- /dev/null +++ b/wsclient.c @@ -0,0 +1,300 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wsclient.h" +#include "sha1.h" + +int libwsclient_run(wsclient *c) { + +} + + + +int libwsclient_open_connection(const char *host, const char *port) { + struct addrinfo hints, *servinfo, *p; + int rv, sockfd; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if((rv = getaddrinfo(host, port, &hints, &servinfo)) != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); + return -1; + } + + for(p = servinfo; p != NULL; p = p->ai_next) { + if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { + perror("socket"); + continue; + } + if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { + close(sockfd); + perror("connect"); + continue; + } + break; + } + if(p == NULL) { + fprintf(stderr, "Failed to connect.\n"); + freeaddrinfo(servinfo); + return -1; + } + return sockfd; +} + +wsclient *libwsclient_new(const char *URI) { + SHA1Context shactx; + const char *UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + char pre_encode[256]; + char sha1bytes[20]; + char expected_base64[512]; + char request_headers[1024]; + char websocket_key[256]; + char key_nonce[16]; + char scheme[10]; + char host[255]; + char request_host[255]; + char port[10]; + char path[255]; + char recv_buf[1024]; + char *URI_copy = NULL, *p = NULL, *rcv = NULL, *tok = NULL; + int i, z, sockfd, n, flags = 0, headers_space = 1024; + wsclient *client = (wsclient *)malloc(sizeof(wsclient)); + if(!client) { + fprintf(stderr, "Unable to allocate memory in libwsclient_new.\n"); + exit(1); + } + memset(client, 0, sizeof(wsclient)); + URI_copy = (char *)malloc(strlen(URI)+1); + if(!URI_copy) { + fprintf(stderr, "Unable to allocate memory in libwsclient_new.\n"); + exit(2); + } + memset(URI_copy, 0, strlen(URI)+1); + strncpy(URI_copy, URI, strlen(URI)); + p = strstr(URI_copy, "://"); + if(p == NULL) { + fprintf(stderr, "Malformed or missing scheme for URI.\n"); + exit(3); + } + strncpy(scheme, URI_copy, p-URI_copy); + scheme[p-URI_copy] = '\0'; + if(strcmp(scheme, "ws") != 0 && strcmp(scheme, "wss") != 0) { + fprintf(stderr, "Invalid scheme for URI: %s\n", scheme); + exit(4); + } + for(i=p-URI_copy+3,z=0;*(URI_copy+i) != '/' && *(URI_copy+i) != ':' && *(URI_copy+i) != '\0';i++,z++) { + host[z] = *(URI_copy+i); + } + host[z] = '\0'; + if(*(URI_copy+i) == '\0') { + //end of URI request path will be / + strncpy(path, "/", 1); + } else { + if(*(URI_copy+i) != ':') { + if(strcmp(scheme, "ws") == 0) { + strncpy(port, "80", 9); + } else { + strncpy(port, "443", 9); + client->flags |= CLIENT_IS_SSL; + } + } else { + i++; + p = strchr(URI_copy+i, '/'); + strncpy(port, URI_copy+i, (p - (URI_copy+i))); + port[p-(URI_copy+i)] = '\0'; + i += p-(URI_copy+i); + } + } + strncpy(path, URI_copy+i, 254); + free(URI_copy); + sockfd = libwsclient_open_connection(host, port); + if(sockfd == -1) { + fprintf(stderr, "Error opening socket.\n"); + exit(5); + } + client->sockfd = sockfd; + + //perform handshake + //generate nonce + srand(time(NULL)); + for(z=0;z<16;z++) { + key_nonce[z] = rand() & 0xff; + } + base64_encode(key_nonce, 16, websocket_key, 256); + memset(request_headers, 0, 1024); + + if(strcmp(port, "80") != 0) { + snprintf(request_host, 256, "%s:%s", host, port); + } else { + snprintf(request_host, 256, "%s", host); + } + snprintf(request_headers, 1024, "GET %s HTTP/1.1\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nHost: %s\r\nSec-WebSocket-Key: %s\r\nSec-WebSocket-Version: 13\r\n\r\n", path, request_host, websocket_key); + n = send(client->sockfd, request_headers, strlen(request_headers), 0); + z = 0; + memset(recv_buf, 0, 1024); + do { + n = recv(client->sockfd, recv_buf + z, 1023 - z, 0); + z += n; + } while((z < 4 || strcmp(recv_buf + z - 4, "\r\n\r\n") != 0) && n > 0); + //parse recv_buf for response headers and assure Accept matches expected value + rcv = (char *)malloc(strlen(recv_buf)+1); + if(!rcv) { + fprintf(stderr, "Unable to allocate memory in libwsclient_new.\n"); + exit(6); + } + memset(rcv, 0, strlen(recv_buf)+1); + strncpy(rcv, recv_buf, strlen(recv_buf)); + memset(pre_encode, 0, 256); + snprintf(pre_encode, 256, "%s%s", websocket_key, UUID); + SHA1Reset(&shactx); + SHA1Input(&shactx, pre_encode, strlen(pre_encode)); + SHA1Result(&shactx); + memset(pre_encode, 0, 256); + snprintf(pre_encode, 256, "%08x%08x%08x%08x%08x", shactx.Message_Digest[0], shactx.Message_Digest[1], shactx.Message_Digest[2], shactx.Message_Digest[3], shactx.Message_Digest[4]); + for(z = 0; z < (strlen(pre_encode)/2);z++) + sscanf(pre_encode+(z*2), "%02hhx", sha1bytes+z); + memset(expected_base64, 0, 512); + base64_encode(sha1bytes, 20, expected_base64, 512); + for(tok = strtok(rcv, "\r\n"); tok != NULL; tok = strtok(NULL, "\r\n")) { + if(*tok == 'H' && *(tok+1) == 'T' && *(tok+2) == 'T' && *(tok+3) == 'P') { + p = strchr(tok, ' '); + p = strchr(p+1, ' '); + *p = '\0'; + if(strcmp(tok, "HTTP/1.1 101") != 0 && strcmp(tok, "HTTP/1.0 101") != 0) { + fprintf(stderr, "Invalid HTTP version or invalid HTTP status from server: %s\n", tok); + exit(7); + } + flags |= REQUEST_VALID_STATUS; + } else { + p = strchr(tok, ' '); + *p = '\0'; + if(strcmp(tok, "Upgrade:") == 0) { + if(stricmp(p+1, "websocket") == 0) { + flags |= REQUEST_HAS_UPGRADE; + } + } + if(strcmp(tok, "Connection:") == 0) { + if(stricmp(p+1, "upgrade") == 0) { + flags |= REQUEST_HAS_CONNECTION; + } + } + if(strcmp(tok, "Sec-WebSocket-Accept:") == 0) { + if(strcmp(p+1, expected_base64) == 0) { + flags |= REQUEST_VALID_ACCEPT; + } + } + } + } + if(!flags & REQUEST_HAS_UPGRADE) { + fprintf(stderr, "Response from server did not include Upgrade header, failing.\n"); + exit(8); + } + if(!flags & REQUEST_HAS_CONNECTION) { + fprintf(stderr, "Response from server did not include Connection header, failing.\n"); + exit(9); + } + if(!flags & REQUEST_VALID_ACCEPT) { + fprintf(stderr, "Server did not send valid Sec-WebSocket-Accept header, failing.\n"); + exit(10); + } + return client; +} + +//somewhat hackish stricmp +int stricmp(const char *s1, const char *s2) { + register unsigned char c1, c2; + register unsigned char flipbit = ~(1 << 5); + do { + c1 = (unsigned char)*s1++ & flipbit; + c2 = (unsigned char)*s2++ & flipbit; + if(c1 == '\0') + return c1 - c2; + } while(c1 == c2); + return c1 - c2; +} + +int libwsclient_send(wsclient *client, char *strdata) { + if(strdata == NULL) { + fprintf(stderr, "Will not send empty message.\n"); + return -1; + } + int sockfd = client->sockfd; + struct timeval tv; + unsigned char mask[4]; + unsigned int mask_int; + unsigned long long payload_len; + unsigned char finNopcode; + unsigned int payload_len_small; + unsigned int payload_offset = 6; + unsigned int len_size; + unsigned long long be_payload_len; + unsigned int sent = 0; + int i; + unsigned int frame_size; + char *data; + gettimeofday(&tv); + srand(tv.tv_usec * tv.tv_sec); + mask_int = rand(); + memcpy(mask, &mask_int, 4); + payload_len = strlen(strdata); + finNopcode = 0x81; //FIN and text opcode. + if(payload_len <= 125) { + frame_size = 6 + payload_len; + data = (void *)malloc(frame_size); + payload_len_small = payload_len; + + } else if(payload_len > 125 && payload_len <= 0xffff) { + frame_size = 8 + payload_len; + data = (void *)malloc(frame_size); + payload_len_small = 126; + payload_offset += 2; + } else if(payload_len > 0xffff && payload_len <= 0xffffffffffffffffLL) { + frame_size = 14 + payload_len; + data = (void *)malloc(frame_size); + payload_len_small = 127; + payload_offset += 8; + } else { + fprintf(stderr, "Whoa man. What are you trying to send?\n"); + return -1; + } + memset(data, 0, frame_size); + payload_len_small |= 0x80; + memcpy(data, &finNopcode, 1); + memcpy(data+1, &payload_len_small, 1); //mask bit om, 7 bit payload len + if(payload_len_small == 126) { + payload_len &= 0xffff; + len_size = 2; + for(i = 0; i < len_size; i++) { + memcpy(data+2+i, (void *)&payload_len+(len_size-i-1), 1); + } + } + if(payload_len_small == 127) { + payload_len &= 0xffffffffffffffffLL; + len_size = 8; + for(i = 0; i < len_size; i++) { + memcpy(data+2+i, (void *)&payload_len+(len_size-i-1), 1); + } + } + for(i=0;i<4;i++) + *(data+(payload_offset-4)+i) = mask[i]; + + memcpy(data+payload_offset, strdata, strlen(strdata)); + for(i=0;i + +#ifndef WSCLIENT_H_ +#define WSCLIENT_H_ + +#define CLIENT_IS_SSL (1 << 0) + +#define REQUEST_HAS_CONNECTION (1 << 0) +#define REQUEST_HAS_UPGRADE (1 << 1) +#define REQUEST_VALID_STATUS (1 << 2) +#define REQUEST_VALID_ACCEPT (1 << 3) + +typedef struct _wsclient { + int sockfd; + int flags; + int (*onopen)(void); + int (*onclose)(void); + int (*onerror)(void); + int (*onmessage)(char *message, int64_t length); + void (*run)(void); +} wsclient; + +//Function defs + +wsclient *libwsclient_new(const char *URI); +int libwsclient_open_connection(const char *host, const char *port); +int stricmp(const char *s1, const char *s2); + +#endif /* WSCLIENT_H_ */