From d01f0a9e1307a4b83d69a68123616b77207d391f Mon Sep 17 00:00:00 2001 From: Payden Sutherland Date: Fri, 5 Oct 2012 20:46:37 -0400 Subject: [PATCH] Initial commit of libwsclient Only got the handshake and basic text frame sending done. --- .gitignore | 42 ++++++ AUTHORS | 0 COPYING | 1 + ChangeLog | 0 INSTALL | 1 + Makefile.am | 5 + NEWS | 0 README | 0 autogen.sh | 5 + base64.c | 215 +++++++++++++++++++++++++++++ config.guess | 1 + config.h.in | 97 ++++++++++++++ config.sub | 1 + configure.ac | 28 ++++ sha1.c | 371 +++++++++++++++++++++++++++++++++++++++++++++++++++ sha1.h | 54 ++++++++ test.c | 29 ++++ wsclient.c | 300 +++++++++++++++++++++++++++++++++++++++++ wsclient.h | 29 ++++ 19 files changed, 1179 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 120000 COPYING create mode 100644 ChangeLog create mode 120000 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100755 autogen.sh create mode 100644 base64.c create mode 120000 config.guess create mode 100644 config.h.in create mode 120000 config.sub create mode 100644 configure.ac create mode 100644 sha1.c create mode 100644 sha1.h create mode 100644 test.c create mode 100644 wsclient.c create mode 100644 wsclient.h 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_ */