commit ff95d7a50c699421cf92f7a26517f571d5f7fbaa Author: Andy Green Date: Thu Oct 28 22:36:01 2010 +0100 initial commit diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..402227ac --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +all: + gcc -Wall -Werror -rdynamic -fPIC -c libwebsockets.c + gcc -Wall -Werror -rdynamic -fPIC -c md5.c + gcc libwebsockets.o md5.o --shared -o libwebsockets.so + +clean: + rm -f *.o *.so + diff --git a/libwebsockets.c b/libwebsockets.c new file mode 100644 index 00000000..9e7c1d89 --- /dev/null +++ b/libwebsockets.c @@ -0,0 +1,457 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "libwebsockets.h" + +void md5(const unsigned char *input, int ilen, unsigned char output[16]); + +void dostuff(struct libwebsocket *wsi, int sock); + +const struct lws_tokens lws_tokens[WSI_TOKEN_COUNT] = { + { "GET ", 4 }, + { "Host:", 5 }, + { "Connection:", 11 }, + { "Sec-WebSocket-Key1:", 19 }, + { "Sec-WebSocket-Key2:", 19 }, + { "Sec-WebSocket-Protocol:", 23 }, + { "Upgrade:", 8 }, + { "Origin:", 7 }, + { "\x0d\x0a", 2 }, +}; + +int libwebsocket_init(struct libwebsocket *wsi, int port) +{ + int n; + int sockfd, newsockfd; + unsigned int clilen; + struct sockaddr_in serv_addr, cli_addr; + int pid; + + wsi->state = WSI_STATE_CLOSED; + wsi->name_buffer_pos = 0; + wsi->response = NULL; + + for (n = 0; n < WSI_TOKEN_COUNT; n++) { + wsi->utf8_token[n].token = NULL; + wsi->utf8_token[n].token_len = 0; + } + + /* fork off a master server for this websocket server */ + + n = fork(); + if (n < 0) { + fprintf(stderr, "Failed on forking server thread: %d\n", n); + exit(1); + } + + /* we are done as far as the caller is concerned */ + + if (n) + return 0; + + /* sit there listening for connects, accept and spawn session servers */ + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) + fprintf(stderr, "ERROR opening socket"); + bzero((char *) &serv_addr, sizeof(serv_addr)); + + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = INADDR_ANY; + serv_addr.sin_port = htons(port); + if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) + fprintf(stderr, "ERROR on binding"); + + listen(sockfd, 5); + + while (1) { + clilen = sizeof(cli_addr); + + newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); + if (newsockfd < 0) + fprintf(stderr, "ERROR on accept"); + + /* fork off a new server instance */ + + pid = fork(); + if (pid < 0) + fprintf(stderr, "ERROR on fork"); + if (!pid) { + close(sockfd); + + /* sit in dostuff() until session socket closed */ + + dostuff(wsi, newsockfd); + exit(0); + } else + close(newsockfd); + } +} + +void libwebsocket_close(struct libwebsocket *wsi) +{ + int n; + + wsi->state = WSI_STATE_DEAD_SOCKET; + + if (wsi->websocket_closed_callback) + wsi->websocket_closed_callback(wsi); + + for (n = 0; n < WSI_TOKEN_COUNT; n++) + if (wsi->utf8_token[n].token) + free(wsi->utf8_token[n].token); + + if (wsi->response) + free(wsi->response); +} + + +static int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c) +{ + int n; + + switch (wsi->parser_state) { + case WSI_TOKEN_GET_URI: + case WSI_TOKEN_HOST: + case WSI_TOKEN_CONNECTION: + case WSI_TOKEN_KEY1: + case WSI_TOKEN_KEY2: + case WSI_TOKEN_PROTOCOL: + case WSI_TOKEN_UPGRADE: + case WSI_TOKEN_ORIGIN: + case WSI_TOKEN_CHALLENGE: + +// fprintf(stderr, "WSI_TOKEN_(body %d) '%c'\n", wsi->parser_state, c); + + /* collect into malloc'd buffers */ + /* optional space swallow */ + if (!wsi->utf8_token[wsi->parser_state].token_len && c == ' ') + break; + + /* special case space terminator for get-uri */ + if (wsi->parser_state == WSI_TOKEN_GET_URI && c == ' ') { + wsi->utf8_token[wsi->parser_state].token[ + wsi->utf8_token[wsi->parser_state].token_len] = '\0'; + wsi->parser_state = WSI_TOKEN_SKIPPING; + break; + } + + /* allocate appropriate memory */ + if (wsi->utf8_token[wsi->parser_state].token_len == wsi->current_alloc_len - 1) { + /* need to extend */ + wsi->current_alloc_len += LWS_ADDITIONAL_HDR_ALLOC; + if (wsi->current_alloc_len >= LWS_MAX_HEADER_LEN) { + /* it's waaay to much payload, fail it */ + strcpy(wsi->utf8_token[wsi->parser_state].token, + "!!! Length exceeded maximum supported !!!"); + wsi->parser_state = WSI_TOKEN_SKIPPING; + break; + } + wsi->utf8_token[wsi->parser_state].token = + realloc(wsi->utf8_token[wsi->parser_state].token, + wsi->current_alloc_len); + } + + /* bail at EOL */ + if (wsi->parser_state != WSI_TOKEN_CHALLENGE && c == '\x0d') { + wsi->utf8_token[wsi->parser_state].token[ + wsi->utf8_token[wsi->parser_state].token_len] = '\0'; + wsi->parser_state = WSI_TOKEN_SKIPPING_SAW_CR; + break; + } + + wsi->utf8_token[wsi->parser_state].token[ + wsi->utf8_token[wsi->parser_state].token_len++] = c; + + /* special payload limiting */ + if (wsi->parser_state == WSI_TOKEN_CHALLENGE) + if (wsi->utf8_token[wsi->parser_state].token_len == 8) { +// fprintf(stderr, "Setting WSI_PARSING_COMPLETE\n"); + wsi->parser_state = WSI_PARSING_COMPLETE; + break; + } + + break; + + /* collecting and checking a name part */ + case WSI_TOKEN_NAME_PART: +// fprintf(stderr, "WSI_TOKEN_NAME_PART '%c'\n", c); + + if (wsi->name_buffer_pos == sizeof(wsi->name_buffer) - 1) { + /* name bigger than we can handle, skip until next */ + wsi->parser_state = WSI_TOKEN_SKIPPING; + break; + } + wsi->name_buffer[wsi->name_buffer_pos++] = c; + wsi->name_buffer[wsi->name_buffer_pos] = '\0'; + + for (n = 0; n < WSI_TOKEN_COUNT; n++) { + if (wsi->name_buffer_pos != lws_tokens[n].token_len) + continue; + if (strcmp(lws_tokens[n].token, wsi->name_buffer)) + continue; + wsi->parser_state = WSI_TOKEN_GET_URI + n; + wsi->current_alloc_len = LWS_INITIAL_HDR_ALLOC; + wsi->utf8_token[wsi->parser_state].token = + malloc(wsi->current_alloc_len); + wsi->utf8_token[wsi->parser_state].token_len = 0; + n = WSI_TOKEN_COUNT; + } + if (wsi->parser_state != WSI_TOKEN_NAME_PART) + break; + break; + + /* skipping arg part of a name we didn't recognize */ + case WSI_TOKEN_SKIPPING: +// fprintf(stderr, "WSI_TOKEN_SKIPPING '%c'\n", c); + if (c == '\x0d') + wsi->parser_state = WSI_TOKEN_SKIPPING_SAW_CR; + break; + case WSI_TOKEN_SKIPPING_SAW_CR: +// fprintf(stderr, "WSI_TOKEN_SKIPPING_SAW_CR '%c'\n", c); + if (c == '\x0a') + wsi->parser_state = WSI_TOKEN_NAME_PART; + else + wsi->parser_state = WSI_TOKEN_SKIPPING; + wsi->name_buffer_pos = 0; + break; + /* we're done, ignore anything else */ + case WSI_PARSING_COMPLETE: +// fprintf(stderr, "WSI_PARSING_COMPLETE '%c'\n", c); + break; + + default: /* keep gcc happy */ + break; + } + + return 0; +} + +static int interpret_key(const char *key, unsigned int *result) +{ + char digits[20]; + int digit_pos = 0; + const char *p = key; + int spaces = 0; + + while (*p) { + if (isdigit(*p)) { + if (digit_pos == sizeof(digits) - 1) + return -1; + digits[digit_pos++] = *p; + } + p++; + } + digits[digit_pos] = '\0'; + if (!digit_pos) + return -2; + + while (*key) { + if (*key == ' ') + spaces++; + key++; + } + + if (!spaces) + return -3; + + *result = atol(digits) / spaces; + + return 0; +} + + +/* + * We have to take care about parsing because the headers may be split + * into multiple fragments. They may contain unknown headers with arbitrary + * argument lengths. + */ + +int libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len) +{ + size_t n; + char *p; + unsigned int key1, key2; + unsigned char sum[16]; + + switch (wsi->state) { + case WSI_STATE_CLOSED: + wsi->state = WSI_STATE_HANDSHAKE_RX; + wsi->parser_state = WSI_TOKEN_NAME_PART; + case WSI_STATE_HANDSHAKE_RX: + + fprintf(stderr, "issuing %ld bytes to parser\n", len); + + + fwrite(buf, 1, len, stderr); + for (n = 0; n< len; n++) + libwebsocket_parse(wsi, *buf++); + + if (wsi->parser_state != WSI_PARSING_COMPLETE) + break; + + fprintf(stderr, "Preparing return packet\n"); + + + /* Confirm we have all the necessary pieces */ + + if ( + !wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len || + !wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len || + !wsi->utf8_token[WSI_TOKEN_ORIGIN].token_len || + !wsi->utf8_token[WSI_TOKEN_HOST].token_len || + !wsi->utf8_token[WSI_TOKEN_CHALLENGE].token_len || + !wsi->utf8_token[WSI_TOKEN_KEY1].token_len || + !wsi->utf8_token[WSI_TOKEN_KEY2].token_len) { + + /* completed header processing, but missing some bits */ + goto bail; + } + + /* create the response packet */ + + wsi->response = malloc(256 + + wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len + + wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len + + wsi->utf8_token[WSI_TOKEN_HOST].token_len + + wsi->utf8_token[WSI_TOKEN_ORIGIN].token_len + + wsi->utf8_token[WSI_TOKEN_GET_URI].token_len + + wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len); + + + fprintf(stderr, "'%s;\n", wsi->utf8_token[WSI_TOKEN_HOST].token); + + p = wsi->response; + strcpy(p, "HTTP/1.1 101 WebSocket Protocol Handshake\x0d\x0aUpgrade: WebSocket\x0d\x0a"); + p += strlen("HTTP/1.1 101 WebSocket Protocol Handshake\x0d\x0aUpgrade: WebSocket\x0d\x0a"); + strcpy(p, "Connection: Upgrade\x0d\x0aSec-WebSocket-Origin: "); + p += strlen("Connection: Upgrade\x0d\x0aSec-WebSocket-Origin: "); + strcpy(p, wsi->utf8_token[WSI_TOKEN_ORIGIN].token); + p += wsi->utf8_token[WSI_TOKEN_ORIGIN].token_len; + strcpy(p, "\x0d\x0aSec-WebSocket-Location: ws://"); + p += strlen("\x0d\x0aSec-WebSocket-Location: ws://"); + strcpy(p, wsi->utf8_token[WSI_TOKEN_HOST].token); + p += wsi->utf8_token[WSI_TOKEN_HOST].token_len; + strcpy(p, wsi->utf8_token[WSI_TOKEN_GET_URI].token); + p += wsi->utf8_token[WSI_TOKEN_GET_URI].token_len; + strcpy(p, "\x0d\x0aSec-WebSocket-Protocol: "); + p += strlen("\x0d\x0aSec-WebSocket-Protocol: "); + if (wsi->utf8_token[WSI_TOKEN_PROTOCOL].token) { + strcpy(p, wsi->utf8_token[WSI_TOKEN_PROTOCOL].token); + p += wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len; + } + strcpy(p, "\x0d\x0a\x0d\x0a"); + p += strlen("\x0d\x0a\x0d\x0a"); + + if (interpret_key(wsi->utf8_token[WSI_TOKEN_KEY1].token, &key1)) + goto bail; + + if (interpret_key(wsi->utf8_token[WSI_TOKEN_KEY2].token, &key2)) + goto bail; + + sum[0] = key1 >> 24; + sum[1] = key1 >> 16; + sum[2] = key1 >> 8; + sum[3] = key1; + sum[4] = key2 >> 24; + sum[5] = key2 >> 16; + sum[6] = key2 >> 8; + sum[7] = key2; + memcpy(&sum[8], wsi->utf8_token[WSI_TOKEN_CHALLENGE].token, 8); + + md5(sum, 16, (unsigned char *)p); + p += 16; + + wsi->response_length = p - wsi->response; + + wsi->state = WSI_STATE_ISSUE_HANDSHAKE; + + if (wsi->websocket_established_callback) + wsi->websocket_established_callback(wsi); + break; + + case WSI_STATE_ESTABLISHED: + break; + default: + break; + } + + return 0; + +bail: + libwebsocket_close(wsi); + return -1; +} + + +void dostuff(struct libwebsocket *wsi, int sock) +{ + int n; + unsigned char buf[256]; + struct pollfd fds; + + wsi->sock = sock; + + fds.fd = sock; + fds.events = POLLIN | POLLOUT; + + while (1) { + + n = poll(&fds, 1, 10); + if (n < 0) { + fprintf(stderr, "Socket dead (poll = %d)\n", n); + return; + } + + if (fds.revents & (POLLERR | POLLHUP)) { + fprintf(stderr, "Socket dead\n"); + return; + } + + if (wsi->state == WSI_STATE_DEAD_SOCKET) + return; + + + if (fds.revents & POLLIN) { + +// fprintf(stderr, "POLLIN\n"); + + n = read(sock, buf, sizeof(buf)); + if (n < 0) { + fprintf(stderr, "Socket read returned %d\n", n); + continue; + } + if (n) + libwebsocket_read(wsi, buf, n); + } + + if (wsi->state == WSI_STATE_ISSUE_HANDSHAKE) { + + fprintf(stderr, "issuing response packet %d len\n", wsi->response_length); + + fwrite(wsi->response, 1, wsi->response_length, stderr); + + n = write(sock, wsi->response, wsi->response_length); + if (n < 0) { + fprintf(stderr, "ERROR writing to socket"); + exit(1); + } + wsi->state = WSI_STATE_ESTABLISHED; + continue; + } + + if (wsi->websocket_send_callback) + wsi->websocket_send_callback(wsi); + } +} + diff --git a/libwebsockets.h b/libwebsockets.h new file mode 100644 index 00000000..5258a178 --- /dev/null +++ b/libwebsockets.h @@ -0,0 +1,68 @@ + +#define LWS_MAX_HEADER_NAME_LENGTH 64 +#define LWS_MAX_HEADER_LEN 4096 +#define LWS_INITIAL_HDR_ALLOC 256 +#define LWS_ADDITIONAL_HDR_ALLOC 64 + + +enum lws_connection_states { + WSI_STATE_CLOSED, + WSI_STATE_HANDSHAKE_RX, + WSI_STATE_ISSUE_HANDSHAKE, + WSI_STATE_DEAD_SOCKET, + WSI_STATE_ESTABLISHED +}; + +enum lws_token_indexes { + WSI_TOKEN_GET_URI, + WSI_TOKEN_HOST, + WSI_TOKEN_CONNECTION, + WSI_TOKEN_KEY1, + WSI_TOKEN_KEY2, + WSI_TOKEN_PROTOCOL, + WSI_TOKEN_UPGRADE, + WSI_TOKEN_ORIGIN, + WSI_TOKEN_CHALLENGE, + + /* always last real token index*/ + WSI_TOKEN_COUNT, + /* parser state additions */ + WSI_TOKEN_NAME_PART, + WSI_TOKEN_SKIPPING, + WSI_TOKEN_SKIPPING_SAW_CR, + WSI_PARSING_COMPLETE +}; + + +struct lws_tokens { + char * token; + int token_len; +}; + +struct libwebsocket { + + /* set these up before calling libwebsocket_init */ + + int (*websocket_established_callback)(struct libwebsocket *); + int (*websocket_closed_callback)(struct libwebsocket *); + int (*websocket_send_callback)(struct libwebsocket *); + int (*websocket_receive_callback)(struct libwebsocket *); + + /* these are all opaque and maintained by the library */ + + enum lws_connection_states state; + + char name_buffer[LWS_MAX_HEADER_NAME_LENGTH]; + int name_buffer_pos; + int current_alloc_len; + enum lws_token_indexes parser_state; + struct lws_tokens utf8_token[WSI_TOKEN_COUNT]; + char * response; + int response_length; + + int sock; +}; + + +extern int libwebsocket_init(struct libwebsocket *wsi, int port); + diff --git a/md5.c b/md5.c new file mode 100644 index 00000000..a91337f2 --- /dev/null +++ b/md5.c @@ -0,0 +1,353 @@ +/* + * Originally from Polarssl here + * http://polarssl.org/show_source?file=md5 + * under GPL2 or later + * truncated to remove hmac support + */ + + +#include +#include + + +typedef struct +{ + unsigned long total[2]; /*!< number of bytes processed */ + unsigned long state[4]; /*!< intermediate digest state */ + unsigned char buffer[64]; /*!< data block being processed */ +} +md5_context; + + +/** + * \brief MD5 context setup + * + * \param ctx context to be initialized + */ +void md5_starts( md5_context *ctx ); + +/** + * \brief MD5 process buffer + * + * \param ctx MD5 context + * \param input buffer holding the data + * \param ilen length of the input data + */ +void md5_update( md5_context *ctx, const unsigned char *input, int ilen ); + +/** + * \brief MD5 final digest + * + * \param ctx MD5 context + * \param output MD5 checksum result + */ +void md5_finish( md5_context *ctx, unsigned char output[16] ); + +/** + * \brief Output = MD5( input buffer ) + * + * \param input buffer holding the data + * \param ilen length of the input data + * \param output MD5 checksum result + */ +void md5( const unsigned char *input, int ilen, unsigned char output[16] ); + +/** + * \brief Output = MD5( file contents ) + * + * \param path input file name + * \param output MD5 checksum result + * + * \return 0 if successful, 1 if fopen failed, + * or 2 if fread failed + */ +int md5_file( const char *path, unsigned char output[16] ); + + + +/* + * 32-bit integer manipulation macros (little endian) + */ +#ifndef GET_ULONG_LE +#define GET_ULONG_LE(n,b,i) \ +{ \ + (n) = ( (unsigned long) (b)[(i) ] ) \ + | ( (unsigned long) (b)[(i) + 1] << 8 ) \ + | ( (unsigned long) (b)[(i) + 2] << 16 ) \ + | ( (unsigned long) (b)[(i) + 3] << 24 ); \ +} +#endif + +#ifndef PUT_ULONG_LE +#define PUT_ULONG_LE(n,b,i) \ +{ \ + (b)[(i) ] = (unsigned char) ( (n) ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) >> 24 ); \ +} +#endif + +/* + * MD5 context setup + */ +void md5_starts( md5_context *ctx ) +{ + ctx->total[0] = 0; + ctx->total[1] = 0; + + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; +} + +static void md5_process( md5_context *ctx, const unsigned char data[64] ) +{ + unsigned long X[16], A, B, C, D; + + GET_ULONG_LE( X[ 0], data, 0 ); + GET_ULONG_LE( X[ 1], data, 4 ); + GET_ULONG_LE( X[ 2], data, 8 ); + GET_ULONG_LE( X[ 3], data, 12 ); + GET_ULONG_LE( X[ 4], data, 16 ); + GET_ULONG_LE( X[ 5], data, 20 ); + GET_ULONG_LE( X[ 6], data, 24 ); + GET_ULONG_LE( X[ 7], data, 28 ); + GET_ULONG_LE( X[ 8], data, 32 ); + GET_ULONG_LE( X[ 9], data, 36 ); + GET_ULONG_LE( X[10], data, 40 ); + GET_ULONG_LE( X[11], data, 44 ); + GET_ULONG_LE( X[12], data, 48 ); + GET_ULONG_LE( X[13], data, 52 ); + GET_ULONG_LE( X[14], data, 56 ); + GET_ULONG_LE( X[15], data, 60 ); + +#define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) + +#define P(a,b,c,d,k,s,t) \ +{ \ + a += F(b,c,d) + X[k] + t; a = S(a,s) + b; \ +} + + A = ctx->state[0]; + B = ctx->state[1]; + C = ctx->state[2]; + D = ctx->state[3]; + +#define F(x,y,z) (z ^ (x & (y ^ z))) + + P( A, B, C, D, 0, 7, 0xD76AA478 ); + P( D, A, B, C, 1, 12, 0xE8C7B756 ); + P( C, D, A, B, 2, 17, 0x242070DB ); + P( B, C, D, A, 3, 22, 0xC1BDCEEE ); + P( A, B, C, D, 4, 7, 0xF57C0FAF ); + P( D, A, B, C, 5, 12, 0x4787C62A ); + P( C, D, A, B, 6, 17, 0xA8304613 ); + P( B, C, D, A, 7, 22, 0xFD469501 ); + P( A, B, C, D, 8, 7, 0x698098D8 ); + P( D, A, B, C, 9, 12, 0x8B44F7AF ); + P( C, D, A, B, 10, 17, 0xFFFF5BB1 ); + P( B, C, D, A, 11, 22, 0x895CD7BE ); + P( A, B, C, D, 12, 7, 0x6B901122 ); + P( D, A, B, C, 13, 12, 0xFD987193 ); + P( C, D, A, B, 14, 17, 0xA679438E ); + P( B, C, D, A, 15, 22, 0x49B40821 ); + +#undef F + +#define F(x,y,z) (y ^ (z & (x ^ y))) + + P( A, B, C, D, 1, 5, 0xF61E2562 ); + P( D, A, B, C, 6, 9, 0xC040B340 ); + P( C, D, A, B, 11, 14, 0x265E5A51 ); + P( B, C, D, A, 0, 20, 0xE9B6C7AA ); + P( A, B, C, D, 5, 5, 0xD62F105D ); + P( D, A, B, C, 10, 9, 0x02441453 ); + P( C, D, A, B, 15, 14, 0xD8A1E681 ); + P( B, C, D, A, 4, 20, 0xE7D3FBC8 ); + P( A, B, C, D, 9, 5, 0x21E1CDE6 ); + P( D, A, B, C, 14, 9, 0xC33707D6 ); + P( C, D, A, B, 3, 14, 0xF4D50D87 ); + P( B, C, D, A, 8, 20, 0x455A14ED ); + P( A, B, C, D, 13, 5, 0xA9E3E905 ); + P( D, A, B, C, 2, 9, 0xFCEFA3F8 ); + P( C, D, A, B, 7, 14, 0x676F02D9 ); + P( B, C, D, A, 12, 20, 0x8D2A4C8A ); + +#undef F + +#define F(x,y,z) (x ^ y ^ z) + + P( A, B, C, D, 5, 4, 0xFFFA3942 ); + P( D, A, B, C, 8, 11, 0x8771F681 ); + P( C, D, A, B, 11, 16, 0x6D9D6122 ); + P( B, C, D, A, 14, 23, 0xFDE5380C ); + P( A, B, C, D, 1, 4, 0xA4BEEA44 ); + P( D, A, B, C, 4, 11, 0x4BDECFA9 ); + P( C, D, A, B, 7, 16, 0xF6BB4B60 ); + P( B, C, D, A, 10, 23, 0xBEBFBC70 ); + P( A, B, C, D, 13, 4, 0x289B7EC6 ); + P( D, A, B, C, 0, 11, 0xEAA127FA ); + P( C, D, A, B, 3, 16, 0xD4EF3085 ); + P( B, C, D, A, 6, 23, 0x04881D05 ); + P( A, B, C, D, 9, 4, 0xD9D4D039 ); + P( D, A, B, C, 12, 11, 0xE6DB99E5 ); + P( C, D, A, B, 15, 16, 0x1FA27CF8 ); + P( B, C, D, A, 2, 23, 0xC4AC5665 ); + +#undef F + +#define F(x,y,z) (y ^ (x | ~z)) + + P( A, B, C, D, 0, 6, 0xF4292244 ); + P( D, A, B, C, 7, 10, 0x432AFF97 ); + P( C, D, A, B, 14, 15, 0xAB9423A7 ); + P( B, C, D, A, 5, 21, 0xFC93A039 ); + P( A, B, C, D, 12, 6, 0x655B59C3 ); + P( D, A, B, C, 3, 10, 0x8F0CCC92 ); + P( C, D, A, B, 10, 15, 0xFFEFF47D ); + P( B, C, D, A, 1, 21, 0x85845DD1 ); + P( A, B, C, D, 8, 6, 0x6FA87E4F ); + P( D, A, B, C, 15, 10, 0xFE2CE6E0 ); + P( C, D, A, B, 6, 15, 0xA3014314 ); + P( B, C, D, A, 13, 21, 0x4E0811A1 ); + P( A, B, C, D, 4, 6, 0xF7537E82 ); + P( D, A, B, C, 11, 10, 0xBD3AF235 ); + P( C, D, A, B, 2, 15, 0x2AD7D2BB ); + P( B, C, D, A, 9, 21, 0xEB86D391 ); + +#undef F + + ctx->state[0] += A; + ctx->state[1] += B; + ctx->state[2] += C; + ctx->state[3] += D; +} + +/* + * MD5 process buffer + */ +void md5_update( md5_context *ctx, const unsigned char *input, int ilen ) +{ + int fill; + unsigned long left; + + if( ilen <= 0 ) + return; + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += ilen; + ctx->total[0] &= 0xFFFFFFFF; + + if( ctx->total[0] < (unsigned long) ilen ) + ctx->total[1]++; + + if( left && ilen >= fill ) + { + memcpy( (void *) (ctx->buffer + left), + (void *) input, fill ); + md5_process( ctx, ctx->buffer ); + input += fill; + ilen -= fill; + left = 0; + } + + while( ilen >= 64 ) + { + md5_process( ctx, input ); + input += 64; + ilen -= 64; + } + + if( ilen > 0 ) + { + memcpy( (void *) (ctx->buffer + left), + (void *) input, ilen ); + } +} + +static const unsigned char md5_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * MD5 final digest + */ +void md5_finish( md5_context *ctx, unsigned char output[16] ) +{ + unsigned long last, padn; + unsigned long high, low; + unsigned char msglen[8]; + + high = ( ctx->total[0] >> 29 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_ULONG_LE( low, msglen, 0 ); + PUT_ULONG_LE( high, msglen, 4 ); + + last = ctx->total[0] & 0x3F; + padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); + + md5_update( ctx, (unsigned char *) md5_padding, padn ); + md5_update( ctx, msglen, 8 ); + + PUT_ULONG_LE( ctx->state[0], output, 0 ); + PUT_ULONG_LE( ctx->state[1], output, 4 ); + PUT_ULONG_LE( ctx->state[2], output, 8 ); + PUT_ULONG_LE( ctx->state[3], output, 12 ); +} + +/* + * output = MD5( input buffer ) + */ +void md5( const unsigned char *input, int ilen, unsigned char output[16] ) +{ + md5_context ctx; + + md5_starts( &ctx ); + md5_update( &ctx, input, ilen ); + md5_finish( &ctx, output ); + + memset( &ctx, 0, sizeof( md5_context ) ); +} + +/* + * output = MD5( file contents ) + */ +int md5_file( const char *path, unsigned char output[16] ) +{ + FILE *f; + size_t n; + md5_context ctx; + unsigned char buf[1024]; + + if( ( f = fopen( path, "rb" ) ) == NULL ) + return( 1 ); + + md5_starts( &ctx ); + + while( ( n = fread( buf, 1, sizeof( buf ), f ) ) > 0 ) + md5_update( &ctx, buf, (int) n ); + + md5_finish( &ctx, output ); + + memset( &ctx, 0, sizeof( md5_context ) ); + + if( ferror( f ) != 0 ) + { + fclose( f ); + return( 2 ); + } + + fclose( f ); + return( 0 ); +} +