From c6efa45b47a0353266e330ad6ec81cc5479196c3 Mon Sep 17 00:00:00 2001 From: bacara Date: Thu, 18 Jul 2013 16:15:20 +0200 Subject: [PATCH 1/4] Sauvegarde de mon avancement --- SConscript | 22 +- SConstruct | 31 +- core/auth.c | 65 + core/auth.h | 55 + core/connections.h | 256 ++-- core/handlers.h | 34 +- core/input.c | 2294 ++++++++++++++++--------------- core/output.c | 103 +- doc/userguide.pdf | Bin 391577 -> 482984 bytes tools/GenApps.py | 87 +- tools/GenAuthData.py | 272 ++++ tools/access.xml | 11 + tools/supportedHttpAuthTypeList | 1 + 13 files changed, 1957 insertions(+), 1274 deletions(-) create mode 100644 core/auth.c create mode 100644 core/auth.h create mode 100644 tools/GenAuthData.py create mode 100644 tools/access.xml create mode 100644 tools/supportedHttpAuthTypeList diff --git a/SConscript b/SConscript index 9444732..6d601c2 100644 --- a/SConscript +++ b/SConscript @@ -35,7 +35,7 @@ import os import GenApps # imports from SConstruct -Import('env libFileName elfFileName binDir coreDir driversDir genDir appBase toolsList chuncksNbits sourcesMap gzipped test') +Import('env libFileName elfFileName binDir coreDir driversDir genDir appBase toolsList chuncksNbits dirsMap sourcesMap restrictMap gzipped test') # returns the list of .c and .s files in dir, prefixed by dstDir def getAllSourceFiles(dir, dstDir): @@ -50,12 +50,26 @@ def getAllSourceFiles(dir, dstDir): return sourceFiles # builders for web applicative resources creation -# used to generate both static and dynamic resources +# used to geerate both static and dynamic resources def generateResource(target, source, env): + realm = '' + offsetCount = (-1, -1) + + # Checking for HTTP Authentication data + finalUri = sourcesMap[str(source[0])] + maxlen = 0 + for r in restrictMap: + for uri in restrictMap[r]: + if finalUri.startswith(uri) and len(uri) > maxlen: + offsetCount = (restrictMap[r][uri][0], + restrictMap[r][uri][1]) + realm = r + maxlen = len(uri) + if propsFilesMap.has_key(str(source[0])): - GenApps.generateResource(str(source[0]),str(target[0]),chuncksNbits,gzipped,propsFilesMap[str(source[0])]) + GenApps.generateResource(str(source[0]),str(target[0]),chuncksNbits,gzipped,propsFilesMap[str(source[0])], realm, offsetCount) else: - GenApps.generateResource(str(source[0]),str(target[0]),chuncksNbits,gzipped,None) + GenApps.generateResource(str(source[0]),str(target[0]),chuncksNbits,gzipped,None,realm, offsetCount) return None # builder used to generate the file index, with the URLs tree diff --git a/SConstruct b/SConstruct index d2af229..a848bfc 100644 --- a/SConstruct +++ b/SConstruct @@ -35,6 +35,7 @@ import os import sys sys.path.append('tools') import GenApps +import GenAuthData from SCons.Errors import BuildError import sys, string @@ -81,6 +82,7 @@ opts.Add(BoolVariable('debug', 'Set to 1 to build for debug', False)) opts.Add(BoolVariable('sdump', 'Set to 1 to include stack dump', False)) opts.Add(BoolVariable('test', 'Set to 1 to test the test apps', False)) opts.Add('ipaddr', 'Set the IP address of Smews', None) +opts.Add('accessfile', 'Set the path to XML-defined restrictions file', None) # the list of disableable options disabledHash = {} disabledHash['timers'] = 'DISABLE_TIMERS' @@ -190,6 +192,31 @@ for appDir in dirsMap.keys(): appSource = os.path.join(appBase,appDir,file) sourcesMap[appSource] = os.path.join(dirsMap[appDir],file) +# Contains a data structure for HTTP authentication restrictions. +# Refer to GenAuthData.py for more details. +restrictMap = { } +if globalEnv.has_key('accessfile'): + http_auth = GenAuthData.parseAuthData(globalEnv['accessfile']) + #restrictMap = GenAuthData.genHTTPAuthDataFile(genBase + '/' + str(globalEnv['target']) + '/http_auth_data') + restrictMap = GenAuthData.genHTTPAuthDataFile('gen/linux/http_auth_data') + +# DEBUG PURPOSE +# restrictMap = GenAuthData.parseAuthData(globalEnv['accessfile']) +# for realm in restrictMap: +# print '%s' % (realm) +# for uri in restrictMap[realm]: +# print '\t%s' % (uri) +# for credential in restrictMap[realm][uri]: +# print '\t\t%s' % (credential) +# uriDatasMap = GenAuthData.genHTTPAuthDataFile('http-auth-data.c') +# for uri in uriDatasMap: +# offset = uriDatasMap[uri][0] +# count = uriDatasMap[uri][1] +# print '%s credentials: from %s to %s' % (uri, +# str(offset), +# str(offset + count - 1)) +# exit() + # compilation options globalEnv.Replace(CC = 'gcc') globalEnv.Replace(AS = 'as') @@ -199,6 +226,8 @@ if sdump: globalEnv.Append(CCFLAGS = '-DSTACK_DUMP') if debug: globalEnv.Append(CCFLAGS = '-O0 -g') +if 'http_auth' in locals(): + globalEnv.Append(CCFLAGS = '-DHTTP_AUTH=HTTP_AUTH_' + http_auth.upper()) else: globalEnv.Append(CCFLAGS = '-Os -ffunction-sections -fdata-sections -fno-strict-aliasing') globalEnv.Append(LINKFLAGS = '-Wl,--gc-sections -Wl,--print-gc-sections') @@ -235,7 +264,7 @@ for target in targets: # export variables for external SConscript files Export('env libFileName elfFileName binDir coreDir driversDir genDir appBase toolsList chuncksNbits sourcesMap gzipped test') Export('env targetDir binDir projectName elfName') - Export('dirsMap sourcesMap target sconsBasePath httpCodesDir tmpBase') + Export('dirsMap sourcesMap restrictMap target sconsBasePath httpCodesDir tmpBase') # target dependent SConscript call SConscript(os.path.join(targetDir,'SConscript'),variant_dir = binDir,duplicate = 0) # possible web applications SConscript calls diff --git a/core/auth.c b/core/auth.c new file mode 100644 index 0000000..29cc918 --- /dev/null +++ b/core/auth.c @@ -0,0 +1,65 @@ +#include "auth.h" + +#ifdef HTTP_AUTH + +#if HTTP_AUTH == HTTP_AUTH_BASIC + +CONST_VAR(char, HttpAuthenticateHeader[]) = "\ +HTTP/1.1 401 Authorization required\r\nWWW-Authenticate: Basic realm=\ +"; + +#elif HTTP_AUTH == HTTP_AUTH_DIGEST + +CONST_VAR(char, HttpAuthenticateHeader[]) = "\ +HTTP/1.1 401 Authorization required\r\nWWW-Authenticate: Digest realm=\ +"; +CONST_VAR(char, HttpAuthenticateHeaderNonce[]) = "\ +, nonce=\ +"; + +#endif + +uint32_t http_401_handler_dynamic_header_length(struct output_handler_t *restricted_handler) +{ + unsigned const char *char_p = restricted_handler->handler_restriction.realm; + uint32_t length = 0; + + /* Error checking */ + if (char_p == NULL) + return -1; + + /* Realm length computation */ + while (*char_p++) + ++length; + +#if HTTP_AUTH == HTTP_AUTH_BASIC + + /* Remaining size computation */ + length += sizeof(HttpAuthenticateHeader) - 1; + length += 2; /* Realm must be double quoted */ + +#elif HTTP_AUTH == HTTP_AUTH_DIGEST + + /* Nonce length computation */ + char_p = http_auth_nonce; + while (*char_p++) + ++length; + + /* Remaining size computation */ + length += sizeof(HttpAuthenticateHeader) - 1; + length += sizeof(HttpAuthenticateHeaderNonce) - 1; + length += 4; /* Realm and nonce must be double quoted. */ + +#endif + + return length; +} + +uint32_t http_401_handler_length(struct output_handler_t *restricted_handler) +{ + /* Add the size of remaining bytes */ + return http_401_handler_dynamic_header_length (restricted_handler) + + CONST_UI32(GET_FILE(&http_401_handler).length); +} + +#endif diff --git a/core/auth.h b/core/auth.h new file mode 100644 index 0000000..d2542bc --- /dev/null +++ b/core/auth.h @@ -0,0 +1,55 @@ +#ifdef HTTP_AUTH + +#ifndef __AUTH_H__ +#define __AUTH_H__ + +#include "handlers.h" +#include "types.h" + +/* Shortcut to the http 401 handler */ +#define http_401_handler apps_httpCodes_401_html_handler + +/* Restriction/authentication macro */ +#define IS_RESTRICTED(handler) (handler->handler_restriction.realm) +#define IS_AUTHENTICATED(handler) (handler) + +/* Http 401 handler */ +extern CONST_VAR(struct output_handler_t, apps_httpCodes_401_html_handler); + +/* http_401_handler_length: + * + * Returns the length of the 401 handler that matches the specified + * restricted handler, including the dynamically generated http header with + * correct realm and nonce (if digest auth). + */ +extern uint32_t http_401_handler_length(struct output_handler_t *restricted_handler); + +/* http_401_handler_length: + * + * Returns the length of the dynamically generated http header for a 401 handler. + */ +extern uint32_t http_401_handler_dynamic_header_length(struct output_handler_t *restricted_handler); + +#if HTTP_AUTH == HTTP_AUTH_BASIC + +extern CONST_VAR(char, HttpAuthenticateHeader[]); + +#define HTTP_AUTHENTICATE_HEADER_LEN 69 + +#define HTTP_AUTHENTICATE_HEADER_CHK 0x109Eu + +#elif HTTP_AUTH == HTTP_AUTH_DIGEST + +extern CONST_VAR(char, HttpAuthenticateHeader[]); +extern CONST_VAR(char, HttpAuthenticateHeaderNonce[]); + +#define HTTP_AUTHENTICATE_HEADER_LEN 70 +#define HTTP_AUTHENTICATE_HEADER_NONCE_LEN 8 + +#define HTTP_AUTHENTICATE_HEADER_CHK 0x9A92u +#define HTTP_AUTHENTICATE_HEADER_NONCE_CHK 0x6E30u + +#endif + +#endif +#endif diff --git a/core/connections.h b/core/connections.h index ef09b51..596f706 100644 --- a/core/connections.h +++ b/core/connections.h @@ -1,37 +1,37 @@ /* -* Copyright or © or Copr. 2008, Simon Duquennoy -* -* Author e-mail: simon.duquennoy@lifl.fr -* -* This software is a computer program whose purpose is to design an -* efficient Web server for very-constrained embedded system. -* -* This software is governed by the CeCILL license under French law and -* abiding by the rules of distribution of free software. You can use, -* modify and/ or redistribute the software under the terms of the CeCILL -* license as circulated by CEA, CNRS and INRIA at the following URL -* "http://www.cecill.info". -* -* As a counterpart to the access to the source code and rights to copy, -* modify and redistribute granted by the license, users are provided only -* with a limited warranty and the software's author, the holder of the -* economic rights, and the successive licensors have only limited -* liability. -* -* In this respect, the user's attention is drawn to the risks associated -* with loading, using, modifying and/or developing or reproducing the -* software by the user in light of its specific status of free software, -* that may mean that it is complicated to manipulate, and that also -* therefore means that it is reserved for developers and experienced -* professionals having in-depth computer knowledge. Users are therefore -* encouraged to load and test the software's suitability as regards their -* requirements in conditions enabling the security of their systems and/or -* data to be ensured and, more generally, to use and operate it in the -* same conditions as regards security. -* -* The fact that you are presently reading this means that you have had -* knowledge of the CeCILL license and that you accept its terms. -*/ + * Copyright or © or Copr. 2008, Simon Duquennoy + * + * Author e-mail: simon.duquennoy@lifl.fr + * + * This software is a computer program whose purpose is to design an + * efficient Web server for very-constrained embedded system. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ #ifndef __CONNECTIONS_H__ #define __CONNECTIONS_H__ @@ -54,150 +54,154 @@ #ifndef DISABLE_POST /* Boundary structure (for multipart) */ struct boundary_t { - char *boundary_ref; /* boundary which separates parts in multipart post data */ - char *boundary_buffer; /* buffer used to detect boundary */ - uint8_t boundary_size; /* size of boundary */ - uint8_t index; - uint8_t ready_to_count; /* true when post header is parsed */ - uint8_t multi_part_counter; /* to know index of part parsed */ + char *boundary_ref; /* boundary which separates parts in multipart post data */ + char *boundary_buffer; /* buffer used to detect boundary */ + uint8_t boundary_size; /* size of boundary */ + uint8_t index; + uint8_t ready_to_count; /* true when post header is parsed */ + uint8_t multi_part_counter; /* to know index of part parsed */ }; /* Post data structure */ struct post_data_t { - uint16_t content_length; - struct boundary_t *boundary; - struct coroutine_t coroutine; - char *filename; /* filename of current part */ - void *post_data; /* data flowing between dopostin and dopostout functions */ - uint8_t content_type; + uint16_t content_length; + struct boundary_t *boundary; + struct coroutine_t coroutine; + char *filename; /* filename of current part */ + void *post_data; /* data flowing between dopostin and dopostout functions */ + uint8_t content_type; }; #endif /* Possible http headers being used */ -enum service_header_e { header_none, header_standard, header_chunks }; +enum service_header_e { header_none, header_standard, header_chunks, header_authenticate }; /* Information about one in-flight segment (several per service) */ struct in_flight_infos_t { - unsigned char next_outseqno[4]; /* associated sequence number */ - unsigned char checksum[2]; /* segment checksum */ - union if_infos_e { /* either a coroutine context or a data buffer */ + unsigned char next_outseqno[4]; /* associated sequence number */ + unsigned char checksum[2]; /* segment checksum */ + union if_infos_e { /* either a coroutine context or a data buffer */ #ifndef DISABLE_COROUTINES - struct cr_context_t *context; + struct cr_context_t *context; #endif - char *buffer; - } infos; - struct in_flight_infos_t *next; /* the next in-flight segment */ - enum service_header_e service_header: 2; /* http header infos */ + char *buffer; + } infos; + struct in_flight_infos_t *next; /* the next in-flight segment */ + enum service_header_e service_header: 2; /* http header infos */ }; struct generator_service_t { - unsigned char curr_outseqno[4]; + unsigned char curr_outseqno[4]; #ifndef DISABLE_COROUTINES - struct coroutine_t coroutine; + struct coroutine_t coroutine; #endif - struct in_flight_infos_t *in_flight_infos; - enum service_header_e service_header: 2; - unsigned is_persistent: 1; + struct in_flight_infos_t *in_flight_infos; + enum service_header_e service_header: 2; + unsigned is_persistent: 1; }; /* Connection structure */ struct http_connection { - unsigned tcp_mss: 12; - unsigned ready_to_send:1; - enum tcp_state_e {tcp_listen, tcp_syn_rcvd, tcp_established, tcp_closing, tcp_last_ack} tcp_state: 3; - unsigned char next_outseqno[4]; - unsigned char final_outseqno[4]; - unsigned char current_inseqno[4]; - - unsigned char port[2]; - unsigned char cwnd[2]; - unsigned char inflight[2]; - - unsigned const char * /*CONST_VAR*/ blob; - struct generator_service_t *generator_service; + unsigned tcp_mss: 12; + unsigned ready_to_send:1; + enum tcp_state_e {tcp_listen, tcp_syn_rcvd, tcp_established, tcp_closing, tcp_last_ack} tcp_state: 3; + unsigned char next_outseqno[4]; + unsigned char final_outseqno[4]; + unsigned char current_inseqno[4]; + + unsigned char port[2]; + unsigned char cwnd[2]; + unsigned char inflight[2]; + + unsigned const char * /*CONST_VAR*/ blob; + struct generator_service_t *generator_service; + +#ifdef HTTP_AUTH + uint8_t authenticated: 1; +#endif #ifndef DISABLE_ARGS - struct args_t *args; - unsigned char *curr_arg; - unsigned char arg_ref_index; + struct args_t *args; + unsigned char *curr_arg; + unsigned char arg_ref_index; #endif #ifndef DISABLE_TIMERS - unsigned char transmission_time; + unsigned char transmission_time; #endif - enum parsing_state_e {parsing_out, parsing_cmd, parsing_url, parsing_end + enum parsing_state_e {parsing_out, parsing_cmd, parsing_url, parsing_end #ifndef DISABLE_POST - , parsing_post_attributes, parsing_post_end, parsing_post_content_type, parsing_post_args, parsing_post_data, parsing_boundary, parsing_init_buffer } parsing_state: 4; - uint8_t post_url_detected:1; /* bit used to know if url is post or get request */ - struct post_data_t *post_data; + , parsing_post_attributes, parsing_post_end, parsing_post_content_type, parsing_post_args, parsing_post_data, parsing_boundary, parsing_init_buffer } parsing_state: 4; + uint8_t post_url_detected:1; /* bit used to know if url is post or get request */ + struct post_data_t *post_data; #else } parsing_state/*: 2*/; /* this field will be on one byte anyway as previous fields are exactly 16 bits. Using a full char does not change the struct size, but lower the code size needed to access it */ #endif #ifndef DISABLE_COMET - unsigned char comet_passive: 1; - unsigned char comet_send_ack: 1; - unsigned char comet_streaming: 1; +unsigned char comet_passive: 1; +unsigned char comet_send_ack: 1; +unsigned char comet_streaming: 1; #endif }; #ifndef DISABLE_GP_IP_HANDLER struct gp_ip_connection { - uint16_t payload_size; - uint8_t protocol; - uint8_t want_to_send; + uint16_t payload_size; + uint8_t protocol; + uint8_t want_to_send; }; #endif /* Generic connection structure */ struct connection { #ifndef IPV6 - unsigned char ip_addr[4]; + unsigned char ip_addr[4]; #endif - const struct output_handler_t * /*CONST_VAR*/ output_handler; - union { + const struct output_handler_t * /*CONST_VAR*/ output_handler; + union { #ifndef DISABLE_GP_IP_HANDLER - struct gp_ip_connection gpip; + struct gp_ip_connection gpip; #endif - struct http_connection http; - } protocol; + struct http_connection http; + } protocol; - struct connection *next; - struct connection *prev; + struct connection *next; + struct connection *prev; #ifdef IPV6 - unsigned char ip_addr[0]; + unsigned char ip_addr[0]; #endif }; #ifndef DISABLE_GP_IP_HANDLER - #define IS_GPIP(connection) (connection && connection->output_handler && IS_GPIP_HANDLER(connection->output_handler)) - #define IS_HTTP(connection) (!IS_GPIP(connection)) +#define IS_GPIP(connection) (connection && connection->output_handler && IS_GPIP_HANDLER(connection->output_handler)) +#define IS_HTTP(connection) (!IS_GPIP(connection)) #else - #define IS_HTTP(connection) (connection) +#define IS_HTTP(connection) (connection) #endif /* Loop on each connection */ -#define FOR_EACH_CONN(item, code) \ - if(all_connections) { \ - struct connection *(item) = all_connections; \ - do { \ - {code} \ - (item) = (item)->next; \ - } while((item) != all_connections); \ - } \ +#define FOR_EACH_CONN(item, code) \ + if(all_connections) { \ + struct connection *(item) = all_connections; \ + do { \ + {code} \ + (item) = (item)->next; \ + } while((item) != all_connections); \ + } \ #define NEXT_CONN(item) (item) = (item)->next /* Pseudo connection for RST */ struct http_rst_connection { #ifdef IPV6 - unsigned char ip_addr[16]; + unsigned char ip_addr[16]; #else - unsigned char ip_addr[4]; + unsigned char ip_addr[4]; #endif - unsigned char current_inseqno[4]; - unsigned char next_outseqno[4]; - unsigned char port[2]; + unsigned char current_inseqno[4]; + unsigned char next_outseqno[4]; + unsigned char port[2]; }; /* Shared global connections structures */ @@ -208,16 +212,16 @@ extern struct http_rst_connection rst_connection; struct curr_output_t { - struct generator_service_t *service; - char *buffer; - unsigned char checksum[2]; - uint16_t content_length; - uint16_t max_bytes; - unsigned char next_outseqno[4]; - enum service_header_e service_header: 2; + struct generator_service_t *service; + char *buffer; + unsigned char checksum[2]; + uint16_t content_length; + uint16_t max_bytes; + unsigned char next_outseqno[4]; + enum service_header_e service_header: 2; #ifdef DISABLE_COROUTINES - enum dynamic_state {none, in_dynamic, sending_segment,waiting_ack, ack_received, connection_terminated} dynamic_service_state:3; - unsigned char in_handler:1; + enum dynamic_state {none, in_dynamic, sending_segment,waiting_ack, ack_received, connection_terminated} dynamic_service_state:3; + unsigned char in_handler:1; #endif }; @@ -235,11 +239,11 @@ extern unsigned char * decompress_ip(const unsigned char comp_ip_addr[], unsigne extern unsigned char * compress_ip(const unsigned char full_ip_addr[], unsigned char comp_ip_addr[], unsigned char * indexes); static inline uint8_t compressed_ip_size(const unsigned char ip[]) { - return (17-((ip[0])&15)); + return (17-((ip[0])&15)); } static inline unsigned char * copy_compressed_ip(unsigned char dst[], const unsigned char src[]) { - return memcpy(dst, src, compressed_ip_size(src)); + return memcpy(dst, src, compressed_ip_size(src)); } @@ -255,10 +259,10 @@ extern void free_connection(const struct connection *connection); /* Allocates and insert a new connection. The inserted connection will be a copy of the from parameter */ extern struct connection *add_connection(const struct connection *from #ifdef IPV6 - , uint8_t compressed_ip_size /* Used in ipv6 to allocation enough bytes to - store the compressed ip */ + , uint8_t compressed_ip_size /* Used in ipv6 to allocation enough bytes to + store the compressed ip */ #endif - ); + ); /* deallocate all the memory used by in-flight segments that are now acknowledged (with out_seqno < inack) */ extern void clean_service(struct generator_service_t *service, unsigned char inack[]); @@ -266,7 +270,7 @@ extern void clean_service(struct generator_service_t *service, unsigned char ina #ifndef DISABLE_POST /* Shared coroutine state (in = 0 / out = 1)*/ struct coroutine_state_t { - enum coroutine_state_e {cor_in, cor_out} state:1; + enum coroutine_state_e {cor_in, cor_out} state:1; }; extern struct coroutine_state_t coroutine_state; #endif diff --git a/core/handlers.h b/core/handlers.h index 91140c4..11304c1 100644 --- a/core/handlers.h +++ b/core/handlers.h @@ -59,17 +59,17 @@ typedef char (generator_dopost_out_func_t)(uint8_t,void *); /* Generator: init and run functions */ struct dynamic_resource { - generator_init_func_t *init; - union handlers_u { - struct get_handlers { - generator_initget_func_t *initget; - generator_doget_func_t *doget; - } get; + generator_init_func_t *init; + union handlers_u { + struct get_handlers { + generator_initget_func_t *initget; + generator_doget_func_t *doget; + } get; #ifndef DISABLE_POST - struct post_handlers{ - generator_dopost_in_func_t *dopostin; - generator_dopost_out_func_t *dopostout; - } post; + struct post_handlers{ + generator_dopost_in_func_t *dopostin; + generator_dopost_out_func_t *dopostout; + } post; #endif #ifndef DISABLE_GP_IP_HANDLER struct gp_ip_handlers { @@ -136,7 +136,7 @@ struct output_handler_t { const struct dynamic_resource generator; } handler_data; #ifndef DISABLE_ARGS - struct handler_args_t { + struct handler_args_t { unsigned const char * /*CONST_VAR*/ args_tree; const struct arg_ref_t * /*CONST_VAR*/ args_index; uint16_t args_size; @@ -148,6 +148,18 @@ struct output_handler_t { uint8_t mimes_size; }handler_mimes; #endif +#ifdef HTTP_AUTH + #if HTTP_AUTH == HTTP_AUTH_BASIC + struct handler_restrictions_t { + unsigned const char *realm; /* Realm of restriction : + - no restriction if NULL */ + uint8_t credentials_offset; /* Offset to the first credential */ + uint8_t credentials_count; /* Credentials count from offset */ + } handler_restriction; + #elif HTTP_AUTH == HTTP_AUTH_DIGEST + /* TODO */ + #endif +#endif }; #ifndef DISABLE_GP_IP_HANDLER diff --git a/core/input.c b/core/input.c index b718c54..39329b6 100644 --- a/core/input.c +++ b/core/input.c @@ -42,6 +42,7 @@ #include "coroutines.h" #include "blobs.h" #include "defines.h" +#include "auth.h" /* Used to dump the runtime stack */ #ifdef STACK_DUMP @@ -53,6 +54,12 @@ unsigned char *stack_base; #define http_404_handler apps_httpCodes_404_html_handler extern CONST_VAR(struct output_handler_t, apps_httpCodes_404_html_handler); +/* 401 Authorization required handler */ +/* #ifdef HTTP_AUTH */ +/* #define http_401_handler apps_httpCodes_401_html_handler */ +/* extern CONST_VAR(struct output_handler_t, apps_httpCodes_401_html_handler); */ +/* #endif */ + /* Maximal TCP MSS */ #ifndef DEV_MTU #define MAX_MSS 0xffff @@ -84,97 +91,97 @@ extern CONST_VAR(struct output_handler_t, apps_httpCodes_505_html_handler); /* gets 16 bits and checks if nothing wrong appened */ static char dev_get16(unsigned char *word) { - int16_t tmp; - DEV_GET(tmp); - if(tmp == -1) - return -1; - word[1] = tmp; - DEV_GET(tmp); - if(tmp == -1) - return -1; - word[0] = tmp; - return 1; + int16_t tmp; + DEV_GET(tmp); + if(tmp == -1) + return -1; + word[1] = tmp; + DEV_GET(tmp); + if(tmp == -1) + return -1; + word[0] = tmp; + return 1; } /* gets 32 bits and checks if nothing wrong appened */ static char dev_get32(unsigned char *dword) { - if(dev_get16(&dword[2]) == -1) - return -1; - if(dev_get16(&dword[0]) == -1) - return -1; - return 1; + if(dev_get16(&dword[2]) == -1) + return -1; + if(dev_get16(&dword[0]) == -1) + return -1; + return 1; } #ifdef IPV6 /* gets 128 bits and checks if nothing wrong appened */ static char dev_get128(unsigned char *dword) { - if(dev_get32(&dword[12]) == -1) - return -1; - if(dev_get32(&dword[8]) == -1) - return -1; - if(dev_get32(&dword[4]) == -1) - return -1; - if(dev_get32(&dword[0]) == -1) - return -1; - return 1; + if(dev_get32(&dword[12]) == -1) + return -1; + if(dev_get32(&dword[8]) == -1) + return -1; + if(dev_get32(&dword[4]) == -1) + return -1; + if(dev_get32(&dword[0]) == -1) + return -1; + return 1; } /* Get 2 bytes */ -#define DEV_GET16(c) { \ - if(dev_get16((unsigned char*)(c)) == -1) \ - return 1; \ - } +#define DEV_GET16(c) { \ + if(dev_get16((unsigned char*)(c)) == -1) \ + return 1; \ + } /* Get 4 bytes */ -#define DEV_GET32(c) { \ - if(dev_get32((unsigned char*)(c)) == -1) \ - return 1; \ - } +#define DEV_GET32(c) { \ + if(dev_get32((unsigned char*)(c)) == -1) \ + return 1; \ + } /* Get 16 bytes */ -#define DEV_GET128(c) { \ - if(dev_get128((unsigned char*)(c)) == -1) \ - return 1; \ - } +#define DEV_GET128(c) { \ + if(dev_get128((unsigned char*)(c)) == -1) \ + return 1; \ + } #endif /* Get and checksum a byte */ #define DEV_GETC(c) { int16_t getc; \ - DEV_GET(getc); \ - if(getc == -1) return 1; \ - c = getc; \ - checksum_add(c);} \ + DEV_GET(getc); \ + if(getc == -1) return 1; \ + c = getc; \ + checksum_add(c);} \ /* Get and checksum 2 bytes */ -#define DEV_GETC16(c) { \ - if(dev_get16((unsigned char*)(c)) == -1) \ - return 1; \ - checksum_add16(UI16(c)); \ - } +#define DEV_GETC16(c) { \ + if(dev_get16((unsigned char*)(c)) == -1) \ + return 1; \ + checksum_add16(UI16(c)); \ + } /* Get and checksum 4 bytes */ -#define DEV_GETC32(c) { \ - if(dev_get32((unsigned char*)(c)) == -1) \ - return 1; \ - checksum_add32(c); \ - } +#define DEV_GETC32(c) { \ + if(dev_get32((unsigned char*)(c)) == -1) \ + return 1; \ + checksum_add32(c); \ + } #ifdef IPV6 /* Get and checksum 16 bytes */ -#define DEV_GETC128(c) { \ - if(dev_get128((unsigned char*)(c)) == -1) \ - return 1; \ - checksum_add32(&c[12]); \ - checksum_add32(&c[8]); \ - checksum_add32(&c[4]); \ - checksum_add32(&c[0]); \ - } +#define DEV_GETC128(c) { \ + if(dev_get128((unsigned char*)(c)) == -1) \ + return 1; \ + checksum_add32(&c[12]); \ + checksum_add32(&c[8]); \ + checksum_add32(&c[4]); \ + checksum_add32(&c[0]); \ + } #endif #ifndef DISABLE_POST struct curr_input_t { - struct connection *connection; - uint16_t length; + struct connection *connection; + uint16_t length; } curr_input; #endif @@ -183,87 +190,87 @@ struct curr_input_t { * for general purpose ip handler is very simple */ short in() { - unsigned char tmp; - DEV_GET(tmp); - return tmp; + unsigned char tmp; + DEV_GET(tmp); + return tmp; } #endif #ifndef DISABLE_POST /* called from dopostin or dopacketin */ short in(){ - unsigned char tmp; + unsigned char tmp; #ifndef DISABLE_GP_IP_HANDLER - /* If post AND general purpose ip, then check if the connection is null - * if so, it is called from packetin */ - if (curr_input.connection == NULL) + /* If post AND general purpose ip, then check if the connection is null + * if so, it is called from packetin */ + if (curr_input.connection == NULL) { - DEV_GET(tmp); - return tmp; + DEV_GET(tmp); + return tmp; } #endif - if(coroutine_state.state == cor_out) - return -1; - if(!curr_input.connection->protocol.http.post_data->content_length) - return -1; - if(curr_input.connection->protocol.http.post_data->boundary && (curr_input.connection->protocol.http.post_data->boundary->index == (uint8_t)-1)) /* index = -1 => boundary found */ - return -1; - if(!curr_input.length) - cr_run(NULL,cor_type_get); /* input buffer is empty, changing context */ - if(curr_input.connection->protocol.http.post_data->boundary){ - /* getting next value in input buffer */ - tmp = curr_input.connection->protocol.http.post_data->boundary->boundary_buffer[curr_input.connection->protocol.http.post_data->boundary->index]; - DEV_GETC(curr_input.connection->protocol.http.post_data->boundary->boundary_buffer[curr_input.connection->protocol.http.post_data->boundary->index]); - curr_input.connection->protocol.http.post_data->boundary->index++; - /* searching boundary */ - if(curr_input.connection->protocol.http.post_data->boundary->index == curr_input.connection->protocol.http.post_data->boundary->boundary_size) - curr_input.connection->protocol.http.post_data->boundary->index = 0; - /* comparing first and last characters of buffer with boundary, then all characters */ - if(curr_input.connection->protocol.http.post_data->boundary->boundary_buffer[(unsigned char)(curr_input.connection->protocol.http.post_data->boundary->index - 1) % curr_input.connection->protocol.http.post_data->boundary->boundary_size] - == curr_input.connection->protocol.http.post_data->boundary->boundary_ref[curr_input.connection->protocol.http.post_data->boundary->boundary_size-1] - && curr_input.connection->protocol.http.post_data->boundary->boundary_buffer[curr_input.connection->protocol.http.post_data->boundary->index] - == curr_input.connection->protocol.http.post_data->boundary->boundary_ref[0]){ - unsigned char index = curr_input.connection->protocol.http.post_data->boundary->index + 1; - unsigned char i; - for(i = 1 ; i < curr_input.connection->protocol.http.post_data->boundary->boundary_size-1 ; i++){ - if(index == curr_input.connection->protocol.http.post_data->boundary->boundary_size) - index = 0; - if(curr_input.connection->protocol.http.post_data->boundary->boundary_ref[i] != curr_input.connection->protocol.http.post_data->boundary->boundary_buffer[index]) - break; - index++; - } - if(i == curr_input.connection->protocol.http.post_data->boundary->boundary_size-1) - curr_input.connection->protocol.http.post_data->boundary->index = -1; - } + if(coroutine_state.state == cor_out) + return -1; + if(!curr_input.connection->protocol.http.post_data->content_length) + return -1; + if(curr_input.connection->protocol.http.post_data->boundary && (curr_input.connection->protocol.http.post_data->boundary->index == (uint8_t)-1)) /* index = -1 => boundary found */ + return -1; + if(!curr_input.length) + cr_run(NULL,cor_type_get); /* input buffer is empty, changing context */ + if(curr_input.connection->protocol.http.post_data->boundary){ + /* getting next value in input buffer */ + tmp = curr_input.connection->protocol.http.post_data->boundary->boundary_buffer[curr_input.connection->protocol.http.post_data->boundary->index]; + DEV_GETC(curr_input.connection->protocol.http.post_data->boundary->boundary_buffer[curr_input.connection->protocol.http.post_data->boundary->index]); + curr_input.connection->protocol.http.post_data->boundary->index++; + /* searching boundary */ + if(curr_input.connection->protocol.http.post_data->boundary->index == curr_input.connection->protocol.http.post_data->boundary->boundary_size) + curr_input.connection->protocol.http.post_data->boundary->index = 0; + /* comparing first and last characters of buffer with boundary, then all characters */ + if(curr_input.connection->protocol.http.post_data->boundary->boundary_buffer[(unsigned char)(curr_input.connection->protocol.http.post_data->boundary->index - 1) % curr_input.connection->protocol.http.post_data->boundary->boundary_size] + == curr_input.connection->protocol.http.post_data->boundary->boundary_ref[curr_input.connection->protocol.http.post_data->boundary->boundary_size-1] + && curr_input.connection->protocol.http.post_data->boundary->boundary_buffer[curr_input.connection->protocol.http.post_data->boundary->index] + == curr_input.connection->protocol.http.post_data->boundary->boundary_ref[0]){ + unsigned char index = curr_input.connection->protocol.http.post_data->boundary->index + 1; + unsigned char i; + for(i = 1 ; i < curr_input.connection->protocol.http.post_data->boundary->boundary_size-1 ; i++){ + if(index == curr_input.connection->protocol.http.post_data->boundary->boundary_size) + index = 0; + if(curr_input.connection->protocol.http.post_data->boundary->boundary_ref[i] != curr_input.connection->protocol.http.post_data->boundary->boundary_buffer[index]) + break; + index++; + } + if(i == curr_input.connection->protocol.http.post_data->boundary->boundary_size-1) + curr_input.connection->protocol.http.post_data->boundary->index = -1; } - else - DEV_GETC(tmp); - /* updating counters */ - if(curr_input.connection->protocol.http.post_data->content_length != (uint16_t)-1) - curr_input.connection->protocol.http.post_data->content_length--; - curr_input.length--; - return tmp; + } + else + DEV_GETC(tmp); + /* updating counters */ + if(curr_input.connection->protocol.http.post_data->content_length != (uint16_t)-1) + curr_input.connection->protocol.http.post_data->content_length--; + curr_input.length--; + return tmp; } #endif #ifndef DISABLE_GP_IP_HANDLER const struct output_handler_t *smews_gpip_get_output_handler(uint8_t protocol) { - /* Try to find a handler that match the protocol number */ - uint8_t i; - for (i = 0 ; ; ++i) - { - struct output_handler_t *handler = (struct output_handler_t*)CONST_ADDR(resources_index[i]); - if (handler == NULL) - return NULL; - if (!IS_GPIP_HANDLER(handler)) - continue; - if (CONST_UI8(handler->handler_data.generator.handlers.gp_ip.protocol) == protocol) /* found one */ - return handler; - } + /* Try to find a handler that match the protocol number */ + uint8_t i; + for (i = 0 ; ; ++i) + { + struct output_handler_t *handler = (struct output_handler_t*)CONST_ADDR(resources_index[i]); + if (handler == NULL) return NULL; + if (!IS_GPIP_HANDLER(handler)) + continue; + if (CONST_UI8(handler->handler_data.generator.handlers.gp_ip.protocol) == protocol) /* found one */ + return handler; + } + return NULL; } /*static struct connection *smews_gpip_get_connection(uint8_t protocol, unsigned char *source_ip) @@ -284,1170 +291,1223 @@ const struct output_handler_t *smews_gpip_get_output_handler(uint8_t protocol) /*-----------------------------------------------------------------------------------*/ char smews_receive(void) { - /* local variables */ - unsigned char current_inseqno[4]; - unsigned char current_inack[4]; - unsigned char tmp_ui32[4]; - unsigned char tmp_ui16[2]; - uint16_t packet_length; - unsigned char tcp_header_length; - uint16_t segment_length; - struct connection *connection; - unsigned char tmp_char; - uint16_t x; - unsigned char new_tcp_data; + /* local variables */ + unsigned char current_inseqno[4]; + unsigned char current_inack[4]; + unsigned char tmp_ui32[4]; + unsigned char tmp_ui16[2]; + uint16_t packet_length; + unsigned char tcp_header_length; + uint16_t segment_length; + struct connection *connection; + unsigned char tmp_blob; + unsigned char tmp_char; + uint16_t x; + unsigned char new_tcp_data; #ifndef DISABLE_GP_IP_HANDLER - uint8_t protocol; + uint8_t protocol; #endif #ifdef IPV6 - /* Full and compressed IPv6 adress of the received packet */ - unsigned char full_ipv6_addr[16]; - unsigned char comp_ipv6_addr[17]; /* 16 bytes (addr) + 1 byte (compression indexes) = 17 bytes */ + /* Full and compressed IPv6 adress of the received packet */ + unsigned char full_ipv6_addr[16]; + unsigned char comp_ipv6_addr[17]; /* 16 bytes (addr) + 1 byte (compression indexes) = 17 bytes */ #endif - /* variables used to defer processing with side effects until the incomming packet has been checked (with checksum) */ - unsigned char defer_clean_service = 0; - unsigned char defer_free_handler = 0; + /* variables used to defer processing with side effects until the incomming packet has been checked (with checksum) */ + unsigned char defer_clean_service = 0; + unsigned char defer_free_handler = 0; #ifndef DISABLE_ARGS - struct args_t *defer_free_args; - uint16_t defer_free_args_size; + struct args_t *defer_free_args; + uint16_t defer_free_args_size; #endif - /* tmp connection used to store the current state until checksums are checked */ - struct connection tmp_connection; + /* tmp connection used to store the current state until checksums are checked */ + struct connection tmp_connection; - if(!DEV_DATA_TO_READ) - return 0; + if(!DEV_DATA_TO_READ) + return 0; #ifdef IPV6 - /* Get IP Version (and 4 bits of the traffic class) */ - DEV_GET(tmp_char); + /* Get IP Version (and 4 bits of the traffic class) */ + DEV_GET(tmp_char); #else - checksum_init(); + checksum_init(); - /* get IP version & header length */ - DEV_GETC(tmp_char); + /* get IP version & header length */ + DEV_GETC(tmp_char); #endif #ifdef SMEWS_RECEIVING - SMEWS_RECEIVING; + SMEWS_RECEIVING; #endif - /* Starting to decode IP */ + /* Starting to decode IP */ #ifdef IPV6 - /* 0x06 : IPV6, 4 bits right because we don't care (yet?) - about the Traffic Class */ - if(tmp_char>>4 != 0x6) + /* 0x06 : IPV6, 4 bits right because we don't care (yet?) + about the Traffic Class */ + if(tmp_char>>4 != 0x6) #else - /* 0x45 : IPv4, no option in header */ - if(tmp_char != 0x45) + /* 0x45 : IPv4, no option in header */ + if(tmp_char != 0x45) #endif - return 1; + return 1; #ifdef IPV6 - /* Discard the rest of the traffic class and the Flow Label (still unused) */ - DEV_GET(tmp_char); - DEV_GET16(tmp_ui16); + /* Discard the rest of the traffic class and the Flow Label (still unused) */ + DEV_GET(tmp_char); + DEV_GET16(tmp_ui16); - /* Get IP packet payload length in len */ - DEV_GET16(((uint16_t *)&packet_length)); + /* Get IP packet payload length in len */ + DEV_GET16(((uint16_t *)&packet_length)); - DEV_GET(tmp_char); - /* What's the next header? */ + DEV_GET(tmp_char); + /* What's the next header? */ #ifndef DISABLE_GP_IP_HANDLER - protocol = tmp_char; + protocol = tmp_char; #else - if (tmp_char != IP_PROTO_TCP) /* If not TCP and general purpose IP is disabled, drop packet */ - return 1; + if (tmp_char != IP_PROTO_TCP) /* If not TCP and general purpose IP is disabled, drop packet */ + return 1; #endif - /* We don't care about the Hop Limit (TTL) */ - DEV_GET(tmp_char); + /* We don't care about the Hop Limit (TTL) */ + DEV_GET(tmp_char); - /* get & store IP source address */ - DEV_GET128(&full_ipv6_addr[0]); + /* get & store IP source address */ + DEV_GET128(&full_ipv6_addr[0]); - /* Compress the received IPv6 adress - compress_ip(FullIPv6, IP's Offset, Indexe's offset) */ - compress_ip(full_ipv6_addr, comp_ipv6_addr+1, &comp_ipv6_addr[0]); - /* discard the dest IP */ - DEV_GET32(tmp_ui32); - DEV_GET32(tmp_ui32); - DEV_GET32(tmp_ui32); - DEV_GET32(tmp_ui32); + /* Compress the received IPv6 adress + compress_ip(FullIPv6, IP's Offset, Indexe's offset) */ + compress_ip(full_ipv6_addr, comp_ipv6_addr+1, &comp_ipv6_addr[0]); + /* discard the dest IP */ + DEV_GET32(tmp_ui32); + DEV_GET32(tmp_ui32); + DEV_GET32(tmp_ui32); + DEV_GET32(tmp_ui32); #else - /* discard IP type of service */ - DEV_GETC(tmp_char); + /* discard IP type of service */ + DEV_GETC(tmp_char); - /* get IP packet length in len */ - DEV_GETC16(((uint16_t *)&packet_length)); - /* The packet_length is the size of the IP payload, without the IP header size. - * This make the value homegeneous for IPv4 and IPv6 and simplifies code for TCP segment length calculation (no more #ifndef hacks) */ - packet_length = packet_length - IP_HEADER_SIZE; + /* get IP packet length in len */ + DEV_GETC16(((uint16_t *)&packet_length)); + /* The packet_length is the size of the IP payload, without the IP header size. + * This make the value homegeneous for IPv4 and IPv6 and simplifies code for TCP segment length calculation (no more #ifndef hacks) */ + packet_length = packet_length - IP_HEADER_SIZE; - /* discard IP ID */ - DEV_GETC16(tmp_ui16); + /* discard IP ID */ + DEV_GETC16(tmp_ui16); - /* get IP fragmentation flags (fragmentation is not supported) */ - DEV_GETC16(tmp_ui16); - if((tmp_ui16[S0] & 0x20) || (tmp_ui16[S0] & 0x1f) != 0) - return 1; + /* get IP fragmentation flags (fragmentation is not supported) */ + DEV_GETC16(tmp_ui16); + if((tmp_ui16[S0] & 0x20) || (tmp_ui16[S0] & 0x1f) != 0) + return 1; - /* get IP fragmentation offset */ - if(tmp_ui16[S1] != 0) - return 1; + /* get IP fragmentation offset */ + if(tmp_ui16[S1] != 0) + return 1; - /* discard IP TTL */ - DEV_GETC16(tmp_ui16); + /* discard IP TTL */ + DEV_GETC16(tmp_ui16); #ifndef DISABLE_GP_IP_HANDLER - protocol = tmp_ui16[S1]; + protocol = tmp_ui16[S1]; #else - if(tmp_ui16[S1] != IP_PROTO_TCP) /* If not TCP and general purpose IP is disabled, drop packet */ - return 1; + if(tmp_ui16[S1] != IP_PROTO_TCP) /* If not TCP and general purpose IP is disabled, drop packet */ + return 1; #endif - /* discard IP checksum */ - DEV_GETC16(tmp_ui16); + /* discard IP checksum */ + DEV_GETC16(tmp_ui16); - /* get & store IP source address */ - DEV_GETC32(tmp_connection.ip_addr); + /* get & store IP source address */ + DEV_GETC32(tmp_connection.ip_addr); - /* discard the IP destination address 2*16*/ - DEV_GETC32(tmp_ui32); + /* discard the IP destination address 2*16*/ + DEV_GETC32(tmp_ui32); - /* check IP checksum */ - checksum_end(); + /* check IP checksum */ + checksum_end(); - if(UI16(current_checksum) != 0xffff) - return 1; + if(UI16(current_checksum) != 0xffff) + return 1; #endif #ifndef DISABLE_GP_IP_HANDLER - if (protocol != IP_PROTO_TCP) + if (protocol != IP_PROTO_TCP) { - /* Finaly, the decision has been made to create a new connection for each packet and to - * free it after having called the output handler. - * Previous code left in comments if we change our mind later */ - connection = NULL; /* smews_gpip_get_connection(protocol, NULL);*/ + /* Finaly, the decision has been made to create a new connection for each packet and to + * free it after having called the output handler. + * Previous code left in comments if we change our mind later */ + connection = NULL; /* smews_gpip_get_connection(protocol, NULL);*/ - if (connection == NULL) /* no connection found, create one */ + if (connection == NULL) /* no connection found, create one */ { - tmp_connection.output_handler = smews_gpip_get_output_handler(protocol); - if (tmp_connection.output_handler == NULL) - return 1; - tmp_connection.protocol.gpip.protocol = protocol; - /* add the connection */ - connection = add_connection(&tmp_connection + tmp_connection.output_handler = smews_gpip_get_output_handler(protocol); + if (tmp_connection.output_handler == NULL) + return 1; + tmp_connection.protocol.gpip.protocol = protocol; + /* add the connection */ + connection = add_connection(&tmp_connection #ifdef IPV6 - , compressed_ip_size(comp_ipv6_addr) + , compressed_ip_size(comp_ipv6_addr) #endif - ); - if (connection == NULL) - return 1; + ); + if (connection == NULL) + return 1; } - /* update the connection */ + /* update the connection */ #ifdef IPV6 - copy_compressed_ip(connection->ip_addr, comp_ipv6_addr); + copy_compressed_ip(connection->ip_addr, comp_ipv6_addr); #else - memcpy(connection->ip_addr, tmp_connection.ip_addr, sizeof(tmp_connection.ip_addr)); + memcpy(connection->ip_addr, tmp_connection.ip_addr, sizeof(tmp_connection.ip_addr)); #endif #ifndef DISABLE_POST /* if post is available, must set the curr_input connection to null so that the in function knows that we are in dopacketin */ - curr_input.connection = NULL; -#endif - connection->protocol.gpip.payload_size = packet_length; - //connection->protocol.gpip.want_to_send = connection->output_handler->handler_data.generator.handlers.gp_ip.dopacketin(connection); - generator_dopacket_in_func_t *callback = CONST_ADDR(connection->output_handler->handler_data.generator.handlers.gp_ip.dopacketin); - connection->protocol.gpip.want_to_send = callback(connection); - if (!connection->protocol.gpip.want_to_send) - free_connection(connection); - return 1; + curr_input.connection = NULL; +#endif + connection->protocol.gpip.payload_size = packet_length; + //connection->protocol.gpip.want_to_send = connection->output_handler->handler_data.generator.handlers.gp_ip.dopacketin(connection); + generator_dopacket_in_func_t *callback = CONST_ADDR(connection->output_handler->handler_data.generator.handlers.gp_ip.dopacketin); + connection->protocol.gpip.want_to_send = callback(connection); + if (!connection->protocol.gpip.want_to_send) + free_connection(connection); + return 1; } #endif - /* End of IP, starting TCP */ - checksum_init(); + /* End of IP, starting TCP */ + checksum_init(); - /* get TCP source port */ - DEV_GETC16(tmp_connection.protocol.http.port); + /* get TCP source port */ + DEV_GETC16(tmp_connection.protocol.http.port); - /* current connection selection */ - connection = NULL; - /* search an existing TCP connection using the current port */ + /* current connection selection */ + connection = NULL; + /* search an existing TCP connection using the current port */ - FOR_EACH_CONN(conn, { - /* Only search http connection */ - if (!IS_HTTP(conn)) - { - NEXT_CONN(conn); - continue; - } - if(UI16(conn->protocol.http.port) == UI16(tmp_connection.protocol.http.port) && + FOR_EACH_CONN(conn, { + /* Only search http connection */ + if (!IS_HTTP(conn)) + { + NEXT_CONN(conn); + continue; + } + if(UI16(conn->protocol.http.port) == UI16(tmp_connection.protocol.http.port) && #ifdef IPV6 - ipcmp(conn->ip_addr, comp_ipv6_addr)) { + ipcmp(conn->ip_addr, comp_ipv6_addr)) { #else - UI32(conn->ip_addr) == UI32(tmp_connection.ip_addr)) { -#endif - /* connection already existing */ - connection = conn; - break; - } - }) - - if(connection) { - /* a connection has been found */ - tmp_connection = *connection; - } else { - tmp_connection.protocol.http.tcp_state = tcp_listen; - tmp_connection.output_handler = NULL; - UI32(tmp_connection.protocol.http.next_outseqno) = BASIC_SEQNO; - UI32(tmp_connection.protocol.http.current_inseqno) = 0; - tmp_connection.protocol.http.parsing_state = parsing_out; - UI16(tmp_connection.protocol.http.inflight) = 0; - tmp_connection.protocol.http.generator_service = NULL; - tmp_connection.protocol.http.ready_to_send = 1; + UI32(conn->ip_addr) == UI32(tmp_connection.ip_addr)) { +#endif + /* connection already existing */ + connection = conn; + break; + } + }) + + if(connection) { + /* a connection has been found */ + tmp_connection = *connection; + } else { + tmp_connection.protocol.http.tcp_state = tcp_listen; + tmp_connection.output_handler = NULL; + UI32(tmp_connection.protocol.http.next_outseqno) = BASIC_SEQNO; + UI32(tmp_connection.protocol.http.current_inseqno) = 0; + tmp_connection.protocol.http.parsing_state = parsing_out; + UI16(tmp_connection.protocol.http.inflight) = 0; + tmp_connection.protocol.http.generator_service = NULL; + tmp_connection.protocol.http.ready_to_send = 1; #ifndef DISABLE_COMET - tmp_connection.protocol.http.comet_send_ack = 0; - tmp_connection.protocol.http.comet_passive = 0; + tmp_connection.protocol.http.comet_send_ack = 0; + tmp_connection.protocol.http.comet_passive = 0; #endif #ifndef DISABLE_TIMERS - tmp_connection.protocol.http.transmission_time = last_transmission_time; + tmp_connection.protocol.http.transmission_time = last_transmission_time; #endif #ifndef DISABLE_POST - tmp_connection.protocol.http.post_data = NULL; - tmp_connection.protocol.http.post_url_detected = 0; + tmp_connection.protocol.http.post_data = NULL; + tmp_connection.protocol.http.post_url_detected = 0; #endif - } + } - /* get and check the destination port */ + /* get and check the destination port */ - DEV_GETC16(tmp_ui16); - if(tmp_ui16[S1] != HTTP_PORT) { + DEV_GETC16(tmp_ui16); + if(tmp_ui16[S1] != HTTP_PORT) { #ifdef STACK_DUMP - DEV_PREPARE_OUTPUT(STACK_DUMP_SIZE); - for(stack_i = 0; stack_i < STACK_DUMP_SIZE ; stack_i++) { - DEV_PUT(stack_base[-stack_i]); - } - DEV_OUTPUT_DONE; -#endif - return 1; + DEV_PREPARE_OUTPUT(STACK_DUMP_SIZE); + for(stack_i = 0; stack_i < STACK_DUMP_SIZE ; stack_i++) { + DEV_PUT(stack_base[-stack_i]); } + DEV_OUTPUT_DONE; +#endif + return 1; + } - /* get TCP sequence number */ - DEV_GETC32(current_inseqno); + /* get TCP sequence number */ + DEV_GETC32(current_inseqno); - /* get TCP ack */ - DEV_GETC32(current_inack); + /* get TCP ack */ + DEV_GETC32(current_inack); - /* duplicate ACK: set nextoutseqno for retransmission */ - if(UI32(tmp_connection.protocol.http.next_outseqno) - UI16(tmp_connection.protocol.http.inflight) == UI32(current_inack)) { - UI32(tmp_connection.protocol.http.next_outseqno) = UI32(current_inack); - } + /* duplicate ACK: set nextoutseqno for retransmission */ + if(UI32(tmp_connection.protocol.http.next_outseqno) - UI16(tmp_connection.protocol.http.inflight) == UI32(current_inack)) { + UI32(tmp_connection.protocol.http.next_outseqno) = UI32(current_inack); + } - /* TCP ack management */ - if(UI32(current_inack) && UI32(current_inack) <= UI32(tmp_connection.protocol.http.next_outseqno)) { - UI16(tmp_connection.protocol.http.inflight) = UI32(tmp_connection.protocol.http.next_outseqno) - UI32(current_inack); - if(tmp_connection.protocol.http.generator_service) { - /* deferred because current segment has not yet been checked */ - defer_clean_service = 1; - } + /* TCP ack management */ + if(UI32(current_inack) && UI32(current_inack) <= UI32(tmp_connection.protocol.http.next_outseqno)) { + UI16(tmp_connection.protocol.http.inflight) = UI32(tmp_connection.protocol.http.next_outseqno) - UI32(current_inack); + if(tmp_connection.protocol.http.generator_service) { + /* deferred because current segment has not yet been checked */ + defer_clean_service = 1; } - /* clear output_handler if needed */ - if(tmp_connection.output_handler && UI16(tmp_connection.protocol.http.inflight) == 0 && !something_to_send(&tmp_connection)) { - if(tmp_connection.protocol.http.generator_service) { - /* deferred because current segment has not yet been checked */ - defer_free_handler = 1; + } + /* clear output_handler if needed */ + if(tmp_connection.output_handler && UI16(tmp_connection.protocol.http.inflight) == 0 && !something_to_send(&tmp_connection)) { + if(tmp_connection.protocol.http.generator_service) { + /* deferred because current segment has not yet been checked */ + defer_free_handler = 1; #ifndef DISABLE_ARGS - defer_free_args = tmp_connection.protocol.http.args; - defer_free_args_size = CONST_UI16(tmp_connection.output_handler->handler_args.args_size); + defer_free_args = tmp_connection.protocol.http.args; + defer_free_args_size = CONST_UI16(tmp_connection.output_handler->handler_args.args_size); #endif - } + } #ifndef DISABLE_COMET - if(/*tmp_connection.protocol.http.parsing_state == parsing_out &&*/ tmp_connection.protocol.http.comet_streaming == 0) { - tmp_connection.output_handler = NULL; - } -#endif + if(/*tmp_connection.protocol.http.parsing_state == parsing_out &&*/ tmp_connection.protocol.http.comet_streaming == 0) { + tmp_connection.output_handler = NULL; } +#endif + } - /* get TCP offset and flags */ - DEV_GETC16(tmp_ui16); - tcp_header_length = (tmp_ui16[S0] >> 4) * 4; - - /* TCP segment length calculation */ - - /* packet_length is the length of the IP payload *without* IP headers */ - segment_length = packet_length - tcp_header_length; - - if(packet_length - tcp_header_length > 0) - UI32(current_inseqno) += segment_length; - - new_tcp_data = UI32(current_inseqno) > UI32(tmp_connection.protocol.http.current_inseqno); - - if(UI32(current_inseqno) >= UI32(tmp_connection.protocol.http.current_inseqno)) { - UI32(tmp_connection.protocol.http.current_inseqno) = UI32(current_inseqno); - /* TCP state machine management */ - switch(tmp_connection.protocol.http.tcp_state) { - case tcp_established: - if(tmp_ui16[S1] & TCP_FIN) { - tmp_connection.protocol.http.tcp_state = tcp_last_ack; - tmp_connection.output_handler = &ref_finack; - UI32(tmp_connection.protocol.http.current_inseqno)++; - } else if(tmp_ui16[S1] & TCP_RST) { - tmp_connection.protocol.http.tcp_state = tcp_listen; - } - break; - case tcp_listen: - if(tmp_ui16[S1] & TCP_SYN) { - tmp_connection.protocol.http.tcp_state = tcp_syn_rcvd; - tmp_connection.output_handler = &ref_synack; - UI32(tmp_connection.protocol.http.current_inseqno)++; - } - break; - case tcp_syn_rcvd: - if(UI16(tmp_connection.protocol.http.inflight) == 0) { - tmp_connection.protocol.http.tcp_state = tcp_established; - } else { - tmp_connection.output_handler = &ref_synack; - } - break; - case tcp_last_ack: - tmp_connection.protocol.http.tcp_state = tcp_listen; - break; - default: - break; - } + /* get TCP offset and flags */ + DEV_GETC16(tmp_ui16); + tcp_header_length = (tmp_ui16[S0] >> 4) * 4; + + /* TCP segment length calculation */ + + /* packet_length is the length of the IP payload *without* IP headers */ + segment_length = packet_length - tcp_header_length; + + if(packet_length - tcp_header_length > 0) + UI32(current_inseqno) += segment_length; + + new_tcp_data = UI32(current_inseqno) > UI32(tmp_connection.protocol.http.current_inseqno); + + if(UI32(current_inseqno) >= UI32(tmp_connection.protocol.http.current_inseqno)) { + UI32(tmp_connection.protocol.http.current_inseqno) = UI32(current_inseqno); + /* TCP state machine management */ + switch(tmp_connection.protocol.http.tcp_state) { + case tcp_established: + if(tmp_ui16[S1] & TCP_FIN) { + tmp_connection.protocol.http.tcp_state = tcp_last_ack; + tmp_connection.output_handler = &ref_finack; + UI32(tmp_connection.protocol.http.current_inseqno)++; + } else if(tmp_ui16[S1] & TCP_RST) { + tmp_connection.protocol.http.tcp_state = tcp_listen; + } + break; + case tcp_listen: + if(tmp_ui16[S1] & TCP_SYN) { + tmp_connection.protocol.http.tcp_state = tcp_syn_rcvd; + tmp_connection.output_handler = &ref_synack; + UI32(tmp_connection.protocol.http.current_inseqno)++; + } + break; + case tcp_syn_rcvd: + if(UI16(tmp_connection.protocol.http.inflight) == 0) { + tmp_connection.protocol.http.tcp_state = tcp_established; + } else { + tmp_connection.output_handler = &ref_synack; + } + break; + case tcp_last_ack: + tmp_connection.protocol.http.tcp_state = tcp_listen; + break; + default: + break; } + } - /* get the advertissed TCP window in order to limit our sending rate if needed */ - DEV_GETC16(tmp_connection.protocol.http.cwnd); + /* get the advertissed TCP window in order to limit our sending rate if needed */ + DEV_GETC16(tmp_connection.protocol.http.cwnd); - /* discard the checksum (which is checksummed as other data) and the urgent pointer */ - DEV_GETC32(tmp_ui32); + /* discard the checksum (which is checksummed as other data) and the urgent pointer */ + DEV_GETC32(tmp_ui32); - /* add the changing part of the TCP pseudo header checksum */ + /* add the changing part of the TCP pseudo header checksum */ #ifdef IPV6 - checksum_add32(&local_ip_addr[0]); - checksum_add32(&local_ip_addr[4]); - checksum_add32(&local_ip_addr[8]); - checksum_add32(&local_ip_addr[12]); - - checksum_add32(&full_ipv6_addr[0]); - checksum_add32(&full_ipv6_addr[4]); - checksum_add32(&full_ipv6_addr[8]); - checksum_add32(&full_ipv6_addr[12]); + checksum_add32(&local_ip_addr[0]); + checksum_add32(&local_ip_addr[4]); + checksum_add32(&local_ip_addr[8]); + checksum_add32(&local_ip_addr[12]); + + checksum_add32(&full_ipv6_addr[0]); + checksum_add32(&full_ipv6_addr[4]); + checksum_add32(&full_ipv6_addr[8]); + checksum_add32(&full_ipv6_addr[12]); #else - checksum_add32(local_ip_addr); - checksum_add32(tmp_connection.ip_addr); + checksum_add32(local_ip_addr); + checksum_add32(tmp_connection.ip_addr); #endif - checksum_add16(packet_length); + checksum_add16(packet_length); - /* get TCP mss (for initial negociation) */ - tcp_header_length -= TCP_HEADER_SIZE; - if(tcp_header_length >= 4) { - tcp_header_length -= 4; - DEV_GETC16(tmp_ui16); - DEV_GETC16(tmp_ui16); - tmp_connection.protocol.http.tcp_mss = UI16(tmp_ui16) > MAX_MSS ? MAX_MSS : UI16(tmp_ui16); - } + /* get TCP mss (for initial negociation) */ + tcp_header_length -= TCP_HEADER_SIZE; + if(tcp_header_length >= 4) { + tcp_header_length -= 4; + DEV_GETC16(tmp_ui16); + DEV_GETC16(tmp_ui16); + tmp_connection.protocol.http.tcp_mss = UI16(tmp_ui16) > MAX_MSS ? MAX_MSS : UI16(tmp_ui16); + } - /* discard the remaining part of the TCP header */ - for(; tcp_header_length > 0; tcp_header_length-=4) { - DEV_GETC32(tmp_ui32); - } + /* discard the remaining part of the TCP header */ + for(; tcp_header_length > 0; tcp_header_length-=4) { + DEV_GETC32(tmp_ui32); + } - /* End of TCP, starting HTTP */ - x = 0; - if(segment_length && tmp_connection.protocol.http.tcp_state == tcp_established && (new_tcp_data || tmp_connection.output_handler == NULL)) { - const struct output_handler_t * /*CONST_VAR*/ output_handler = NULL; + /* End of TCP, starting HTTP */ + x = 0; + if(segment_length && tmp_connection.protocol.http.tcp_state == tcp_established && (new_tcp_data || tmp_connection.output_handler == NULL)) { + const struct output_handler_t * /*CONST_VAR*/ output_handler = NULL; - /* parse the eventual GET request */ - unsigned const char * /*CONST_VAR*/ blob; - unsigned char blob_curr; + /* parse the eventual GET request */ + unsigned const char * /*CONST_VAR*/ blob; + unsigned char blob_curr; #ifndef DISABLE_ARGS - struct arg_ref_t tmp_arg_ref = {0,0,0}; - uint16_t tmp_args_size_ref; + struct arg_ref_t tmp_arg_ref = {0,0,0}; + uint16_t tmp_args_size_ref; #endif - if(tmp_connection.protocol.http.parsing_state == parsing_out) { + if(tmp_connection.protocol.http.parsing_state == parsing_out) { #ifndef DISABLE_ARGS - tmp_connection.protocol.http.args = NULL; - tmp_connection.protocol.http.arg_ref_index = 128; + tmp_connection.protocol.http.args = NULL; + tmp_connection.protocol.http.arg_ref_index = 128; #endif - tmp_connection.protocol.http.blob = blob_http_rqt; - tmp_connection.protocol.http.parsing_state = parsing_cmd; - tmp_connection.protocol.http.ready_to_send = 1; - tmp_connection.output_handler = NULL; + tmp_connection.protocol.http.blob = blob_http_rqt; + tmp_connection.protocol.http.parsing_state = parsing_cmd; + tmp_connection.protocol.http.ready_to_send = 1; + tmp_connection.output_handler = NULL; #ifndef DISABLE_POST - tmp_connection.protocol.http.post_data = NULL; - tmp_connection.protocol.http.post_url_detected = 0; + tmp_connection.protocol.http.post_data = NULL; + tmp_connection.protocol.http.post_url_detected = 0; #endif - } - else if(tmp_connection.output_handler) - output_handler = tmp_connection.output_handler; - blob = tmp_connection.protocol.http.blob; + } + else if(tmp_connection.output_handler) + output_handler = tmp_connection.output_handler; + blob = tmp_connection.protocol.http.blob; #ifndef DISABLE_ARGS - if(tmp_connection.protocol.http.arg_ref_index != 128) { - struct arg_ref_t * /*CONST_VAR*/ tmp_arg_ref_ptr; - tmp_arg_ref_ptr = &(((struct arg_ref_t*)CONST_ADDR(output_handler->handler_args.args_index))[tmp_connection.protocol.http.arg_ref_index]); - tmp_arg_ref.arg_type = CONST_UI8(tmp_arg_ref_ptr->arg_type); - tmp_arg_ref.arg_size = CONST_UI8(tmp_arg_ref_ptr->arg_size); - tmp_arg_ref.arg_offset = CONST_UI8(tmp_arg_ref_ptr->arg_offset); - tmp_args_size_ref = CONST_UI16(output_handler->handler_args.args_size); - } -#endif - while(x < segment_length && output_handler != &http_404_handler + if(tmp_connection.protocol.http.arg_ref_index != 128) { + struct arg_ref_t * /*CONST_VAR*/ tmp_arg_ref_ptr; + tmp_arg_ref_ptr = &(((struct arg_ref_t*)CONST_ADDR(output_handler->handler_args.args_index))[tmp_connection.protocol.http.arg_ref_index]); + tmp_arg_ref.arg_type = CONST_UI8(tmp_arg_ref_ptr->arg_type); + tmp_arg_ref.arg_size = CONST_UI8(tmp_arg_ref_ptr->arg_size); + tmp_arg_ref.arg_offset = CONST_UI8(tmp_arg_ref_ptr->arg_offset); + tmp_args_size_ref = CONST_UI16(output_handler->handler_args.args_size); + } +#endif + while(x < segment_length && output_handler != &http_404_handler #ifndef DISABLE_POST - && output_handler != &http_505_handler + && output_handler != &http_505_handler #endif - ) { - blob_curr = CONST_READ_UI8(blob); + ) { + blob_curr = CONST_READ_UI8(blob); #ifndef DISABLE_POST - /* testing end multipart */ - if(tmp_connection.protocol.http.post_data - && tmp_connection.protocol.http.post_data->boundary - && tmp_connection.protocol.http.post_data->boundary->ready_to_count - && tmp_connection.protocol.http.post_data->content_length < 3){ - tmp_connection.protocol.http.ready_to_send = 1; - tmp_connection.protocol.http.parsing_state = parsing_end; - break; - } - if(tmp_connection.protocol.http.parsing_state != parsing_post_data){ -#endif - x++; - DEV_GETC(tmp_char); + /* testing end multipart */ + if(tmp_connection.protocol.http.post_data + && tmp_connection.protocol.http.post_data->boundary + && tmp_connection.protocol.http.post_data->boundary->ready_to_count + && tmp_connection.protocol.http.post_data->content_length < 3){ + tmp_connection.protocol.http.ready_to_send = 1; + tmp_connection.protocol.http.parsing_state = parsing_end; + break; + } + if(tmp_connection.protocol.http.parsing_state != parsing_post_data){ +#endif + x++; + DEV_GETC(tmp_char); #ifndef DISABLE_POST - /* updating content length */ - if((tmp_connection.protocol.http.parsing_state == parsing_init_buffer - || tmp_connection.protocol.http.parsing_state == parsing_post_args - ||(tmp_connection.protocol.http.post_data - && tmp_connection.protocol.http.post_data->boundary - && tmp_connection.protocol.http.post_data->boundary->ready_to_count)) - && tmp_connection.protocol.http.post_data->content_length != (uint16_t)-1) - tmp_connection.protocol.http.post_data->content_length--; - } - /* initializing buffer before parsing data */ - if(tmp_connection.protocol.http.parsing_state == parsing_init_buffer){ - tmp_connection.protocol.http.post_data->boundary->boundary_buffer[tmp_connection.protocol.http.post_data->boundary->index] = tmp_char; - tmp_connection.protocol.http.post_data->boundary->index++; - if(tmp_connection.protocol.http.post_data->boundary->index == tmp_connection.protocol.http.post_data->boundary->boundary_size){ - tmp_connection.protocol.http.parsing_state = parsing_post_data; - /* verifying buffer */ - if(tmp_connection.protocol.http.post_data->boundary->boundary_buffer[(tmp_connection.protocol.http.post_data->boundary->index-1) % tmp_connection.protocol.http.post_data->boundary->boundary_size] - == tmp_connection.protocol.http.post_data->boundary->boundary_ref[tmp_connection.protocol.http.post_data->boundary->boundary_size-1] - && tmp_connection.protocol.http.post_data->boundary->boundary_buffer[tmp_connection.protocol.http.post_data->boundary->index] - == tmp_connection.protocol.http.post_data->boundary->boundary_ref[0]){ - unsigned char index = tmp_connection.protocol.http.post_data->boundary->index + 1; - unsigned char i; - for(i = 1 ; i < tmp_connection.protocol.http.post_data->boundary->boundary_size-1 ; i++){ - if(index == tmp_connection.protocol.http.post_data->boundary->boundary_size) - index = 0; - if(tmp_connection.protocol.http.post_data->boundary->boundary_ref[i] != tmp_connection.protocol.http.post_data->boundary->boundary_buffer[index]) - break; - index++; - } - if(i == tmp_connection.protocol.http.post_data->boundary->boundary_size-1){ - tmp_connection.protocol.http.parsing_state = parsing_post_attributes; - blob = blob_http_header_content; - tmp_connection.protocol.http.post_data->boundary->index = -1; - } - } - } - else - continue; - } - /* parsing boundary */ - else if(tmp_connection.protocol.http.parsing_state == parsing_boundary){ - if(tmp_connection.protocol.http.post_data->boundary->index == tmp_connection.protocol.http.post_data->boundary->boundary_size) - tmp_connection.protocol.http.post_data->boundary->index = 0; /* update index */ - /* comparing first and last characters then all */ - if(tmp_connection.protocol.http.post_data->boundary->boundary_buffer[(tmp_connection.protocol.http.post_data->boundary->index-1) % tmp_connection.protocol.http.post_data->boundary->boundary_size] - == tmp_connection.protocol.http.post_data->boundary->boundary_ref[tmp_connection.protocol.http.post_data->boundary->boundary_size-1] - && tmp_connection.protocol.http.post_data->boundary->boundary_buffer[tmp_connection.protocol.http.post_data->boundary->index] - == tmp_connection.protocol.http.post_data->boundary->boundary_ref[0]){ - unsigned char index = tmp_connection.protocol.http.post_data->boundary->index + 1; - unsigned char i; - for(i = 1 ; i < tmp_connection.protocol.http.post_data->boundary->boundary_size-1 ; i++){ - if(index == tmp_connection.protocol.http.post_data->boundary->boundary_size) - index = 0; - if(tmp_connection.protocol.http.post_data->boundary->boundary_ref[i] != tmp_connection.protocol.http.post_data->boundary->boundary_buffer[index]) - break; - index++; - } - if(i == tmp_connection.protocol.http.post_data->boundary->boundary_size-1){ - tmp_connection.protocol.http.parsing_state = parsing_post_attributes; - blob = blob_http_header_content; - tmp_connection.protocol.http.post_data->boundary->index = -1; - continue; - } - } - tmp_connection.protocol.http.post_data->boundary->boundary_buffer[tmp_connection.protocol.http.post_data->boundary->index] = tmp_char; - tmp_connection.protocol.http.post_data->boundary->index++; - continue; - } -#endif - /* search for the web applicative resource to send */ - if(blob_curr >= 128 && output_handler != &http_404_handler + /* updating content length */ + if((tmp_connection.protocol.http.parsing_state == parsing_init_buffer + || tmp_connection.protocol.http.parsing_state == parsing_post_args + ||(tmp_connection.protocol.http.post_data + && tmp_connection.protocol.http.post_data->boundary + && tmp_connection.protocol.http.post_data->boundary->ready_to_count)) + && tmp_connection.protocol.http.post_data->content_length != (uint16_t)-1) + tmp_connection.protocol.http.post_data->content_length--; + } + /* initializing buffer before parsing data */ + if(tmp_connection.protocol.http.parsing_state == parsing_init_buffer){ + tmp_connection.protocol.http.post_data->boundary->boundary_buffer[tmp_connection.protocol.http.post_data->boundary->index] = tmp_char; + tmp_connection.protocol.http.post_data->boundary->index++; + if(tmp_connection.protocol.http.post_data->boundary->index == tmp_connection.protocol.http.post_data->boundary->boundary_size){ + tmp_connection.protocol.http.parsing_state = parsing_post_data; + /* verifying buffer */ + if(tmp_connection.protocol.http.post_data->boundary->boundary_buffer[(tmp_connection.protocol.http.post_data->boundary->index-1) % tmp_connection.protocol.http.post_data->boundary->boundary_size] + == tmp_connection.protocol.http.post_data->boundary->boundary_ref[tmp_connection.protocol.http.post_data->boundary->boundary_size-1] + && tmp_connection.protocol.http.post_data->boundary->boundary_buffer[tmp_connection.protocol.http.post_data->boundary->index] + == tmp_connection.protocol.http.post_data->boundary->boundary_ref[0]){ + unsigned char index = tmp_connection.protocol.http.post_data->boundary->index + 1; + unsigned char i; + for(i = 1 ; i < tmp_connection.protocol.http.post_data->boundary->boundary_size-1 ; i++){ + if(index == tmp_connection.protocol.http.post_data->boundary->boundary_size) + index = 0; + if(tmp_connection.protocol.http.post_data->boundary->boundary_ref[i] != tmp_connection.protocol.http.post_data->boundary->boundary_buffer[index]) + break; + index++; + } + if(i == tmp_connection.protocol.http.post_data->boundary->boundary_size-1){ + tmp_connection.protocol.http.parsing_state = parsing_post_attributes; + blob = blob_http_header_content; + tmp_connection.protocol.http.post_data->boundary->index = -1; + } + } + } + else + continue; + } + /* parsing boundary */ + else if(tmp_connection.protocol.http.parsing_state == parsing_boundary){ + if(tmp_connection.protocol.http.post_data->boundary->index == tmp_connection.protocol.http.post_data->boundary->boundary_size) + tmp_connection.protocol.http.post_data->boundary->index = 0; /* update index */ + /* comparing first and last characters then all */ + if(tmp_connection.protocol.http.post_data->boundary->boundary_buffer[(tmp_connection.protocol.http.post_data->boundary->index-1) % tmp_connection.protocol.http.post_data->boundary->boundary_size] + == tmp_connection.protocol.http.post_data->boundary->boundary_ref[tmp_connection.protocol.http.post_data->boundary->boundary_size-1] + && tmp_connection.protocol.http.post_data->boundary->boundary_buffer[tmp_connection.protocol.http.post_data->boundary->index] + == tmp_connection.protocol.http.post_data->boundary->boundary_ref[0]){ + unsigned char index = tmp_connection.protocol.http.post_data->boundary->index + 1; + unsigned char i; + for(i = 1 ; i < tmp_connection.protocol.http.post_data->boundary->boundary_size-1 ; i++){ + if(index == tmp_connection.protocol.http.post_data->boundary->boundary_size) + index = 0; + if(tmp_connection.protocol.http.post_data->boundary->boundary_ref[i] != tmp_connection.protocol.http.post_data->boundary->boundary_buffer[index]) + break; + index++; + } + if(i == tmp_connection.protocol.http.post_data->boundary->boundary_size-1){ + tmp_connection.protocol.http.parsing_state = parsing_post_attributes; + blob = blob_http_header_content; + tmp_connection.protocol.http.post_data->boundary->index = -1; + continue; + } + } + tmp_connection.protocol.http.post_data->boundary->boundary_buffer[tmp_connection.protocol.http.post_data->boundary->index] = tmp_char; + tmp_connection.protocol.http.post_data->boundary->index++; + continue; + } +#endif + /* search for the web applicative resource to send */ + if(blob_curr >= 128 && output_handler != &http_404_handler #ifndef DISABLE_POST - && output_handler != &http_505_handler + && output_handler != &http_505_handler #endif - ) { + ) { #ifndef DISABLE_POST - /* "\n\r\n\r" detected (end header or end part of multipart )*/ - if(tmp_connection.protocol.http.parsing_state == parsing_post_end){ - if(tmp_connection.protocol.http.post_data->content_type == (uint8_t)-1){ /* no content type */ - output_handler = &http_505_handler; - break; - } - if(tmp_connection.protocol.http.post_data->boundary){ - tmp_connection.protocol.http.post_data->boundary->ready_to_count = 1; - /* parsing header part of multipart */ - if(tmp_connection.protocol.http.post_data->content_type == CONTENT_TYPE_MULTIPART_47_FORM_45_DATA){ - tmp_connection.protocol.http.parsing_state = parsing_post_attributes; - blob = blob_http_header_content; - continue; - } - else - /* parsing data */ - tmp_connection.protocol.http.parsing_state = parsing_post_data; - } - /* parsing form */ - else if(tmp_connection.protocol.http.post_data->content_type == CONTENT_TYPE_APPLICATION_47_X_45_WWW_45_FORM_45_URLENCODED) - tmp_connection.protocol.http.parsing_state = parsing_post_args; - else{ - /* no content length detected = boundary is "\r\n\r\n" */ - if(tmp_connection.protocol.http.post_data->content_length == (uint16_t)-1){ - tmp_connection.protocol.http.post_data->boundary = mem_alloc(sizeof(struct boundary_t)); - if(!tmp_connection.protocol.http.post_data->boundary){ - output_handler = &http_404_handler; - break; - } - tmp_connection.protocol.http.post_data->boundary->boundary_size = 4; - tmp_connection.protocol.http.post_data->boundary->index = -1; - tmp_connection.protocol.http.post_data->boundary->multi_part_counter = 0; - tmp_connection.protocol.http.post_data->boundary->boundary_ref = "\r\n\r\n"; - tmp_connection.protocol.http.post_data->boundary->boundary_buffer = mem_alloc(4*sizeof(char)); - if(!tmp_connection.protocol.http.post_data->boundary->boundary_buffer){ - /* If boundary buffer can not be allocated, also free the boundary itself */ - mem_free(tmp_connection.protocol.http.post_data->boundary, sizeof(struct boundary_t)); - output_handler = &http_404_handler; - break; - } - } - tmp_connection.protocol.http.parsing_state = parsing_post_data; - } - } - /* parsing post data => run coroutine */ - if(tmp_connection.protocol.http.parsing_state == parsing_post_data){ - /* starting buffer init */ - if(tmp_connection.protocol.http.post_data->coroutine.curr_context.status == cr_terminated - && tmp_connection.protocol.http.post_data->boundary - && tmp_connection.protocol.http.post_data->boundary->index == 255){ - tmp_connection.protocol.http.post_data->boundary->index = 0; - tmp_connection.protocol.http.parsing_state = parsing_init_buffer; - continue; - } - uint16_t length_temp = segment_length - x; - /* initialization coroutine */ - if(tmp_connection.protocol.http.post_data->coroutine.curr_context.status == cr_terminated){ - cr_init(&(tmp_connection.protocol.http.post_data->coroutine)); - tmp_connection.protocol.http.post_data->coroutine.func.func_post_in = CONST_ADDR(GET_GENERATOR(output_handler).handlers.post.dopostin); - tmp_connection.protocol.http.post_data->coroutine.params.in.content_type = tmp_connection.protocol.http.post_data->content_type; - tmp_connection.protocol.http.post_data->coroutine.params.in.filename = tmp_connection.protocol.http.post_data->filename; - tmp_connection.protocol.http.post_data->coroutine.params.in.post_data = tmp_connection.protocol.http.post_data->post_data; - if(tmp_connection.protocol.http.post_data->boundary){ - tmp_connection.protocol.http.post_data->coroutine.params.in.part_number = tmp_connection.protocol.http.post_data->boundary->multi_part_counter; - tmp_connection.protocol.http.post_data->boundary->index = 0; - } - else - tmp_connection.protocol.http.post_data->coroutine.params.in.part_number = 0; - if(cr_prepare(&(tmp_connection.protocol.http.post_data->coroutine)) == NULL) - { - /* TODO: shoudn't the boundary and boundary_buffer be free ? */ - return 1; - } - } - curr_input.length = length_temp; - curr_input.connection = &tmp_connection; - coroutine_state.state = cor_in; - /* running coroutine */ - cr_run(&(tmp_connection.protocol.http.post_data->coroutine),cor_type_post_in); - coroutine_state.state = cor_out; - x += (length_temp-curr_input.length); - if(tmp_connection.protocol.http.post_data->coroutine.curr_context.status == cr_terminated){ /* if is terminated */ - tmp_connection.protocol.http.post_data->post_data = tmp_connection.protocol.http.post_data->coroutine.params.in.post_data; - cr_clean(&(tmp_connection.protocol.http.post_data->coroutine)); - /* cleaning filename memory */ - if(tmp_connection.protocol.http.post_data->filename){ - uint8_t i = 0; - while(tmp_connection.protocol.http.post_data->filename[i++] != '\0'); - mem_free(tmp_connection.protocol.http.post_data->filename,i*sizeof(char)); - tmp_connection.protocol.http.post_data->filename = NULL; - } - /* if no boundary, there is no data to parse */ - if(!tmp_connection.protocol.http.post_data->boundary){ - tmp_connection.protocol.http.parsing_state = parsing_end; - tmp_connection.protocol.http.ready_to_send = 1; - } - else{ - /* starting search of boundary if no found, or ready for next part */ - tmp_connection.protocol.http.post_data->boundary->multi_part_counter++; - if(tmp_connection.protocol.http.post_data->boundary->index != (uint8_t)-1) - tmp_connection.protocol.http.parsing_state = parsing_boundary; - else{ - tmp_connection.protocol.http.parsing_state = parsing_post_attributes; - blob = blob_http_header_content; - } - /* if no content length, parsing is terminated */ - if(tmp_connection.protocol.http.post_data->content_length == (uint16_t)-1){ - tmp_connection.protocol.http.parsing_state = parsing_end; - tmp_connection.protocol.http.ready_to_send = 1; - break; - } - } - } - if(tmp_connection.protocol.http.post_data->boundary) - continue; - break; - } - /* searching end character of post url detection */ - if(tmp_connection.protocol.http.parsing_state == parsing_url && blob_curr == URL_POST_END){ - if(tmp_connection.protocol.http.post_data){ - tmp_connection.protocol.http.post_url_detected = 1; - blob_curr = CONST_READ_UI8(++blob); - } - else{ /* get unauthorized */ - output_handler = &http_404_handler; - break; - } - } - /* parsing attributes of post header */ - if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes){ - if(blob_curr == ATTRIBUT_CONTENT_45_TYPE + 128) { /* content-type */ - blob = mimes_tree; - tmp_connection.protocol.http.parsing_state = parsing_post_content_type; - } - else if(blob_curr == ATTRIBUT_CONTENT_45_LENGTH + 128){ /* content-length */ - if(tmp_connection.protocol.http.post_data->content_length == (uint8_t)-1) - tmp_connection.protocol.http.post_data->content_length = 0; - if(tmp_char != ' ' && tmp_char != '\r'){ - tmp_connection.protocol.http.post_data->content_length *= 10; - tmp_connection.protocol.http.post_data->content_length += tmp_char - '0'; - } - } - else if(blob_curr == ATTRIBUT_FILENAME_61_ + 128){ /* filename */ - if(!tmp_connection.protocol.http.post_data->filename){ - tmp_connection.protocol.http.post_data->filename = mem_alloc(10 * sizeof(char)); - if(!tmp_connection.protocol.http.post_data->filename){ - output_handler = &http_404_handler; - break; - } - tmp_connection.protocol.http.post_data->filename[0] = 2; /* first value is the size of filename */ - tmp_connection.protocol.http.post_data->filename[1] = 10; /* seconde value is the size allocated */ - } - else if(tmp_char == '\"'){ /* end of filename, the indexes are removed */ - uint8_t i = 0; - char *new_tab = mem_alloc((tmp_connection.protocol.http.post_data->filename[0]-1)*sizeof(char)); - if(!new_tab){ - output_handler = &http_404_handler; - break; - } - for(i = 0 ; i < tmp_connection.protocol.http.post_data->filename[0]-2 ; i++) - new_tab[i] = tmp_connection.protocol.http.post_data->filename[i+2]; - new_tab[i]='\0'; - mem_free(tmp_connection.protocol.http.post_data->filename,(tmp_connection.protocol.http.post_data->filename[1])*sizeof(char)); - tmp_connection.protocol.http.post_data->filename = new_tab; - tmp_connection.protocol.http.parsing_state = parsing_post_attributes; - blob = blob_http_header_content; - } - else{ - /* reallocating filename */ - if(tmp_connection.protocol.http.post_data->filename[0] == tmp_connection.protocol.http.post_data->filename[1]){ - tmp_connection.protocol.http.post_data->filename = mem_realloc(tmp_connection.protocol.http.post_data->filename,tmp_connection.protocol.http.post_data->filename[1],10); - if(!tmp_connection.protocol.http.post_data->filename){ - output_handler = &http_404_handler; - break; - } - /* updating size */ - tmp_connection.protocol.http.post_data->filename[1] = tmp_connection.protocol.http.post_data->filename[1] + 10; - } - /* add current character */ - tmp_connection.protocol.http.post_data->filename[tmp_connection.protocol.http.post_data->filename[0]] = tmp_char; - tmp_connection.protocol.http.post_data->filename[0]++; - } - } - /* parsing boundary */ - else if(blob_curr == ATTRIBUT_BOUNDARY_61_ + 128){ - if(tmp_char != '\r'){ - /* reallocating tab */ - if(tmp_connection.protocol.http.post_data->boundary->index == tmp_connection.protocol.http.post_data->boundary->boundary_size){ - tmp_connection.protocol.http.post_data->boundary->boundary_ref = mem_realloc(tmp_connection.protocol.http.post_data->boundary->boundary_ref,tmp_connection.protocol.http.post_data->boundary->boundary_size,10); - if(!tmp_connection.protocol.http.post_data->boundary->boundary_ref){ - output_handler = &http_404_handler; - break; - } - /* updating size */ - tmp_connection.protocol.http.post_data->boundary->boundary_size += 10; - } - tmp_connection.protocol.http.post_data->boundary->boundary_ref[tmp_connection.protocol.http.post_data->boundary->index++] = tmp_char; /* saving boundary char */ - } - else{ - /* final reallocation */ - uint8_t i = 0; - char *new_tab = mem_alloc((tmp_connection.protocol.http.post_data->boundary->index+5)*sizeof(char)); - if(!new_tab){ - output_handler = &http_404_handler; - break; - } - new_tab[0] = '\n'; - new_tab[1] = '\r'; - new_tab[2] = '\n'; - new_tab[3] = '-'; - new_tab[4] = '-'; - /* copying boundary */ - for(i = 0 ; i < tmp_connection.protocol.http.post_data->boundary->index ; i++) - new_tab[i+5] = tmp_connection.protocol.http.post_data->boundary->boundary_ref[i]; - mem_free(tmp_connection.protocol.http.post_data->boundary->boundary_ref,(tmp_connection.protocol.http.post_data->boundary->boundary_size)*sizeof(char)); - tmp_connection.protocol.http.post_data->boundary->boundary_size = tmp_connection.protocol.http.post_data->boundary->index + 5; - tmp_connection.protocol.http.post_data->boundary->boundary_ref = new_tab; - tmp_connection.protocol.http.post_data->boundary->index = -1; - tmp_connection.protocol.http.post_data->boundary->boundary_buffer = mem_alloc(tmp_connection.protocol.http.post_data->boundary->boundary_size*sizeof(char)); - if(!tmp_connection.protocol.http.post_data->boundary->boundary_buffer){ - /* If no space for boundary_ref, free boundary */ - mem_free(tmp_connection.protocol.http.post_data->boundary, sizeof(struct boundary_t)); - output_handler = &http_404_handler; - break; - } - } - } - } - /* parsing content-type */ - else if(tmp_connection.protocol.http.parsing_state == parsing_post_content_type){ - if(blob_curr - 128 == CONTENT_TYPE_MULTIPART_47_FORM_45_DATA){ /* multipart */ - /* allocating boundary structure */ - tmp_connection.protocol.http.post_data->boundary = mem_alloc(sizeof(struct boundary_t)); - if(!tmp_connection.protocol.http.post_data->boundary){ - output_handler = &http_404_handler; - break; - } - tmp_connection.protocol.http.post_data->boundary->boundary_size = 10; - tmp_connection.protocol.http.post_data->boundary->index = 0; - tmp_connection.protocol.http.post_data->boundary->multi_part_counter = 0; - tmp_connection.protocol.http.post_data->boundary->ready_to_count = 0; - tmp_connection.protocol.http.post_data->boundary->boundary_ref = mem_alloc(10*sizeof(char)); - if(!tmp_connection.protocol.http.post_data->boundary->boundary_ref){ - /* If no space for boundary_ref, free boundary */ - mem_free(tmp_connection.protocol.http.post_data->boundary, sizeof(struct boundary_t)); - output_handler = &http_404_handler; - break; - } - tmp_connection.protocol.http.post_data->content_type = CONTENT_TYPE_MULTIPART_47_FORM_45_DATA; - } - else{ /* searching content-type in application to verify it */ - tmp_connection.protocol.http.post_data->content_type = (uint8_t) -1; - if(!output_handler->handler_mimes.mimes_size) - tmp_connection.protocol.http.post_data->content_type = blob_curr - 128; - else{ - uint8_t i; - for(i = 0 ; i < output_handler->handler_mimes.mimes_size ; i++){ - if(output_handler->handler_mimes.mimes_index[i] == blob_curr - 128) - tmp_connection.protocol.http.post_data->content_type = blob_curr - 128; - } - } - if(tmp_connection.protocol.http.post_data->content_type == (uint8_t) -1){ - output_handler = &http_404_handler; - break; - } - } - tmp_connection.protocol.http.parsing_state = parsing_post_attributes; - blob = blob_http_header_content; - } else -#endif - if(tmp_connection.protocol.http.parsing_state == parsing_cmd) { + /* "\n\r\n\r" detected (end header or end part of multipart )*/ + if(tmp_connection.protocol.http.parsing_state == parsing_post_end){ + if(tmp_connection.protocol.http.post_data->content_type == (uint8_t)-1){ /* no content type */ + output_handler = &http_505_handler; + break; + } + if(tmp_connection.protocol.http.post_data->boundary){ + tmp_connection.protocol.http.post_data->boundary->ready_to_count = 1; + /* parsing header part of multipart */ + if(tmp_connection.protocol.http.post_data->content_type == CONTENT_TYPE_MULTIPART_47_FORM_45_DATA){ + tmp_connection.protocol.http.parsing_state = parsing_post_attributes; + blob = blob_http_header_content; + continue; + } + else + /* parsing data */ + tmp_connection.protocol.http.parsing_state = parsing_post_data; + } + /* parsing form */ + else if(tmp_connection.protocol.http.post_data->content_type == CONTENT_TYPE_APPLICATION_47_X_45_WWW_45_FORM_45_URLENCODED) + tmp_connection.protocol.http.parsing_state = parsing_post_args; + else{ + /* no content length detected = boundary is "\r\n\r\n" */ + if(tmp_connection.protocol.http.post_data->content_length == (uint16_t)-1){ + tmp_connection.protocol.http.post_data->boundary = mem_alloc(sizeof(struct boundary_t)); + if(!tmp_connection.protocol.http.post_data->boundary){ + output_handler = &http_404_handler; + break; + } + tmp_connection.protocol.http.post_data->boundary->boundary_size = 4; + tmp_connection.protocol.http.post_data->boundary->index = -1; + tmp_connection.protocol.http.post_data->boundary->multi_part_counter = 0; + tmp_connection.protocol.http.post_data->boundary->boundary_ref = "\r\n\r\n"; + tmp_connection.protocol.http.post_data->boundary->boundary_buffer = mem_alloc(4*sizeof(char)); + if(!tmp_connection.protocol.http.post_data->boundary->boundary_buffer){ + /* If boundary buffer can not be allocated, also free the boundary itself */ + mem_free(tmp_connection.protocol.http.post_data->boundary, sizeof(struct boundary_t)); + output_handler = &http_404_handler; + break; + } + } + tmp_connection.protocol.http.parsing_state = parsing_post_data; + } + } + /* parsing post data => run coroutine */ + if(tmp_connection.protocol.http.parsing_state == parsing_post_data){ + /* starting buffer init */ + if(tmp_connection.protocol.http.post_data->coroutine.curr_context.status == cr_terminated + && tmp_connection.protocol.http.post_data->boundary + && tmp_connection.protocol.http.post_data->boundary->index == 255){ + tmp_connection.protocol.http.post_data->boundary->index = 0; + tmp_connection.protocol.http.parsing_state = parsing_init_buffer; + continue; + } + uint16_t length_temp = segment_length - x; + /* initialization coroutine */ + if(tmp_connection.protocol.http.post_data->coroutine.curr_context.status == cr_terminated){ + cr_init(&(tmp_connection.protocol.http.post_data->coroutine)); + tmp_connection.protocol.http.post_data->coroutine.func.func_post_in = CONST_ADDR(GET_GENERATOR(output_handler).handlers.post.dopostin); + tmp_connection.protocol.http.post_data->coroutine.params.in.content_type = tmp_connection.protocol.http.post_data->content_type; + tmp_connection.protocol.http.post_data->coroutine.params.in.filename = tmp_connection.protocol.http.post_data->filename; + tmp_connection.protocol.http.post_data->coroutine.params.in.post_data = tmp_connection.protocol.http.post_data->post_data; + if(tmp_connection.protocol.http.post_data->boundary){ + tmp_connection.protocol.http.post_data->coroutine.params.in.part_number = tmp_connection.protocol.http.post_data->boundary->multi_part_counter; + tmp_connection.protocol.http.post_data->boundary->index = 0; + } + else + tmp_connection.protocol.http.post_data->coroutine.params.in.part_number = 0; + if(cr_prepare(&(tmp_connection.protocol.http.post_data->coroutine)) == NULL) + { + /* TODO: shoudn't the boundary and boundary_buffer be free ? */ + return 1; + } + } + curr_input.length = length_temp; + curr_input.connection = &tmp_connection; + coroutine_state.state = cor_in; + /* running coroutine */ + cr_run(&(tmp_connection.protocol.http.post_data->coroutine),cor_type_post_in); + coroutine_state.state = cor_out; + x += (length_temp-curr_input.length); + if(tmp_connection.protocol.http.post_data->coroutine.curr_context.status == cr_terminated){ /* if is terminated */ + tmp_connection.protocol.http.post_data->post_data = tmp_connection.protocol.http.post_data->coroutine.params.in.post_data; + cr_clean(&(tmp_connection.protocol.http.post_data->coroutine)); + /* cleaning filename memory */ + if(tmp_connection.protocol.http.post_data->filename){ + uint8_t i = 0; + while(tmp_connection.protocol.http.post_data->filename[i++] != '\0'); + mem_free(tmp_connection.protocol.http.post_data->filename,i*sizeof(char)); + tmp_connection.protocol.http.post_data->filename = NULL; + } + /* if no boundary, there is no data to parse */ + if(!tmp_connection.protocol.http.post_data->boundary){ + tmp_connection.protocol.http.parsing_state = parsing_end; + tmp_connection.protocol.http.ready_to_send = 1; + } + else{ + /* starting search of boundary if no found, or ready for next part */ + tmp_connection.protocol.http.post_data->boundary->multi_part_counter++; + if(tmp_connection.protocol.http.post_data->boundary->index != (uint8_t)-1) + tmp_connection.protocol.http.parsing_state = parsing_boundary; + else{ + tmp_connection.protocol.http.parsing_state = parsing_post_attributes; + blob = blob_http_header_content; + } + /* if no content length, parsing is terminated */ + if(tmp_connection.protocol.http.post_data->content_length == (uint16_t)-1){ + tmp_connection.protocol.http.parsing_state = parsing_end; + tmp_connection.protocol.http.ready_to_send = 1; + break; + } + } + } + if(tmp_connection.protocol.http.post_data->boundary) + continue; + break; + } + /* searching end character of post url detection */ + if(tmp_connection.protocol.http.parsing_state == parsing_url && blob_curr == URL_POST_END){ + if(tmp_connection.protocol.http.post_data){ + tmp_connection.protocol.http.post_url_detected = 1; + blob_curr = CONST_READ_UI8(++blob); + } + else{ /* get unauthorized */ + output_handler = &http_404_handler; + break; + } + } + /* parsing attributes of post header */ + if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes){ + if(blob_curr == ATTRIBUT_CONTENT_45_TYPE + 128) { /* content-type */ + blob = mimes_tree; + tmp_connection.protocol.http.parsing_state = parsing_post_content_type; + } + else if(blob_curr == ATTRIBUT_CONTENT_45_LENGTH + 128){ /* content-length */ + if(tmp_connection.protocol.http.post_data->content_length == (uint8_t)-1) + tmp_connection.protocol.http.post_data->content_length = 0; + if(tmp_char != ' ' && tmp_char != '\r'){ + tmp_connection.protocol.http.post_data->content_length *= 10; + tmp_connection.protocol.http.post_data->content_length += tmp_char - '0'; + } + } + else if(blob_curr == ATTRIBUT_FILENAME_61_ + 128){ /* filename */ + if(!tmp_connection.protocol.http.post_data->filename){ + tmp_connection.protocol.http.post_data->filename = mem_alloc(10 * sizeof(char)); + if(!tmp_connection.protocol.http.post_data->filename){ + output_handler = &http_404_handler; + break; + } + tmp_connection.protocol.http.post_data->filename[0] = 2; /* first value is the size of filename */ + tmp_connection.protocol.http.post_data->filename[1] = 10; /* seconde value is the size allocated */ + } + else if(tmp_char == '\"'){ /* end of filename, the indexes are removed */ + uint8_t i = 0; + char *new_tab = mem_alloc((tmp_connection.protocol.http.post_data->filename[0]-1)*sizeof(char)); + if(!new_tab){ + output_handler = &http_404_handler; + break; + } + for(i = 0 ; i < tmp_connection.protocol.http.post_data->filename[0]-2 ; i++) + new_tab[i] = tmp_connection.protocol.http.post_data->filename[i+2]; + new_tab[i]='\0'; + mem_free(tmp_connection.protocol.http.post_data->filename,(tmp_connection.protocol.http.post_data->filename[1])*sizeof(char)); + tmp_connection.protocol.http.post_data->filename = new_tab; + tmp_connection.protocol.http.parsing_state = parsing_post_attributes; + blob = blob_http_header_content; + } + else{ + /* reallocating filename */ + if(tmp_connection.protocol.http.post_data->filename[0] == tmp_connection.protocol.http.post_data->filename[1]){ + tmp_connection.protocol.http.post_data->filename = mem_realloc(tmp_connection.protocol.http.post_data->filename,tmp_connection.protocol.http.post_data->filename[1],10); + if(!tmp_connection.protocol.http.post_data->filename){ + output_handler = &http_404_handler; + break; + } + /* updating size */ + tmp_connection.protocol.http.post_data->filename[1] = tmp_connection.protocol.http.post_data->filename[1] + 10; + } + /* add current character */ + tmp_connection.protocol.http.post_data->filename[tmp_connection.protocol.http.post_data->filename[0]] = tmp_char; + tmp_connection.protocol.http.post_data->filename[0]++; + } + } + /* parsing boundary */ + else if(blob_curr == ATTRIBUT_BOUNDARY_61_ + 128){ + if(tmp_char != '\r'){ + /* reallocating tab */ + if(tmp_connection.protocol.http.post_data->boundary->index == tmp_connection.protocol.http.post_data->boundary->boundary_size){ + tmp_connection.protocol.http.post_data->boundary->boundary_ref = mem_realloc(tmp_connection.protocol.http.post_data->boundary->boundary_ref,tmp_connection.protocol.http.post_data->boundary->boundary_size,10); + if(!tmp_connection.protocol.http.post_data->boundary->boundary_ref){ + output_handler = &http_404_handler; + break; + } + /* updating size */ + tmp_connection.protocol.http.post_data->boundary->boundary_size += 10; + } + tmp_connection.protocol.http.post_data->boundary->boundary_ref[tmp_connection.protocol.http.post_data->boundary->index++] = tmp_char; /* saving boundary char */ + } + else{ + /* final reallocation */ + uint8_t i = 0; + char *new_tab = mem_alloc((tmp_connection.protocol.http.post_data->boundary->index+5)*sizeof(char)); + if(!new_tab){ + output_handler = &http_404_handler; + break; + } + new_tab[0] = '\n'; + new_tab[1] = '\r'; + new_tab[2] = '\n'; + new_tab[3] = '-'; + new_tab[4] = '-'; + /* copying boundary */ + for(i = 0 ; i < tmp_connection.protocol.http.post_data->boundary->index ; i++) + new_tab[i+5] = tmp_connection.protocol.http.post_data->boundary->boundary_ref[i]; + mem_free(tmp_connection.protocol.http.post_data->boundary->boundary_ref,(tmp_connection.protocol.http.post_data->boundary->boundary_size)*sizeof(char)); + tmp_connection.protocol.http.post_data->boundary->boundary_size = tmp_connection.protocol.http.post_data->boundary->index + 5; + tmp_connection.protocol.http.post_data->boundary->boundary_ref = new_tab; + tmp_connection.protocol.http.post_data->boundary->index = -1; + tmp_connection.protocol.http.post_data->boundary->boundary_buffer = mem_alloc(tmp_connection.protocol.http.post_data->boundary->boundary_size*sizeof(char)); + if(!tmp_connection.protocol.http.post_data->boundary->boundary_buffer){ + /* If no space for boundary_ref, free boundary */ + mem_free(tmp_connection.protocol.http.post_data->boundary, sizeof(struct boundary_t)); + output_handler = &http_404_handler; + break; + } + } + } + } + /* parsing content-type */ + else if(tmp_connection.protocol.http.parsing_state == parsing_post_content_type){ + if(blob_curr - 128 == CONTENT_TYPE_MULTIPART_47_FORM_45_DATA){ /* multipart */ + /* allocating boundary structure */ + tmp_connection.protocol.http.post_data->boundary = mem_alloc(sizeof(struct boundary_t)); + if(!tmp_connection.protocol.http.post_data->boundary){ + output_handler = &http_404_handler; + break; + } + tmp_connection.protocol.http.post_data->boundary->boundary_size = 10; + tmp_connection.protocol.http.post_data->boundary->index = 0; + tmp_connection.protocol.http.post_data->boundary->multi_part_counter = 0; + tmp_connection.protocol.http.post_data->boundary->ready_to_count = 0; + tmp_connection.protocol.http.post_data->boundary->boundary_ref = mem_alloc(10*sizeof(char)); + if(!tmp_connection.protocol.http.post_data->boundary->boundary_ref){ + /* If no space for boundary_ref, free boundary */ + mem_free(tmp_connection.protocol.http.post_data->boundary, sizeof(struct boundary_t)); + output_handler = &http_404_handler; + break; + } + tmp_connection.protocol.http.post_data->content_type = CONTENT_TYPE_MULTIPART_47_FORM_45_DATA; + } + else{ /* searching content-type in application to verify it */ + tmp_connection.protocol.http.post_data->content_type = (uint8_t) -1; + if(!output_handler->handler_mimes.mimes_size) + tmp_connection.protocol.http.post_data->content_type = blob_curr - 128; + else{ + uint8_t i; + for(i = 0 ; i < output_handler->handler_mimes.mimes_size ; i++){ + if(output_handler->handler_mimes.mimes_index[i] == blob_curr - 128) + tmp_connection.protocol.http.post_data->content_type = blob_curr - 128; + } + } + if(tmp_connection.protocol.http.post_data->content_type == (uint8_t) -1){ + output_handler = &http_404_handler; + break; + } + } + tmp_connection.protocol.http.parsing_state = parsing_post_attributes; + blob = blob_http_header_content; + } else +#endif + if(tmp_connection.protocol.http.parsing_state == parsing_cmd) { #ifndef DISABLE_POST - if(blob_curr == REQUEST_POST_32_ + 128){ /* post request */ - /* allocating post data structure */ - tmp_connection.protocol.http.post_data = mem_alloc(sizeof(struct post_data_t)); - if(!tmp_connection.protocol.http.post_data){ - output_handler = &http_404_handler; - break; - } - tmp_connection.protocol.http.post_data->content_type = -1; - tmp_connection.protocol.http.post_data->content_length = -1; - tmp_connection.protocol.http.post_data->boundary = NULL; - tmp_connection.protocol.http.post_data->post_data = NULL; - tmp_connection.protocol.http.post_data->filename = NULL; - tmp_connection.protocol.http.post_data->coroutine.curr_context.status = cr_terminated; - } -#endif - tmp_connection.protocol.http.parsing_state = parsing_url; - blob = urls_tree; - } else { - if(tmp_char == ' ') { + if(blob_curr == REQUEST_POST_32_ + 128){ /* post request */ + /* allocating post data structure */ + tmp_connection.protocol.http.post_data = mem_alloc(sizeof(struct post_data_t)); + if(!tmp_connection.protocol.http.post_data){ + output_handler = &http_404_handler; + break; + } + tmp_connection.protocol.http.post_data->content_type = -1; + tmp_connection.protocol.http.post_data->content_length = -1; + tmp_connection.protocol.http.post_data->boundary = NULL; + tmp_connection.protocol.http.post_data->post_data = NULL; + tmp_connection.protocol.http.post_data->filename = NULL; + tmp_connection.protocol.http.post_data->coroutine.curr_context.status = cr_terminated; + } +#endif + tmp_connection.protocol.http.parsing_state = parsing_url; + blob = urls_tree; + } else { + if(tmp_char == ' ') { #ifndef DISABLE_POST - if(tmp_connection.protocol.http.post_data && tmp_connection.protocol.http.post_url_detected == 0) { /* post unauthorized */ - output_handler = &http_404_handler; - break; - } - else{ -#endif - if(!output_handler) - output_handler = (struct output_handler_t*)CONST_ADDR(resources_index[blob_curr - 128]); + if(tmp_connection.protocol.http.post_data && tmp_connection.protocol.http.post_url_detected == 0) { /* post unauthorized */ + output_handler = &http_404_handler; + break; + } + else{ +#endif + if(!output_handler) { + output_handler = (struct output_handler_t*)CONST_ADDR(resources_index[blob_curr - 128]); + tmp_blob = blob_curr - 128; + } #ifndef DISABLE_POST - if(tmp_connection.protocol.http.post_data){ - tmp_connection.protocol.http.parsing_state = parsing_post_attributes; - blob = blob_http_header_content; - tmp_connection.protocol.http.ready_to_send = 0; - } - else{ -#endif - tmp_connection.protocol.http.parsing_state = parsing_end; - break; + if(tmp_connection.protocol.http.post_data){ + tmp_connection.protocol.http.parsing_state = parsing_post_attributes; + blob = blob_http_header_content; + tmp_connection.protocol.http.ready_to_send = 0; + } + else{ +#endif + tmp_connection.protocol.http.parsing_state = parsing_end; + break; #ifndef DISABLE_POST - } - } + } + } #endif - } else { + } else { #ifndef DISABLE_ARGS - if(tmp_char == '?' + if(tmp_char == '?' #ifndef DISABLE_POST - || (tmp_connection.protocol.http.parsing_state == parsing_post_args && tmp_char == 10) -#endif - ){ - uint16_t tmp_args_size; - if(!output_handler) - output_handler = (struct output_handler_t*)CONST_ADDR(resources_index[blob_curr - 128]); - tmp_args_size = CONST_UI16(output_handler->handler_args.args_size); - tmp_args_size_ref = tmp_args_size; - tmp_connection.protocol.http.ready_to_send = 0; - if(tmp_args_size) { - uint16_t i; - blob = (const unsigned char *)CONST_ADDR(output_handler->handler_args.args_tree); - tmp_connection.protocol.http.args = mem_alloc(tmp_args_size); /* test NULL: done */ - if(tmp_connection.protocol.http.args == NULL) { - output_handler = &http_404_handler; - break; - } - for(i =0 ; i < tmp_args_size ; ++i) { - ((unsigned char *)tmp_connection.protocol.http.args)[i] = 0; - } - continue; - } - } else if(tmp_char == '=' && tmp_connection.protocol.http.args) { - struct arg_ref_t * /*CONST_VAR*/ tmp_arg_ref_ptr; - tmp_connection.protocol.http.arg_ref_index = blob_curr - 128; - tmp_arg_ref_ptr = &(((struct arg_ref_t*)CONST_ADDR(output_handler->handler_args.args_index))[tmp_connection.protocol.http.arg_ref_index]); - tmp_arg_ref.arg_type = CONST_UI8(tmp_arg_ref_ptr->arg_type); - tmp_arg_ref.arg_size = CONST_UI8(tmp_arg_ref_ptr->arg_size); - tmp_arg_ref.arg_offset = CONST_UI8(tmp_arg_ref_ptr->arg_offset); - tmp_connection.protocol.http.curr_arg = ((unsigned char*)tmp_connection.protocol.http.args) + tmp_arg_ref.arg_offset; - if(tmp_arg_ref.arg_type == arg_str) - (*((unsigned char*)tmp_connection.protocol.http.curr_arg + tmp_arg_ref.arg_size - 1)) = tmp_arg_ref.arg_size - 1; - continue; - } else if(tmp_char == '&') { - blob = (const unsigned char *)CONST_ADDR(output_handler->handler_args.args_tree); - } else { - blob++; - } + || (tmp_connection.protocol.http.parsing_state == parsing_post_args && tmp_char == 10) +#endif + ){ + uint16_t tmp_args_size; + if(!output_handler) { + output_handler = (struct output_handler_t*)CONST_ADDR(resources_index[blob_curr - 128]); + tmp_blob = blob_curr - 128; + } + tmp_args_size = CONST_UI16(output_handler->handler_args.args_size); + tmp_args_size_ref = tmp_args_size; + tmp_connection.protocol.http.ready_to_send = 0; + if(tmp_args_size) { + uint16_t i; + blob = (const unsigned char *)CONST_ADDR(output_handler->handler_args.args_tree); + tmp_connection.protocol.http.args = mem_alloc(tmp_args_size); /* test NULL: done */ + if(tmp_connection.protocol.http.args == NULL) { + output_handler = &http_404_handler; + break; + } + for(i =0 ; i < tmp_args_size ; ++i) { + ((unsigned char *)tmp_connection.protocol.http.args)[i] = 0; + } + continue; + } + } else if(tmp_char == '=' && tmp_connection.protocol.http.args) { + struct arg_ref_t * /*CONST_VAR*/ tmp_arg_ref_ptr; + tmp_connection.protocol.http.arg_ref_index = blob_curr - 128; + tmp_arg_ref_ptr = &(((struct arg_ref_t*)CONST_ADDR(output_handler->handler_args.args_index))[tmp_connection.protocol.http.arg_ref_index]); + tmp_arg_ref.arg_type = CONST_UI8(tmp_arg_ref_ptr->arg_type); + tmp_arg_ref.arg_size = CONST_UI8(tmp_arg_ref_ptr->arg_size); + tmp_arg_ref.arg_offset = CONST_UI8(tmp_arg_ref_ptr->arg_offset); + tmp_connection.protocol.http.curr_arg = ((unsigned char*)tmp_connection.protocol.http.args) + tmp_arg_ref.arg_offset; + if(tmp_arg_ref.arg_type == arg_str) + (*((unsigned char*)tmp_connection.protocol.http.curr_arg + tmp_arg_ref.arg_size - 1)) = tmp_arg_ref.arg_size - 1; + continue; + } else if(tmp_char == '&') { + blob = (const unsigned char *)CONST_ADDR(output_handler->handler_args.args_tree); + } else { + blob++; + } #else - blob++; + blob++; #endif - } - } - blob_curr = CONST_READ_UI8(blob); - } + } + } + blob_curr = CONST_READ_UI8(blob); + } #ifndef DISABLE_POST - /* end header detection */ - if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && tmp_char == 13){ - blob = blob_http_header_end; - tmp_connection.protocol.http.parsing_state = parsing_post_end; - } - else if(tmp_connection.protocol.http.parsing_state == parsing_post_end){ - if(tmp_char != blob_curr){ - blob = blob_http_header_content; - tmp_connection.protocol.http.parsing_state = parsing_post_attributes; - } - } + /* end header detection */ + if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && tmp_char == 13){ + blob = blob_http_header_end; + tmp_connection.protocol.http.parsing_state = parsing_post_end; + } + else if(tmp_connection.protocol.http.parsing_state == parsing_post_end){ + if(tmp_char != blob_curr){ + blob = blob_http_header_content; + tmp_connection.protocol.http.parsing_state = parsing_post_attributes; + } + } #endif #ifndef DISABLE_ARGS - if(tmp_connection.protocol.http.arg_ref_index != 128) { - if(tmp_char == '&') { - tmp_connection.protocol.http.arg_ref_index = 128; - blob = (const unsigned char *)CONST_ADDR(output_handler->handler_args.args_tree); - continue; - } else if(tmp_char == ' ') { - tmp_connection.protocol.http.ready_to_send = 1; - tmp_connection.protocol.http.parsing_state = parsing_end; - break; - } else { - switch(tmp_arg_ref.arg_type) { - case arg_str: { - unsigned char *tmp_size_ptr = ((unsigned char*)tmp_connection.protocol.http.curr_arg + tmp_arg_ref.arg_size - 1); - if(*tmp_size_ptr) { - *((unsigned char*)tmp_connection.protocol.http.curr_arg + (tmp_arg_ref.arg_size - *tmp_size_ptr - 1)) = tmp_char; - (*tmp_size_ptr)--; - } - break; - } - case arg_ui8: - *((unsigned char*)tmp_connection.protocol.http.curr_arg) *= 10; - *((unsigned char*)tmp_connection.protocol.http.curr_arg) += tmp_char - '0'; - break; - case arg_ui16: - *((uint16_t*)tmp_connection.protocol.http.curr_arg) *= 10; - *((uint16_t*)tmp_connection.protocol.http.curr_arg) += tmp_char - '0'; - break; - } - } - } else + if(tmp_connection.protocol.http.arg_ref_index != 128) { + if(tmp_char == '&') { + tmp_connection.protocol.http.arg_ref_index = 128; + blob = (const unsigned char *)CONST_ADDR(output_handler->handler_args.args_tree); + continue; + } else if(tmp_char == ' ') { + tmp_connection.protocol.http.ready_to_send = 1; + tmp_connection.protocol.http.parsing_state = parsing_end; + break; + } else { + switch(tmp_arg_ref.arg_type) { + case arg_str: { + unsigned char *tmp_size_ptr = ((unsigned char*)tmp_connection.protocol.http.curr_arg + tmp_arg_ref.arg_size - 1); + if(*tmp_size_ptr) { + *((unsigned char*)tmp_connection.protocol.http.curr_arg + (tmp_arg_ref.arg_size - *tmp_size_ptr - 1)) = tmp_char; + (*tmp_size_ptr)--; + } + break; + } + case arg_ui8: + *((unsigned char*)tmp_connection.protocol.http.curr_arg) *= 10; + *((unsigned char*)tmp_connection.protocol.http.curr_arg) += tmp_char - '0'; + break; + case arg_ui16: + *((uint16_t*)tmp_connection.protocol.http.curr_arg) *= 10; + *((uint16_t*)tmp_connection.protocol.http.curr_arg) += tmp_char - '0'; + break; + } + } + } else #endif #ifndef DISABLE_POST - if(tmp_connection.protocol.http.parsing_state == parsing_post_end && blob_curr != HEADER_POST_END){ - blob++; - } - else if( - !(tmp_connection.protocol.http.parsing_state == parsing_post_content_type && (tmp_char == 32 || tmp_char == 58)) - && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_CONTENT_45_LENGTH + 128) - && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_BOUNDARY_61_ + 128) - && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_FILENAME_61_ + 128) - ) -#endif - { - do { - - unsigned char offsetInf = 0; - unsigned char offsetEq = 0; - unsigned char blob_next; + if(tmp_connection.protocol.http.parsing_state == parsing_post_end && blob_curr != HEADER_POST_END){ + blob++; + } + else if( + !(tmp_connection.protocol.http.parsing_state == parsing_post_content_type && (tmp_char == 32 || tmp_char == 58)) + && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_CONTENT_45_LENGTH + 128) + && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_BOUNDARY_61_ + 128) + && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_FILENAME_61_ + 128) + ) +#endif + { + do { + + unsigned char offsetInf = 0; + unsigned char offsetEq = 0; + unsigned char blob_next; #ifndef DISABLE_POST - if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && tmp_char < 123 && tmp_char > 96) - tmp_char = tmp_char - 'a' + 'A'; -#endif - blob_curr = CONST_READ_UI8(blob); - blob_next = CONST_READ_UI8(++blob); - if (tmp_char != blob_curr && blob_next >= 128) { - blob_next = CONST_READ_UI8(++blob); - } - if (blob_next < 32) { - offsetInf += ((blob_next>>2) & 1) + ((blob_next>>1) & 1) + (blob_next & 1); - offsetEq = offsetInf + ((blob_next & 2)?CONST_READ_UI8(blob+1):0); - } - if (tmp_char == blob_curr) { - if (blob_next < 32) { - if (blob_next & 2) { - blob += offsetEq; - } else { + if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && tmp_char < 123 && tmp_char > 96) + tmp_char = tmp_char - 'a' + 'A'; +#endif + blob_curr = CONST_READ_UI8(blob); + blob_next = CONST_READ_UI8(++blob); + if (tmp_char != blob_curr && blob_next >= 128) { + blob_next = CONST_READ_UI8(++blob); + } + if (blob_next < 32) { + offsetInf += ((blob_next>>2) & 1) + ((blob_next>>1) & 1) + (blob_next & 1); + offsetEq = offsetInf + ((blob_next & 2)?CONST_READ_UI8(blob+1):0); + } + if (tmp_char == blob_curr) { + if (blob_next < 32) { + if (blob_next & 2) { + blob += offsetEq; + } else { #ifndef DISABLE_POST - if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes) - blob = blob_http_header_content; - else -#endif - output_handler = &http_404_handler; - break; - } - } - break; - } else if (tmp_char < blob_curr) { - if (blob_next < 32 && blob_next & 1) { - blob += offsetInf; - } else { + if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes) + blob = blob_http_header_content; + else +#endif + output_handler = &http_404_handler; + break; + } + } + break; + } else if (tmp_char < blob_curr) { + if (blob_next < 32 && blob_next & 1) { + blob += offsetInf; + } else { #ifndef DISABLE_POST - if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes) - blob = blob_http_header_content; - else -#endif - output_handler = &http_404_handler; - break; - } - } else { - if (blob_next < 32 && blob_next & 4) { - unsigned char offsetSup = offsetEq + ((blob_next & 3)?CONST_READ_UI8(blob+(offsetInf-1)):0); - blob += offsetSup; - } else { + if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes) + blob = blob_http_header_content; + else +#endif + output_handler = &http_404_handler; + break; + } + } else { + if (blob_next < 32 && blob_next & 4) { + unsigned char offsetSup = offsetEq + ((blob_next & 3)?CONST_READ_UI8(blob+(offsetInf-1)):0); + blob += offsetSup; + } else { #ifndef DISABLE_POST - if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes) - blob = blob_http_header_content; - else -#endif - output_handler = &http_404_handler; - break; - } - } - } while(1); - } - } - /* detecting parsing_end */ - if( + if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes) + blob = blob_http_header_content; + else +#endif + output_handler = &http_404_handler; + break; + } + } + } while(1); + } + } + /* detecting parsing_end */ + if( #ifndef DISABLE_POST - (tmp_connection.protocol.http.parsing_state == parsing_post_args && !tmp_connection.protocol.http.post_data->content_length) || + (tmp_connection.protocol.http.parsing_state == parsing_post_args && !tmp_connection.protocol.http.post_data->content_length) || #endif - ((output_handler == &http_404_handler + ((output_handler == &http_404_handler #ifndef DISABLE_POST - || output_handler == &http_505_handler -#endif - ) && tmp_connection.protocol.http.parsing_state != parsing_cmd)){ - tmp_connection.protocol.http.parsing_state = parsing_end; - tmp_connection.protocol.http.ready_to_send = 1; - } - if(!output_handler) - tmp_connection.protocol.http.blob = blob; - else { - if(tmp_connection.protocol.http.parsing_state != parsing_cmd) { - tmp_connection.output_handler = output_handler; - UI32(tmp_connection.protocol.http.next_outseqno) = UI32(current_inack); - if(CONST_UI8(output_handler->handler_type) == type_file) { - UI32(tmp_connection.protocol.http.final_outseqno) = UI32(tmp_connection.protocol.http.next_outseqno) + CONST_UI32(GET_FILE(output_handler).length); - } else { - UI32(tmp_connection.protocol.http.final_outseqno) = UI32(tmp_connection.protocol.http.next_outseqno) - 1; - } + || output_handler == &http_505_handler +#endif + ) && tmp_connection.protocol.http.parsing_state != parsing_cmd)){ + tmp_connection.protocol.http.parsing_state = parsing_end; + tmp_connection.protocol.http.ready_to_send = 1; + } + if(!output_handler) + tmp_connection.protocol.http.blob = blob; + else { + +#ifdef HTTP_AUTH + /* HTTP-Auth: If the requested handler is restricted, we compare the + * given credentials with expected ones. In case of failure, the + * output handler will be set on 401 handler, and requested resource + * index (blob - 128) will be stored into the blob field of the struct. */ + if (IS_RESTRICTED(output_handler)) { + tmp_connection.protocol.http.blob = (unsigned char*)(int)tmp_blob; + output_handler = &http_401_handler; + } +#endif + + if (tmp_connection.protocol.http.parsing_state != parsing_cmd) { + tmp_connection.output_handler = output_handler; + UI32(tmp_connection.protocol.http.next_outseqno) = UI32(current_inack); + + if (CONST_UI8(output_handler->handler_type) == type_file) { + +#ifdef HTTP_AUTH + /* HTTP-Auth: Here, we need to compute the full length of the file, + * including the missing header that we dynamically generate. To do + * so, we need to pick the length of this header, add it the length + * of realm (and nonce if digest auth), before getting the file's blob + * size. */ + if (output_handler == &http_401_handler) { + UI32(tmp_connection.protocol.http.final_outseqno) = + UI32(tmp_connection.protocol.http.next_outseqno) + + http_401_handler_length((struct output_handler_t*)tmp_connection.protocol.http.blob); + } + else { + UI32(tmp_connection.protocol.http.final_outseqno) = + UI32(tmp_connection.protocol.http.next_outseqno) + + CONST_UI32(GET_FILE(output_handler).length); + } +#else + UI32(tmp_connection.protocol.http.final_outseqno) = + UI32(tmp_connection.protocol.http.next_outseqno) + + CONST_UI32(GET_FILE(output_handler).length); +#endif + + } + else { + UI32(tmp_connection.protocol.http.final_outseqno) = UI32(tmp_connection.protocol.http.next_outseqno) - 1; + } #ifndef DISABLE_COMET - tmp_connection.protocol.http.comet_send_ack = CONST_UI8(output_handler->handler_comet) ? 1 : 0; - tmp_connection.protocol.http.comet_passive = 0; - tmp_connection.protocol.http.comet_streaming = 0; + tmp_connection.protocol.http.comet_send_ack = CONST_UI8(output_handler->handler_comet) ? 1 : 0; + tmp_connection.protocol.http.comet_passive = 0; + tmp_connection.protocol.http.comet_streaming = 0; #endif - } - if(tmp_connection.protocol.http.parsing_state == parsing_end || tmp_connection.protocol.http.parsing_state == parsing_cmd){ + } + if(tmp_connection.protocol.http.parsing_state == parsing_end || tmp_connection.protocol.http.parsing_state == parsing_cmd){ #ifndef DISABLE_POST - /* cleaning memory */ - if(tmp_connection.protocol.http.post_data){ /* Warning : don't clean post_data, useful in output function (unless error 404 or 505) */ - if(tmp_connection.protocol.http.post_data->filename){ - uint8_t i = 0; - while(tmp_connection.protocol.http.post_data->filename[i++] != '\0'); - mem_free(tmp_connection.protocol.http.post_data->filename,i*sizeof(char)); - tmp_connection.protocol.http.post_data->filename = NULL; - } - if(tmp_connection.protocol.http.post_data->boundary){ - if(tmp_connection.protocol.http.post_data->boundary->boundary_ref && tmp_connection.protocol.http.post_data->content_length != (uint16_t)-1) - mem_free(tmp_connection.protocol.http.post_data->boundary->boundary_ref,tmp_connection.protocol.http.post_data->boundary->boundary_size*sizeof(char)); - if(tmp_connection.protocol.http.post_data->boundary->boundary_buffer) - mem_free(tmp_connection.protocol.http.post_data->boundary->boundary_buffer,(tmp_connection.protocol.http.post_data->boundary->boundary_size)*sizeof(char)); - mem_free(tmp_connection.protocol.http.post_data->boundary,sizeof(struct boundary_t)); - } - tmp_connection.protocol.http.post_data->boundary = NULL; - } - if(output_handler == &http_404_handler + /* cleaning memory */ + if(tmp_connection.protocol.http.post_data){ /* Warning : don't clean post_data, useful in output function (unless error 404 or 505) */ + if(tmp_connection.protocol.http.post_data->filename){ + uint8_t i = 0; + while(tmp_connection.protocol.http.post_data->filename[i++] != '\0'); + mem_free(tmp_connection.protocol.http.post_data->filename,i*sizeof(char)); + tmp_connection.protocol.http.post_data->filename = NULL; + } + if(tmp_connection.protocol.http.post_data->boundary){ + if(tmp_connection.protocol.http.post_data->boundary->boundary_ref && tmp_connection.protocol.http.post_data->content_length != (uint16_t)-1) + mem_free(tmp_connection.protocol.http.post_data->boundary->boundary_ref,tmp_connection.protocol.http.post_data->boundary->boundary_size*sizeof(char)); + if(tmp_connection.protocol.http.post_data->boundary->boundary_buffer) + mem_free(tmp_connection.protocol.http.post_data->boundary->boundary_buffer,(tmp_connection.protocol.http.post_data->boundary->boundary_size)*sizeof(char)); + mem_free(tmp_connection.protocol.http.post_data->boundary,sizeof(struct boundary_t)); + } + tmp_connection.protocol.http.post_data->boundary = NULL; + } + if(output_handler == &http_404_handler #ifndef DISABLE_POST - || output_handler == &http_505_handler -#endif - ){ - /* cleaning post_data if error */ - if(tmp_connection.protocol.http.post_data){ - mem_free(tmp_connection.protocol.http.post_data,sizeof(struct post_data_t)); - tmp_connection.protocol.http.post_data = NULL; - } + || output_handler == &http_505_handler +#endif + ){ + /* cleaning post_data if error */ + if(tmp_connection.protocol.http.post_data){ + mem_free(tmp_connection.protocol.http.post_data,sizeof(struct post_data_t)); + tmp_connection.protocol.http.post_data = NULL; + } #ifndef DISABLE_ARGS - /* cleaning args data if error */ - if(tmp_connection.protocol.http.args){ - mem_free(tmp_connection.protocol.http.args,tmp_args_size_ref); - tmp_connection.protocol.http.args = NULL; - } + /* cleaning args data if error */ + if(tmp_connection.protocol.http.args){ + mem_free(tmp_connection.protocol.http.args,tmp_args_size_ref); + tmp_connection.protocol.http.args = NULL; + } #endif - } + } #endif - tmp_connection.protocol.http.parsing_state = parsing_out; - tmp_connection.protocol.http.blob = blob_http_rqt; + tmp_connection.protocol.http.parsing_state = parsing_out; + +#ifdef HTTP_AUTH + if (output_handler != &http_401_handler) + tmp_connection.protocol.http.blob = blob_http_rqt; +#else + tmp_connection.protocol.http.blob = blob_http_rqt; +#endif + #ifndef DISABLE_ARGS - tmp_connection.protocol.http.arg_ref_index = 128; + tmp_connection.protocol.http.arg_ref_index = 128; #endif - } - else - tmp_connection.protocol.http.blob = blob; - } + } + else + tmp_connection.protocol.http.blob = blob; } - /* drop remaining TCP data */ - while(x++ < segment_length) - DEV_GETC(tmp_char); + } + /* drop remaining TCP data */ + while(x++ < segment_length) + DEV_GETC(tmp_char); - /* acknowledge received and processed TCP data if no there is no current output_handler */ - if(!tmp_connection.output_handler && tmp_connection.protocol.http.tcp_state == tcp_established && segment_length) { - tmp_connection.output_handler = &ref_ack; - } + /* acknowledge received and processed TCP data if no there is no current output_handler */ + if(!tmp_connection.output_handler && tmp_connection.protocol.http.tcp_state == tcp_established && segment_length) { + tmp_connection.output_handler = &ref_ack; + } - /* check TCP checksum using the partially precalculated pseudo header checksum */ - checksum_end(); - if(UI16(current_checksum) == TCP_CHK_CONSTANT_PART) { - if(defer_clean_service) { /* free in-flight segment information for acknowledged segments */ + /* check TCP checksum using the partially precalculated pseudo header checksum */ + checksum_end(); + if(UI16(current_checksum) == TCP_CHK_CONSTANT_PART) { + if(defer_clean_service) { /* free in-flight segment information for acknowledged segments */ #ifdef DISABLE_COROUTINES - DYNAMIC_STATE_CHANGE(ack_received); - /* received ack of the last dynamic segment, dynamic handling is done */ - if (curr_output.dynamic_service_state != none && /* Dynamic content is served */ - tmp_connection.protocol.http.generator_service == curr_output.service && /* This is the currently served dynamic connection */ - !curr_output.in_handler && /* Dynamic handler is finished */ - (curr_output.service_header == header_standard || /* Ack from a single segment data */ - (curr_output.service_header == header_none && curr_output.content_length == 0))) /* or the ack of the last void chunk */ - { - DYNAMIC_STATE_CHANGE(none); - /* When no coroutine, we only have one in_flight_infos. Thus, it has do be freed only at the end */ - clean_service(tmp_connection.protocol.http.generator_service, current_inack); - } + DYNAMIC_STATE_CHANGE(ack_received); + /* received ack of the last dynamic segment, dynamic handling is done */ + if (curr_output.dynamic_service_state != none && /* Dynamic content is served */ + tmp_connection.protocol.http.generator_service == curr_output.service && /* This is the currently served dynamic connection */ + !curr_output.in_handler && /* Dynamic handler is finished */ + (curr_output.service_header == header_standard || /* Ack from a single segment data */ + (curr_output.service_header == header_none && curr_output.content_length == 0))) /* or the ack of the last void chunk */ + { + DYNAMIC_STATE_CHANGE(none); + /* When no coroutine, we only have one in_flight_infos. Thus, it has do be freed only at the end */ + clean_service(tmp_connection.protocol.http.generator_service, current_inack); + } #else - clean_service(tmp_connection.protocol.http.generator_service, current_inack); + clean_service(tmp_connection.protocol.http.generator_service, current_inack); #endif - if(defer_free_handler) { /* free handler and generator service if the service is completely acknowledged */ + if(defer_free_handler) { /* free handler and generator service if the service is completely acknowledged */ #ifndef DISABLE_COROUTINES - cr_clean(&tmp_connection.protocol.http.generator_service->coroutine); + cr_clean(&tmp_connection.protocol.http.generator_service->coroutine); #endif - mem_free(tmp_connection.protocol.http.generator_service, sizeof(struct generator_service_t)); - tmp_connection.protocol.http.generator_service = NULL; + mem_free(tmp_connection.protocol.http.generator_service, sizeof(struct generator_service_t)); + tmp_connection.protocol.http.generator_service = NULL; #ifndef DISABLE_ARGS - mem_free(defer_free_args, defer_free_args_size); + mem_free(defer_free_args, defer_free_args_size); #endif - } - } + } + } - if(!connection && tmp_connection.protocol.http.tcp_state == tcp_syn_rcvd) { - connection = add_connection(&tmp_connection + if(!connection && tmp_connection.protocol.http.tcp_state == tcp_syn_rcvd) { + connection = add_connection(&tmp_connection #ifdef IPV6 - , compressed_ip_size(comp_ipv6_addr) -#endif - ); - /* update the pointer in the tmp_connection because - * it will be copied later so if the pointers do not have the right value, the list - * will be screwed */ - if (connection) - { - tmp_connection.prev = connection->prev; - tmp_connection.next = connection->next; - } + , compressed_ip_size(comp_ipv6_addr) +#endif + ); + /* update the pointer in the tmp_connection because + * it will be copied later so if the pointers do not have the right value, the list + * will be screwed */ + if (connection) + { + tmp_connection.prev = connection->prev; + tmp_connection.next = connection->next; } + } - if(!connection) { - /* no valid connection has been found for this packet, send a reset */ - UI32(tmp_connection.protocol.http.next_outseqno) = UI32(current_inack); + if(!connection) { + /* no valid connection has been found for this packet, send a reset */ + UI32(tmp_connection.protocol.http.next_outseqno) = UI32(current_inack); #ifdef IPV6 - memcpy(rst_connection.ip_addr, full_ipv6_addr, 16); + memcpy(rst_connection.ip_addr, full_ipv6_addr, 16); #else - UI32(rst_connection.ip_addr) = UI32(tmp_connection.ip_addr); + UI32(rst_connection.ip_addr) = UI32(tmp_connection.ip_addr); #endif - UI16(rst_connection.port) = UI16(tmp_connection.protocol.http.port); - UI32(rst_connection.current_inseqno) = UI32(tmp_connection.protocol.http.current_inseqno); - UI32(rst_connection.next_outseqno) = UI32(tmp_connection.protocol.http.next_outseqno); - } else { - if(tmp_connection.protocol.http.tcp_state == tcp_listen) { + UI16(rst_connection.port) = UI16(tmp_connection.protocol.http.port); + UI32(rst_connection.current_inseqno) = UI32(tmp_connection.protocol.http.current_inseqno); + UI32(rst_connection.next_outseqno) = UI32(tmp_connection.protocol.http.next_outseqno); + } else { + if(tmp_connection.protocol.http.tcp_state == tcp_listen) { #ifdef DISABLE_COROUTINES - if (curr_output.dynamic_service_state != none && tmp_connection.protocol.http.generator_service == curr_output.service) - { - if (curr_output.in_handler) - { - DYNAMIC_STATE_CHANGE(connection_terminated); /* If we are still in the handler, we have to notify it of the end of the connection */ - } - else - DYNAMIC_STATE_CHANGE(none); /* Otherwise, the connection was closed while handling last data segment or last chunk, thus can be fully discarded. - Turn the state to not serving dynamic */ - } + if (curr_output.dynamic_service_state != none && tmp_connection.protocol.http.generator_service == curr_output.service) + { + if (curr_output.in_handler) + { + DYNAMIC_STATE_CHANGE(connection_terminated); /* If we are still in the handler, we have to notify it of the end of the connection */ + } + else + DYNAMIC_STATE_CHANGE(none); /* Otherwise, the connection was closed while handling last data segment or last chunk, thus can be fully discarded. + Turn the state to not serving dynamic */ + } +#endif + free_connection(connection); + } else { + /* update the current connection */ + *connection = tmp_connection; +#ifdef HTTP_AUTH + if (tmp_connection.output_handler == &http_401_handler) + tmp_connection.protocol.http.blob = blob_http_rqt; #endif - free_connection(connection); - } else { - /* update the current connection */ - *connection = tmp_connection; + #ifdef IPV6 - copy_compressed_ip(connection->ip_addr, comp_ipv6_addr); + copy_compressed_ip(connection->ip_addr, comp_ipv6_addr); #endif - } - } + } } - return 1; + } + return 1; } diff --git a/core/output.c b/core/output.c index 74b064c..79c9718 100644 --- a/core/output.c +++ b/core/output.c @@ -43,10 +43,9 @@ #include "memory.h" #include "input.h" /* for *_HEADER_SIZE defines */ #include "handlers.h" - -#ifndef DISABLE_POST #include "defines.h" -#endif +#include "auth.h" + /* IPV6 Common values */ #ifdef IPV6 @@ -101,7 +100,6 @@ static CONST_VAR (char, serviceHttpHeaderChunked[]) = "HTTP/1.1 200 OK\r\nConten #define MIN(a,b) ((a) < (b) ? (a) : (b)) - struct curr_output_t curr_output; /* default DEV_PUT16 */ @@ -312,7 +310,17 @@ void smews_send_packet (struct connection *connection) max_out_size = MAX_OUT_SIZE (connection->protocol.http.tcp_mss); file_remaining_bytes = UI32 (connection->protocol.http.final_outseqno) - UI32 (next_outseqno); segment_length = file_remaining_bytes > max_out_size ? max_out_size : file_remaining_bytes; + +#ifdef HTTP_AUTH + if (output_handler == &http_401_handler) + index_in_file = + http_401_handler_length ((struct output_handler_t*)connection->protocol.http.blob) + - file_remaining_bytes; + else + index_in_file = CONST_UI32 (GET_FILE (output_handler).length) - file_remaining_bytes; +#else index_in_file = CONST_UI32 (GET_FILE (output_handler).length) - file_remaining_bytes; +#endif break; } case type_generator: @@ -538,10 +546,51 @@ void smews_send_packet (struct connection *connection) { uint16_t i; uint32_t tmp_sum = 0; + + /* TODO: (bacara) Beware of bugs with index_in_file */ uint16_t *tmpptr = (uint16_t *) CONST_ADDR (GET_FILE (output_handler).chk) + DIV_BY_CHUNCKS_SIZE (index_in_file); +#ifdef HTTP_AUTH + if (output_handler == &http_401_handler) { + /* Getting the realm */ + unsigned const char *char_p = + ((struct output_handler_t*)connection->protocol.http.blob)->handler_restriction.realm; + +#if HTTP_AUTH == HTTP_AUTH_BASIC + /* Precalculated checksum */ + checksum_add16 (HTTP_AUTHENTICATE_HEADER_CHK); + + /* Checksum the double quoted realm */ + checksum_add ('\"'); + while (*char_p) + checksum_add (*char_p++); + checksum_add ('\"'); + +#elif HTTP_AUTH == HTTP_AUTH_DIGEST + /* Precalculated checksum */ + checksum_add16 (HTTP_AUTHENTICATE_HEADER_CHK); + + /* Checksum the double quoted realm */ + checksum_add ('\"'); + while (*char_p) + checksum_add (*char_p++); + checksum_add ('\"'); + + /* Precalculated checksum */ + checksum_add16 (HTTP_AUTHENTICATE_HEADER_NONCE_CHK); + + /* Checksum the double quoted nonce */ + char_p = http_auth_nonce; + checksum_add ('\"'); + while (*char_p) + checksum_add (*char_p++); + checksum_add ('\"'); +#endif + } +#endif + for (i = 0; i < GET_NB_BLOCKS (segment_length); i++) { tmp_sum += CONST_READ_UI16 (tmpptr++); @@ -597,12 +646,46 @@ void smews_send_packet (struct connection *connection) break; case type_file: { - /* Send the payload of the packet */ - const char *tmpptr = - (const char *) (CONST_ADDR (GET_FILE (output_handler).data) + - index_in_file); - DEV_PUTN_CONST (tmpptr, segment_length); - break; + /* Send the payload of the packet */ + unsigned const char *tmpptr; + +#ifdef HTTP_AUTH + uint8_t tmp; + + /* Sending the http header */ + DEV_PUTN_CONST (HttpAuthenticateHeader, HTTP_AUTHENTICATE_HEADER_LEN); + + /* Sending the double quoted realm */ + tmpptr = + ((struct output_handler_t*)(connection->protocol.http.blob))->handler_restriction.realm; + tmp = 0; + while (*tmpptr++) + ++tmp; + + DEV_PUT ('\"'); + DEV_PUTN_CONST (tmpptr, tmp); + DEV_PUT ('\"'); + +#if HTTP_AUTH != HTTP_AUTH_BASIC + /* Sending the second part of the http header */ + DEV_PUTN_CONST (HttpAuthenticateHeaderNonce, HTTP_AUTHENTICATE_HEADER_NONCE_LEN); + + /* Sending the double quoted nonce */ + tmpptr = http_auth_nonce; + tmp = 0; + while (*tmpptr++) + ++tmp; + + DEV_PUT ('\"'); + DEV_PUTN (http_auth_nonce, sizeof (http_auth_nonce)); + DEV_PUT ('\"'); +#endif +#endif + + tmpptr = (unsigned const char*)(CONST_ADDR (GET_FILE (output_handler).data) + index_in_file); + + DEV_PUTN_CONST (tmpptr, segment_length); + break; } default: /* Should never happen but avoid warnings */ return; diff --git a/doc/userguide.pdf b/doc/userguide.pdf index 8e8ff282a2f7c314bd33a251ecd091604ebae92c..29982cb78b2418904a03b515a2609e0d6283f10e 100644 GIT binary patch delta 138666 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%0D^R>dD=9N%bpzm~(o~klX%6CshaYw2L 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 b{LRm?70P|u7lSsP)Wd1T0J!`_N&)-*M0 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 z zYN8q?-B>K#krfu>zwLkk0;aVxEwlQKkB#P}kwJW9NeGWjpM=&QN+|jtig8@_)lVd% zFL8T}m0+jCd86qdk&15fh;3-e^N7cX%qxcf1)-NDoEC{y3?iF#156qsty0AkgA};a zA|rvbN?DD68-9o$X3z1xJp1v{Tf`&6$24B{h)GB{hIJ_`r@yCECIni={H&B8OFd82v0BMTa=ObgqGR`p5xzYRY+&TbeM z+m4L!IPchFRI_dfl?K#d&S+?H;q_RSNY`E|(fE_mA{hyg1GLY>s+>mS7^BhRioCI6 zg+=9jPZ#!IBf7Eyxaq@+r_A^c5}%eVde4OKo6l+2{|MK!gGA8GfdI|abZ7=TDAX^T z+t7CCC8Q(c50Oif@+d6v4)r*MLLPHI@R**T!A{&fHa}1ylB~d0zFu{6@lXlz>NyZe zF@{;MgGSJXm5nP4$L|qwXLS(SQMId0|5Jcs-Xx%zMuX1E`pgyM=gGZeC(o)GW#e_? zu7SjV0H9_~1)Avr&^Yp(D^@hQ(c+50t`h3t*k)ciNRKt2|0SaVkI>LOb0vrhtC&_> z0|BqR?0%6=)BhCbY7+#ItLf>RkYe++^{&9OyzDq>oy~U?E(0jJ@X%eU&^i}sm4xU* zSwSTq>TMnwU_u)O{Bbr30o<-h|3l7LoMy-#`=D_5TrpaSRU(Aa@(~l!XjT6sZai++ z93~AoVb<4p;l#Wn(CmN;w0Nc^ea{8gorn##Am!UPA`E7mNC;8u9ZowFG)QHWAKV6! z^?n7@%B2(g|2DY@7t7iZ8Hsnw4FqD(!-`pw{Q|5dJJ0~FS4xWh2XW9!G#b$C2(@S# z-Gs&(+Z*`qyVqZR{pPzDAAkJvqu+e>-*4hpx{rSS_U$`<(UF(Ge(}-Yee;L6{6(p+ z;?FN${`O-Q^ySmrXU8vYKfitP`R&tB&%gM^XP=)hpYrv~=bwG{$;+jyhjd(O|Sxzs0@{^zc>K8Bm9}v(rJOBUy delta 46183 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 0): attributList.append(split[0]) +for line in open(httpAuthTypeListFileName, 'r'): + indice = line.find('#') + if indice >= 0: + line = line[:indice] + if (len(line) > 0): + httpAuthTypeList.append(line) + def generateBlobsH(dstFile): hOut = open(dstFile,'w') writeHeader(hOut,0) @@ -136,6 +145,16 @@ def writeDefinesH(dstFile): att = att.replace(c,'_'+str(ord(c))+'_') hOut.write('#define \t ATTRIBUT_' + att + '\t' + str(attributList.index(m)) + '\n') + # HTTP_AUTH_BASIC & HTTP_AUTH_DIGEST definitions + value = 0 + for m in httpAuthTypeList: + type = m.upper() + for c in type: + if (re.search('\W', c)): + type = type.replace(c, '_' + str(ord(c)) + '_') + hOut.write('#define \t HTTP_AUTH_' + type + '\t' + str(value) + '\n') + value += 1 + hOut.write('\n#endif\n') hOut.close() @@ -367,14 +386,18 @@ def generateResourceProps(srcFile,dstFileInfos): pOut.close() # launches a Web applicative resource file generation -def generateResource(srcFile,dstFile,chuncksNbits,gzipped,dstFileInfos): +def generateResource(srcFile,dstFile,chuncksNbits,gzipped,dstFileInfos,realm,offsetCountCouple): if getResourceType(srcFile) == DynamicResource: - generateDynamicResource(srcFile,dstFile,dstFileInfos) + generateDynamicResource(srcFile,dstFile,dstFileInfos,realm,offsetCountCouple) else: - generateStaticResource(srcFile,dstFile,chuncksNbits,gzipped) + generateStaticResource(srcFile,dstFile,chuncksNbits,gzipped,realm,offsetCountCouple) # dynamic resources: the generator file is enriched -def generateDynamicResource(srcFile,dstFile,dstFileInfos): +def generateDynamicResource(srcFile, + dstFile, + dstFileInfos, + realm, + offsetCountCouple): # extract the properties from the XML (if needed) fileData = extractPropsFromXml(srcFile,dstFileInfos) # if the c/h file does not conatin any XML, we simply copy it: this is not a generator @@ -390,6 +413,9 @@ def generateDynamicResource(srcFile,dstFile,dstFileInfos): generatedHeader += '#include "stddef.h"\n\n' generatedHeader += '#include "defines.h"\n\n' + if realm != '': + generatedHeader += '#include "http_auth_data.h"\n\n' + generatedOutputHandler = '/********** Output handler **********/\n' # handler functions declaration if dstFileInfos.has_key('init'): @@ -513,6 +539,30 @@ def generateDynamicResource(srcFile,dstFile,dstFileInfos): generatedOutputHandler += '\t},\n' generatedOutputHandler += '#endif\n' + + # HTTP Authentication data generation + generatedOutputHandler += '#ifdef HTTP_AUTH\n' + + if realm != '': + realmVar = realm + for c in realmVar: + if (re.search('\W', c)): + realmVar = realmVar.replace(c, '_' + str(ord(c)) + '_') + generatedOutputHandler += '\t.handler_restriction = {\n' + generatedOutputHandler += '\t\t.realm = (unsigned char*)&' + realmVar.lower() + ',\n' + generatedOutputHandler += '\t\t.credentials_offset = ' + str(offsetCountCouple[0]) + ',\n' + generatedOutputHandler += '\t\t.credentials_count = ' + str(offsetCountCouple[1]) + '\n' + generatedOutputHandler += '\t}\n' + else: + generatedOutputHandler += '\t.handler_restriction = {\n' + generatedOutputHandler += '\t\t.realm = NULL,\n' + generatedOutputHandler += '\t\t.credentials_offset = 0,\n' + generatedOutputHandler += '\t\t.credentials_count = 0\n' + generatedOutputHandler += '\t}\n' + + generatedOutputHandler += '#endif\n' + + generatedOutputHandler += '};\n' if (dstFileInfos.has_key('doPost') or dstFileInfos.has_key('doPostIn')): @@ -595,7 +645,7 @@ def generateDynamicResource(srcFile,dstFile,dstFileInfos): # static file: the file is completely pre-processed into a c file: # HTTP header insertion # chuncks checksums calculation -def generateStaticResource(srcFile,dstFile,chuncksNbits,gzipped): +def generateStaticResource(srcFile,dstFile,chuncksNbits,gzipped,realm,offsetCountCouple): # HTTP header generation and concatenation with file data try: # open the source file @@ -627,6 +677,11 @@ def generateStaticResource(srcFile,dstFile,chuncksNbits,gzipped): fileData = 'HTTP/1.1 404 Not Found\r\nContent-Length: ' + fileData elif os.path.basename(srcFile) == '505.html': fileData = 'HTTP/1.1 505 HTTP Version Not Supported\r\nContent-Length: ' + fileData + # bacara: + # HTTP Authentication needs runtime data, such as realm and nonce. At this + # point, we cannot process the full generation for 401.html. + elif os.path.basename(srcFile) == '401.html': + fileData = '\r\nContent-Length: ' + fileData else: fileData = 'HTTP/1.1 200 OK\r\nContent-Length: ' + fileData @@ -654,6 +709,8 @@ def generateStaticResource(srcFile,dstFile,chuncksNbits,gzipped): # inclusions cOut.write('#include "handlers.h"\n') + if realm != '': + cOut.write('#include "http_auth_data.h"\n') # data structures declaration cName = getCName(srcFile) @@ -672,6 +729,26 @@ def generateStaticResource(srcFile,dstFile,chuncksNbits,gzipped): + '\t\t\t.data = data_' + cName + '\n' + '\t\t}\n' + '\t},\n') + + cOut.write('#ifdef HTTP_AUTH\n') + if realm != '': + realmVar = realm + for c in realmVar: + if (re.search('\W', c)): + realmVar = realmVar.replace(c, '_' + str(ord(c)) + '_') + cOut.write('\t.handler_restriction = {\n' + + '\t\t.realm = (unsigned char*)&' + realmVar.lower() + ',\n' + + '\t\t.credentials_offset = ' + str(offsetCountCouple[0]) + ',\n' + + '\t\t.credentials_count = ' + str(offsetCountCouple[1]) + '\n' + + '\t}\n') + else: + cOut.write('\t.handler_restriction = {\n' + + '\t\t.realm = NULL,\n' + + '\t\t.credentials_offset = 0,\n' + + '\t\t.credentials_count = 0\n' + + '\t}\n') + cOut.write('#endif\n') + cOut.write('};\n') # chuncks checksums data generation diff --git a/tools/GenAuthData.py b/tools/GenAuthData.py new file mode 100644 index 0000000..067eb13 --- /dev/null +++ b/tools/GenAuthData.py @@ -0,0 +1,272 @@ +#!/usr/bin/python + +from xml.dom import minidom + +import base64 +import re + +import GenApps + +# -------------------------------------------------- # +# Class: User +# -------------------------------------------------- # +class User: + def __init__(self, username, password): + self.username = username + self.password = password + + def toBasicHTTPAuthString(self): + return base64.b64encode('%s:%s' % (self.username, self.password)) +# -------------------------------------------------- # + +# Script initialisation +authenticationType = 'basic' # 'basic' | 'digest' +realmUrisMap = { } # Read parseAuthData description for details + +# XML Tag Names +rootNodeTag = "http-authentication" +authNodeTag = "authorizations" +restNodeTag = "restrictions" +userNodeTag = "user" +realmNodeTag = "realm" +uriNodeTag = "uri" + +# parseAuthData(xmlFilename): +# Parse authentication data from the corresponding XML file. +def parseAuthData(xmlFilename): + # Getting XML document + xmlDoc = minidom.parse(xmlFilename) + + # Getting root, authorizations, restrictions + rootNodeList = xmlDoc.getElementsByTagName(rootNodeTag) + authNodeList = xmlDoc.getElementsByTagName(authNodeTag) + restNodeList = xmlDoc.getElementsByTagName(restNodeTag) + + if (len(rootNodeList) != 1): + print '%s: Must contain only one node: %s' % (xmlFilename, rootNodeTag) + exit() + if (len(authNodeList) != 1): + print '%s: Must contain only one node: %s' % (xmlFilename, authNodeTag) + exit() + if (len(restNodeList) != 1): + print '%s: Must contain only one node: %s' % (xmlFilename, restNodeTag) + exit() + + rootNode = rootNodeList[0] + authNode = authNodeList[0] + restNode = restNodeList[0] + + # Checking authentication type + authType = rootNode.attributes['type'].value.lower() + + if (authType.lower() == 'basic'): # Basic Authentication + authenticationType = authType + parseBasicHTTPAuthData(restNode, parseUsersList(authNode)) + + elif (authType.lower() == 'digest'): # Digest authentication +# authenticationType = authType + print 'Digest HTTP Authentication Data parsing is not yet supported' + + else: # Not handled + print 'Unknown HTTP authentication type : %s' % (authType) + exit() + + return authType + +# parseUsersList(authorizationNode): +# Parse the authorization node and return a list of User objects corresponding +# to the XML data. +def parseUsersList(authorizationNode): + usernames = [] # Twins checking purpose + usersList = [] + + userNodeList = authorizationNode.getElementsByTagName(userNodeTag) + + # Parsing username and passwd for each user + for userNode in userNodeList: + login = userNode.attributes['login'].value + passwd = userNode.attributes['passwd'].value + + if login in usernames: # Twins check + print '%s: Username is duplicated.' % (login) + exit() + if passwd == '': + print '%s: Password field not set or empty.' % (login) + exit() + + usernames.append(login) + usersList.append(User(login, passwd)) + + return usersList + +# parseBasicHTTPAuthData(realmNodeList, usersList): +# Parse the data corresponding to the specified restriction node, and +# build the data structure according to the previously parsed users list. +# +# The returned data structure looks like this : +# +# val = { 'realm' : [ { 'uri' : [credentials] } ] } +# where credentials is the base 64 encoding of the string "username:passwd" +# . +# |- realm1 +# | |- uri1 +# | | |- credentials1 +# | | `- credentials2 +# | `- uri2 +# | `- credentials3 +# `- realm2 +# `- uri3 +# |- credentials4 +# `- credentials5 +# +def parseBasicHTTPAuthData(restrictionNode, usersList): + global realmUrisMap + realmNodeList = restrictionNode.getElementsByTagName(realmNodeTag) + mapBuilt = { } + + # Parsing each restriction list (1xlist/realm) + for realmNode in realmNodeList: + uriCredentialsMap = { } + realmName = realmNode.attributes['name'].value + + if realmName in mapBuilt: # Twins check + print '%s: Realm is duplicated.' % (realmName) + exit() + if len(realmName) > 32: # Length check + print '%s: Realm name is too long.' % (realmName) + exit() + + uriNodeList = realmNode.getElementsByTagName(uriNodeTag) + + for uriNode in uriNodeList: + uri = uriNode.attributes['path'].value + + if uri in uriCredentialsMap: # Twins check + print '%s: Uri is duplicated.' % (uri) + exit() + + # Building allowed users list + allowedUsers = uriNode.attributes['users'].value.split(';') + credentials = [] + + for username in allowedUsers: + # Existence check + if username not in [user.username for user in usersList]: + print '%s: User "%s" doesn\'t exist' % (uri, username) + exit() + + # Getting corresponding string + for user in usersList: + if user.username == username: + credentials.append(user.toBasicHTTPAuthString()) + + uriCredentialsMap[uri] = credentials + + mapBuilt[realmName] = uriCredentialsMap + + realmUrisMap = mapBuilt # Saving result for code generation purpose + return mapBuilt + +def genHTTPAuthDataFile(dstFile): + global realmUrisMap + realmUrisCredentialsDataMap = { } + uriDatasMap = { } + + uChar = 'unsigned char' + + generatedHeader = '' + generatedCode = '' + + defHeader = '__HTTP_AUTH_DATA_H__' # TODO: Automated gen + generatedHeader += '#ifndef ' + defHeader + '\n' + generatedHeader += '#define ' + defHeader + '\n' + generatedHeader += '\n' + generatedHeader += '#include "types.h"\n' + generatedHeader += '\n' + + generatedCode += '#include "http_auth_data.h"\n\n' + + # Basic authentication + if (authenticationType == 'basic'): + credentialsList = [] # Ordered list of credentials, app by app + credentialsVarMap = { } # Dictionnary where key is a credentials, + # and value the credentials C string name. + credentialsCount = 0 + + # Write realm string declarations + commentary = '/*********** Realm list ***********/\n' + generatedHeader += commentary + generatedCode += commentary + + offset = 0 + for realm in realmUrisMap: + realmVar = realm.lower() + for c in realmVar: + if (re.search('\W', c)): + realmVar = realmVar.replace(c, '_' + str(ord(c)) + '_') + + generatedHeader += 'extern CONST_VAR(%s, %s[]);\n' % (uChar, realmVar) + generatedCode += 'CONST_VAR(%s, %s[]) = "%s";\n' % (uChar, realmVar, realm) + + for uri in realmUrisMap[realm]: + count = 0 + for credentials in realmUrisMap[realm][uri]: + credentialsList.append(credentials) + count += 1 + + uriDatasMap[uri] = (offset, count) + offset += count + + realmUrisCredentialsDataMap[realm] = uriDatasMap + + generatedHeader += '\n' + generatedCode += '\n' + + # Write basic credentials string declarations + commentary = '/*********** Credentials list ***********/\n' + generatedHeader += commentary + generatedCode += commentary + + for credentials in credentialsList: + credentialsVar = credentials.lower() + for c in credentialsVar: + if (re.search('\W', c)): + credentialsVar = credentialsVar.replace(c, '_' + str(ord(c)) + '_') + + if credentials not in credentialsVarMap: + credentialsVarMap[credentials] = credentialsVar + generatedHeader += 'extern CONST_VAR(%s, %s[]);\n' % (uChar, credentialsVar) + generatedCode += 'CONST_VAR(%s, %s[]) = "%s";\n' % (uChar, credentialsVar, credentials) + + generatedHeader += '\n' + generatedCode += '\n' + + # Write association table for handler->credentials + commentary = '/*********** Credentials string table ***********/\n' + generatedHeader += commentary + generatedCode += commentary + + generatedHeader += 'extern CONST_VAR(%s*, http_auth_basic_credentials_data[]);' % (uChar) + generatedCode += 'CONST_VAR(%s*, http_auth_basic_credentials_data[]) = {\n' %(uChar) + for credentials in credentialsList: + generatedCode += '\t(%s*)&%s,\n' % (uChar, credentialsVarMap[credentials]) + + generatedHeader += '\n' + generatedCode += '};\n' + + # Digest authentication + elif (authenticationType == 'digest'): + print 'HTTP Digest Authentication data generation is not yet supported.' + exit() + + generatedHeader += '\n#endif\n' + + headerFile = open(dstFile + '.h', 'w') + GenApps.writeHeader(headerFile, 0) + headerFile.write(generatedHeader) + + cFile = open(dstFile + '.c', 'w') + GenApps.writeHeader(cFile, 0) + cFile.write(generatedCode) + + return realmUrisCredentialsDataMap diff --git a/tools/access.xml b/tools/access.xml new file mode 100644 index 0000000..58b2292 --- /dev/null +++ b/tools/access.xml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tools/supportedHttpAuthTypeList b/tools/supportedHttpAuthTypeList new file mode 100644 index 0000000..f1e935d --- /dev/null +++ b/tools/supportedHttpAuthTypeList @@ -0,0 +1 @@ +Basic \ No newline at end of file From 007c4e43f2c2988f039cb5d43a48eded982981b3 Mon Sep 17 00:00:00 2001 From: bacara Date: Thu, 22 Aug 2013 14:48:44 +0200 Subject: [PATCH 2/4] Ajout nouveaux fichiers --- apps/httpCodes/401.html | 10 ++ core/md5.c | 345 ++++++++++++++++++++++++++++++++++++++++ core/md5.h | 73 +++++++++ core/utils.c | 52 ++++++ core/utils.h | 76 +++++++++ 5 files changed, 556 insertions(+) create mode 100644 apps/httpCodes/401.html create mode 100644 core/md5.c create mode 100644 core/md5.h create mode 100644 core/utils.c create mode 100644 core/utils.h diff --git a/apps/httpCodes/401.html b/apps/httpCodes/401.html new file mode 100644 index 0000000..6e53480 --- /dev/null +++ b/apps/httpCodes/401.html @@ -0,0 +1,10 @@ + + + + 401 Authorization required + + +

Error 401: Authorization required

+

Please authenticate

+ + diff --git a/core/md5.c b/core/md5.c new file mode 100644 index 0000000..188d69f --- /dev/null +++ b/core/md5.c @@ -0,0 +1,345 @@ +/* + * Copyright or © or Copr. 2008, Simon Duquennoy + * + * Author e-mail: simon.duquennoy@lifl.fr + * + * This software is a computer program whose purpose is to design an + * efficient Web server for very-constrained embedded system. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ + +/* + * Author: Christophe Bacara + * Author e-mail: christophe.bacara@etudiant.univ-lille1.fr + * + * Original software written by L. Peter Deutsch, for Aladdin Enterprises + * Improvements and optimizations for embedded target by Christophe Bacara + */ + +#include "md5.h" + +#define T1 T[0] +#define T2 T[1] +#define T3 T[2] +#define T4 T[3] +#define T5 T[4] +#define T6 T[5] +#define T7 T[6] +#define T8 T[7] +#define T9 T[8] +#define T10 T[9] +#define T11 T[10] +#define T12 T[11] +#define T13 T[12] +#define T14 T[13] +#define T15 T[14] +#define T16 T[15] +#define T17 T[16] +#define T18 T[17] +#define T19 T[18] +#define T20 T[19] +#define T21 T[20] +#define T22 T[21] +#define T23 T[22] +#define T24 T[23] +#define T25 T[24] +#define T26 T[25] +#define T27 T[26] +#define T28 T[27] +#define T29 T[28] +#define T30 T[29] +#define T31 T[30] +#define T32 T[31] +#define T33 T[32] +#define T34 T[33] +#define T35 T[34] +#define T36 T[35] +#define T37 T[36] +#define T38 T[37] +#define T39 T[38] +#define T40 T[39] +#define T41 T[40] +#define T42 T[41] +#define T43 T[42] +#define T44 T[43] +#define T45 T[44] +#define T46 T[45] +#define T47 T[46] +#define T48 T[47] +#define T49 T[48] +#define T50 T[49] +#define T51 T[50] +#define T52 T[51] +#define T53 T[52] +#define T54 T[53] +#define T55 T[54] +#define T56 T[55] +#define T57 T[56] +#define T58 T[57] +#define T59 T[58] +#define T60 T[59] +#define T61 T[60] +#define T62 T[61] +#define T63 T[62] +#define T64 T[63] + +static const uint32_t T[64] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 +}; + +/* Define the state of the MD5 Algorithm. */ +static struct { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state; + +/* Store the final MD5 digest */ +md5_byte_t md5_digest[16]; + +static void md5_process(const md5_byte_t *data /* [64] */) +{ + md5_word_t /* Temporary word a,b,c,d */ + a = md5_state.abcd[0], b = md5_state.abcd[1], + c = md5_state.abcd[2], d = md5_state.abcd[3]; + md5_word_t t; + md5_word_t X[16]; + unsigned char i; + +#if ENDIANNESS == LITTLE_ENDIAN + memcopy(X, data, 64); +#else + for (i = 0; i < 16; ++i, data += 4) + X[i] = data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24); +#endif + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation */ + /* a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti) \ + t = a + F(b,c,d) + X[k] + Ti; \ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation */ + /* a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti) \ + t = a + G(b,c,d) + X[k] + Ti; \ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation */ + /* a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti) \ + t = a + H(b,c,d) + X[k] + Ti; \ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation */ + /* a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti) \ + t = a + I(b,c,d) + X[k] + Ti; \ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each */ + /* of the four registers by the value it had before this block */ + /* was started.) */ + md5_state.abcd[0] += a; + md5_state.abcd[1] += b; + md5_state.abcd[2] += c; + md5_state.abcd[3] += d; +} + +void md5_init() +{ + md5_state.count[0] = 0; + md5_state.count[1] = 0; + md5_state.abcd[0] = 0x67452301; + md5_state.abcd[1] = 0xefcdab89; + md5_state.abcd[2] = 0x98badcfe; + md5_state.abcd[3] = 0x10325476; +} + +void md5_append(const md5_byte_t *data, uint32_t nbytes) +{ + uint32_t offset = (md5_state.count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + md5_state.count[1] += nbytes >> 29; + md5_state.count[0] += nbits; + if (md5_state.count[0] < nbits) + md5_state.count[1]++; + + /* Process an initial partial block. */ + if (offset) { + uint32_t copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcopy(md5_state.buf + offset, data, copy); + if (offset + copy < 64) + return; + data += copy; + nbytes -= copy; + md5_process(md5_state.buf); + } + + /* Process full blocks. */ + for (; nbytes >= 64; data += 64, nbytes -= 64) + md5_process(data); + + /* Process a final partial block. */ + if (nbytes) + memcopy(md5_state.buf, data, nbytes); +} + +void md5_end() +{ + static const md5_byte_t pad[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_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(md5_state.count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pad, ((55 - (md5_state.count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(data, 8); + + for (i = 0; i < 16; ++i) + md5_digest[i] = (md5_byte_t)(md5_state.abcd[i >> 2] >> ((i & 3) << 3)); +} + +/* Useful for improved version of processing */ +/* static const uint8_t R[64] = { */ +/* 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, */ +/* 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, */ +/* 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, */ +/* 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 */ +/* }; */ diff --git a/core/md5.h b/core/md5.h new file mode 100644 index 0000000..d7662ee --- /dev/null +++ b/core/md5.h @@ -0,0 +1,73 @@ +/* + * Copyright or © or Copr. 2008, Simon Duquennoy + * + * Author e-mail: simon.duquennoy@lifl.fr + * + * This software is a computer program whose purpose is to design an + * efficient Web server for very-constrained embedded system. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ + +/* + * Author: Christophe Bacara + * `-Mail: christophe.bacara@etudiant.univ-lille1.fr + * + * Original software written by L. Peter Deutsch, for Aladdin Enterprises + * Few improvements for embedded target by Christophe Bacara + */ + +#ifndef __MD5_H__ +#define __MD5_H__ + +#include "utils.h" + +#include +#include + +#define MD5_DIGEST_LEN 32 + +/* As digest is stored on 16 bytes, useful to get the nth character + * of the hexadecimal representation of the digest */ +#define MD5_DIGEST_GET_NTH_HEXC(i) \ + (hex_char[((i & 1) ? (md5_digest[i >> 1] & 0x0f) : ((md5_digest[i >> 1] & 0xf0) >> 4))]) + +/* Internal type definitions */ +typedef unsigned char md5_byte_t; /* 8-bit byte */ +typedef uint32_t md5_word_t; /* 32-bit word */ + +/* Digest storage */ +extern md5_byte_t md5_digest[16]; + +/* Initialize the algorithm. */ +extern void md5_init(); +/* Append a string to the message. */ +extern void md5_append(const md5_byte_t *data, uint32_t nbytes); +/* Finish the message and return the digest. */ +extern void md5_end(); + +#endif diff --git a/core/utils.c b/core/utils.c new file mode 100644 index 0000000..e488bf2 --- /dev/null +++ b/core/utils.c @@ -0,0 +1,52 @@ +/* + * Copyright or © or Copr. 2008, Simon Duquennoy + * + * Author e-mail: simon.duquennoy@lifl.fr + * + * This software is a computer program whose purpose is to design an + * efficient Web server for very-constrained embedded system. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ + +/* + * Author: Christophe Bacara + * `-Mail: christophe.bacara@etudiant.univ-lille1.fr + */ + +#include "utils.h" + +unsigned const char hex_char[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' +}; + +void memcopy(void *dst, const void *src, uint32_t nbytes) +{ + while (nbytes--) + *(uint8_t *)(dst++) = *(uint8_t *)(src++); +} diff --git a/core/utils.h b/core/utils.h new file mode 100644 index 0000000..da5a05f --- /dev/null +++ b/core/utils.h @@ -0,0 +1,76 @@ +/* + * Copyright or © or Copr. 2008, Simon Duquennoy + * + * Author e-mail: simon.duquennoy@lifl.fr + * + * This software is a computer program whose purpose is to design an + * efficient Web server for very-constrained embedded system. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ + +/* + * Author: Christophe Bacara + * `-Mail: christophe.bacara@etudiant.univ-lille1.fr + */ + +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include "types.h" + +#define LOWER_CASE(c) \ + (c >= 65 && c <= 90 ? c - 'A' + 'a' : c) +#define UPPER_CASE(c) \ + (c >= 97 && c <= 122 ? c - 'a' + 'A' : c) + +#define IS_B64_CHAR(c) \ + ((c >= 97 && c <= 122) \ + || (c >= 65 && c <= 90) \ + || (c >= 48 && c <= 57) \ + || (c == '/') \ + || (c == '+') \ + || (c == '=')) +#define IS_HEX_CHAR(c) \ + ((c >= 97 && c <= 102) \ + || (c >= 65 && c <= 70) \ + || (c >= 48 && c <= 57)) +#define IS_LWS_CHAR(c) \ + (c == ' ' || c == '\t') + +#define GET_HEXC(i) \ + ((i >= 0 && i <= 16) ? hex_char[i] : 0) + +/* Store each hexadecimal character with its value as index */ +extern unsigned const char hex_char[16]; + +/* Copy nbytes bytes from src to dst. */ +extern void memcopy(void *dst, const void *src, uint32_t nbytes); + + + +#endif From 32daec2c22df7823469ff696c51ecd528a35ab18 Mon Sep 17 00:00:00 2001 From: bacara Date: Thu, 22 Aug 2013 14:50:36 +0200 Subject: [PATCH 3/4] Resolution du commit fail precedent --- SConscript | 11 +- SConstruct | 35 +-- core/auth.c | 222 +++++++++++++- core/auth.h | 152 +++++++-- core/connections.h | 67 +++- core/handlers.h | 9 +- core/input.c | 526 ++++++++++++++++++++++++++++---- core/output.c | 150 +++++---- core/smews.h | 7 + tools/GenApps.py | 93 ++++-- tools/GenAuthData.py | 192 ++++++++++-- tools/access.xml | 19 +- tools/supportedHttpAuthTypeList | 3 +- tools/supportedPostAttributList | 4 + 14 files changed, 1220 insertions(+), 270 deletions(-) diff --git a/SConscript b/SConscript index 6d601c2..7ddb851 100644 --- a/SConscript +++ b/SConscript @@ -35,7 +35,7 @@ import os import GenApps # imports from SConstruct -Import('env libFileName elfFileName binDir coreDir driversDir genDir appBase toolsList chuncksNbits dirsMap sourcesMap restrictMap gzipped test') +Import('env libFileName elfFileName binDir coreDir driversDir genDir appBase toolsList chuncksNbits dirsMap sourcesMap restrictMap httpAuthType gzipped test') # returns the list of .c and .s files in dir, prefixed by dstDir def getAllSourceFiles(dir, dstDir): @@ -50,13 +50,14 @@ def getAllSourceFiles(dir, dstDir): return sourceFiles # builders for web applicative resources creation -# used to geerate both static and dynamic resources +# used to generate both static and dynamic resources def generateResource(target, source, env): realm = '' offsetCount = (-1, -1) - + # Checking for HTTP Authentication data finalUri = sourcesMap[str(source[0])] + maxlen = 0 for r in restrictMap: for uri in restrictMap[r]: @@ -67,9 +68,9 @@ def generateResource(target, source, env): maxlen = len(uri) if propsFilesMap.has_key(str(source[0])): - GenApps.generateResource(str(source[0]),str(target[0]),chuncksNbits,gzipped,propsFilesMap[str(source[0])], realm, offsetCount) + GenApps.generateResource(str(source[0]),str(target[0]),chuncksNbits,gzipped,propsFilesMap[str(source[0])], httpAuthType, realm, offsetCount, finalUri) else: - GenApps.generateResource(str(source[0]),str(target[0]),chuncksNbits,gzipped,None,realm, offsetCount) + GenApps.generateResource(str(source[0]),str(target[0]),chuncksNbits,gzipped,None,httpAuthType, realm, offsetCount, finalUri) return None # builder used to generate the file index, with the URLs tree diff --git a/SConstruct b/SConstruct index a848bfc..ae558fd 100644 --- a/SConstruct +++ b/SConstruct @@ -67,6 +67,7 @@ toolsDir = 'tools' tmpBase = 'tmp' appBase = 'apps' httpCodesDir = 'httpCodes' +httpAuthType = '' # the list of python files used by this SConstruct toolsList = map(lambda x: os.path.join(toolsDir,x),filter(lambda x: x.endswith('.py'), os.listdir(toolsDir))) projectName = 'smews' @@ -194,28 +195,12 @@ for appDir in dirsMap.keys(): # Contains a data structure for HTTP authentication restrictions. # Refer to GenAuthData.py for more details. +# TODO: Improve the file generation by stripping the "core/" path and +# allow automatic generation in gen/target restrictMap = { } if globalEnv.has_key('accessfile'): - http_auth = GenAuthData.parseAuthData(globalEnv['accessfile']) - #restrictMap = GenAuthData.genHTTPAuthDataFile(genBase + '/' + str(globalEnv['target']) + '/http_auth_data') - restrictMap = GenAuthData.genHTTPAuthDataFile('gen/linux/http_auth_data') - -# DEBUG PURPOSE -# restrictMap = GenAuthData.parseAuthData(globalEnv['accessfile']) -# for realm in restrictMap: -# print '%s' % (realm) -# for uri in restrictMap[realm]: -# print '\t%s' % (uri) -# for credential in restrictMap[realm][uri]: -# print '\t\t%s' % (credential) -# uriDatasMap = GenAuthData.genHTTPAuthDataFile('http-auth-data.c') -# for uri in uriDatasMap: -# offset = uriDatasMap[uri][0] -# count = uriDatasMap[uri][1] -# print '%s credentials: from %s to %s' % (uri, -# str(offset), -# str(offset + count - 1)) -# exit() + httpAuthType = GenAuthData.parseXMLFile(globalEnv['accessfile']) + restrictMap = GenAuthData.genHTTPAuthDataFile('core/http_auth_data') # compilation options globalEnv.Replace(CC = 'gcc') @@ -226,12 +211,16 @@ if sdump: globalEnv.Append(CCFLAGS = '-DSTACK_DUMP') if debug: globalEnv.Append(CCFLAGS = '-O0 -g') -if 'http_auth' in locals(): - globalEnv.Append(CCFLAGS = '-DHTTP_AUTH=HTTP_AUTH_' + http_auth.upper()) else: globalEnv.Append(CCFLAGS = '-Os -ffunction-sections -fdata-sections -fno-strict-aliasing') globalEnv.Append(LINKFLAGS = '-Wl,--gc-sections -Wl,--print-gc-sections') globalEnv.Append(CPPDEFINES = {'CHUNCKS_NBITS' : str(chuncksNbits)}) + +if httpAuthType == 'basic': + globalEnv.Append(CPPDEFINES = { 'HTTP_AUTH' : 'HTTP_AUTH_BASIC' }) +elif httpAuthType == 'digest': + globalEnv.Append(CPPDEFINES = { 'HTTP_AUTH' : 'HTTP_AUTH_DIGEST' }) + for func in toDisable: globalEnv.Append(CPPDEFINES = { disabledHash[func] : '1'}) if endian: @@ -264,7 +253,7 @@ for target in targets: # export variables for external SConscript files Export('env libFileName elfFileName binDir coreDir driversDir genDir appBase toolsList chuncksNbits sourcesMap gzipped test') Export('env targetDir binDir projectName elfName') - Export('dirsMap sourcesMap restrictMap target sconsBasePath httpCodesDir tmpBase') + Export('dirsMap sourcesMap restrictMap httpAuthType target sconsBasePath httpCodesDir tmpBase') # target dependent SConscript call SConscript(os.path.join(targetDir,'SConscript'),variant_dir = binDir,duplicate = 0) # possible web applications SConscript calls diff --git a/core/auth.c b/core/auth.c index 29cc918..cb322fb 100644 --- a/core/auth.c +++ b/core/auth.c @@ -1,25 +1,84 @@ +/* + * Copyright or © or Copr. 2008, Simon Duquennoy + * + * Author e-mail: simon.duquennoy@lifl.fr + * + * This software is a computer program whose purpose is to design an + * efficient Web server for very-constrained embedded system. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ + +/* + * Author: Christophe Bacara + * `-Mail: christophe.bacara@etudiant.univ-lille1.fr + */ + #include "auth.h" +#include "md5.h" #ifdef HTTP_AUTH +#include "http_auth_data.h" + #if HTTP_AUTH == HTTP_AUTH_BASIC +CONST_VAR(unsigned char, HttpAuthenticateScheme[]) = "basic"; CONST_VAR(char, HttpAuthenticateHeader[]) = "\ -HTTP/1.1 401 Authorization required\r\nWWW-Authenticate: Basic realm=\ +HTTP/1.1 401 Authorization required\r\nWWW-Authenticate: Basic realm=\ "; #elif HTTP_AUTH == HTTP_AUTH_DIGEST +CONST_VAR(unsigned char, HttpAuthenticateScheme[]) = "digest"; CONST_VAR(char, HttpAuthenticateHeader[]) = "\ HTTP/1.1 401 Authorization required\r\nWWW-Authenticate: Digest realm=\ "; + CONST_VAR(char, HttpAuthenticateHeaderNonce[]) = "\ , nonce=\ "; +CONST_VAR(char, HttpAuthenticateHeaderStale[]) = "\ +, stale=true\ +"; + +struct nonce_state_s nonce_state = { + .requester = NULL, + .nonce = "\0" +}; + #endif -uint32_t http_401_handler_dynamic_header_length(struct output_handler_t *restricted_handler) +uint32_t http_401_handler_dynamic_header_length(const struct output_handler_t *restricted_handler +#if HTTP_AUTH == HTTP_AUTH_DIGEST + , const uint8_t stale +#endif + ) { unsigned const char *char_p = restricted_handler->handler_restriction.realm; uint32_t length = 0; @@ -32,34 +91,167 @@ uint32_t http_401_handler_dynamic_header_length(struct output_handler_t *restric while (*char_p++) ++length; + /* If realm length is odd, we'll add a space after it */ + if (length & 1) + ++length; + #if HTTP_AUTH == HTTP_AUTH_BASIC /* Remaining size computation */ - length += sizeof(HttpAuthenticateHeader) - 1; + length += sizeof (HttpAuthenticateHeader) - 1; length += 2; /* Realm must be double quoted */ #elif HTTP_AUTH == HTTP_AUTH_DIGEST - /* Nonce length computation */ - char_p = http_auth_nonce; - while (*char_p++) - ++length; - /* Remaining size computation */ - length += sizeof(HttpAuthenticateHeader) - 1; - length += sizeof(HttpAuthenticateHeaderNonce) - 1; - length += 4; /* Realm and nonce must be double quoted. */ + length += sizeof (HttpAuthenticateHeader) - 1; + length += sizeof (HttpAuthenticateHeaderNonce) - 1; + if (stale) + length += sizeof (HttpAuthenticateHeaderStale) - 1; + + length += 32; /* Nonce is a 32-Hex character string */ + length += 4; /* Realm and nonce must be double quoted. */ #endif return length; } -uint32_t http_401_handler_length(struct output_handler_t *restricted_handler) +uint32_t http_401_handler_length(const struct output_handler_t *restricted_handler +#if HTTP_AUTH == HTTP_AUTH_DIGEST + , const uint8_t stale +#endif + ) { - /* Add the size of remaining bytes */ - return http_401_handler_dynamic_header_length (restricted_handler) - + CONST_UI32(GET_FILE(&http_401_handler).length); + /* Add the size of remaining bytes */ + return http_401_handler_dynamic_header_length (restricted_handler +#if HTTP_AUTH == HTTP_AUTH_DIGEST + , stale +#endif + ) + + CONST_UI32 (GET_FILE (&http_401_handler).length); } +#if HTTP_AUTH == HTTP_AUTH_BASIC +char http_basic_authenticate(const struct output_handler_t *handler, + unsigned const char *credentials) +{ + unsigned const char *current_credentials; + unsigned const char *given_credentials; + uint8_t i; + + /* For each valid credentials ... */ + for (i = handler->handler_restriction.credentials_offset; + i < (handler->handler_restriction.credentials_offset + + handler->handler_restriction.credentials_count); + ++i) { + + current_credentials = http_auth_basic_credentials_data[i]; + given_credentials = credentials; + + /* String comparison loop */ + while (*given_credentials == *current_credentials) { + /* If we reach end of both string simultaneously, they match. */ + if (*given_credentials == '\0' && *current_credentials == '\0') + return HTTP_AUTHENTICATE_SUCCESS; + + ++given_credentials; + ++current_credentials; + } + } + + return HTTP_AUTHENTICATE_FAILURE; +} + +#elif HTTP_AUTH == HTTP_AUTH_DIGEST +char http_digest_authenticate(const struct connection *requester, + const struct output_handler_t *handler, + const struct auth_data_s *auth_data) +{ + credentials_digest_t *digest_infos = NULL; + uint8_t i, j; + + /* Nonce validity check */ + for (i = 0; i < NONCE_LEN; ++i) + if (auth_data->nonce[i] != nonce_state.nonce[i]) + return HTTP_AUTHENTICATE_STALE; + + /* User validity check */ + for (i = handler->handler_restriction.credentials_offset; + i < (handler->handler_restriction.credentials_offset + + handler->handler_restriction.credentials_count); + ++i) { + + if (http_auth_digest_credentials_table[i] != auth_data->username_blob) + continue; + + /* Once username has been validated, get the matching credentials */ + for (j = 0; j < USER_DIGEST_COUNT; ++j) { + if (http_auth_digest_credentials_data[j].username_blob == auth_data->username_blob + && + http_auth_digest_credentials_data[j].realm == handler->handler_restriction.realm) { + digest_infos = (credentials_digest_t *) &http_auth_digest_credentials_data[j]; + break; + } + } + break; + } + + /* No digest informations: invalid username */ + if (digest_infos == NULL) + return HTTP_AUTHENTICATE_FAILURE; + + /* Compute the valid md5 digest : + * md5(user_digest:nonce:resource_digest) */ + md5_init(); + md5_append((const md5_byte_t *) digest_infos->user_digest, MD5_DIGEST_LEN); + md5_append((const md5_byte_t *) ":", 1); + md5_append((const md5_byte_t *) nonce_state.nonce, NONCE_LEN); + md5_append((const md5_byte_t *) ":", 1); + md5_append((const md5_byte_t *) handler->handler_restriction.resource_digest, + MD5_DIGEST_LEN); + md5_end(); + + /* Compare given credentials with computed credentials */ + for (i = 0; i < MD5_DIGEST_LEN; ++i) { + /* WARNING: md5_digest is a 16-byte table, whereas credentials is a classic + * C character string ! + * Don't try something like "credentials[i] == md5_digest[i]" */ + if (auth_data->response[i] != MD5_DIGEST_GET_NTH_HEXC(i)) + return HTTP_AUTHENTICATE_FAILURE; + } + + return HTTP_AUTHENTICATE_SUCCESS; +} + +void http_auth_gen_nonce() +{ + static uint32_t x = 123456789; /* XORSHIFT data */ + static uint32_t y = 362436069; /* XORSHIFT data */ + static uint32_t z = 521288629; /* XORSHIFT data */ + static uint32_t w = 88675123; /* XORSHIFT data */ + uint32_t tmp; + uint8_t i; + + for (i = 0; i < NONCE_LEN; i += 8) { + /* Compute xorshift */ + tmp = x ^ (x << 1); + x = y; + y = z; + z = w; + w = w ^ (w >> 19) ^ (tmp ^ (tmp >> 8)); + + /* Fill nonce with hex character */ + nonce_state.nonce[i] = hex_char[(w & 0xf0000000) >> 28]; + nonce_state.nonce[i + 1] = hex_char[(w & 0x0f000000) >> 24]; + nonce_state.nonce[i + 2] = hex_char[(w & 0x00f00000) >> 20]; + nonce_state.nonce[i + 3] = hex_char[(w & 0x000f0000) >> 16]; + nonce_state.nonce[i + 4] = hex_char[(w & 0x0000f000) >> 12]; + nonce_state.nonce[i + 5] = hex_char[(w & 0x00000f00) >> 8]; + nonce_state.nonce[i + 6] = hex_char[(w & 0x000000f0) >> 4]; + nonce_state.nonce[i + 7] = hex_char[(w & 0x0000000f)]; + } +} + +#endif #endif diff --git a/core/auth.h b/core/auth.h index d2542bc..9ab0947 100644 --- a/core/auth.h +++ b/core/auth.h @@ -1,55 +1,157 @@ -#ifdef HTTP_AUTH +/* + * Copyright or © or Copr. 2008, Simon Duquennoy + * + * Author e-mail: simon.duquennoy@lifl.fr + * + * This software is a computer program whose purpose is to design an + * efficient Web server for very-constrained embedded system. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ + +/* + * Author: Christophe Bacara + * `-Mail: christophe.bacara@etudiant.univ-lille1.fr + */ #ifndef __AUTH_H__ #define __AUTH_H__ +#define HTTP_AUTH_BASIC 0 +#define HTTP_AUTH_DIGEST 1 + #include "handlers.h" +#include "connections.h" #include "types.h" +/* Readability++ macros */ +#define IS_RESTRICTED(handler) (handler->handler_restriction.realm) + +#define HTTP_AUTHENTICATE_SUCCESS 1 +#define HTTP_AUTHENTICATE_FAILURE 0 +#define NO_OFFSET (127) /* Useful for parsing nonce/response in input.c */ + /* Shortcut to the http 401 handler */ #define http_401_handler apps_httpCodes_401_html_handler -/* Restriction/authentication macro */ -#define IS_RESTRICTED(handler) (handler->handler_restriction.realm) -#define IS_AUTHENTICATED(handler) (handler) +struct auth_data_s { + uint8_t scheme_parsed: 1; /* Flag set on 1 if scheme has been parsed */ + uint8_t parsing_offset: 7; /* Offset into fields while parsing */ +#if HTTP_AUTH == HTTP_AUTH_BASIC + unsigned char *credentials; /* Dynamically allocated storage for + * basic credentials: b64(user:passwd) */ +#elif HTTP_AUTH == HTTP_AUTH_DIGEST + uint8_t username_blob; /* Username identifier in blob tree */ + unsigned char response[32]; /* Response digest as given by the client */ + unsigned char nonce[32]; /* Nonce used by the client to compute digest */ +#endif +}; /* Http 401 handler */ extern CONST_VAR(struct output_handler_t, apps_httpCodes_401_html_handler); -/* http_401_handler_length: - * - * Returns the length of the 401 handler that matches the specified +/* Store the character string representation of the authentication scheme */ +extern CONST_VAR(unsigned char, HttpAuthenticateScheme[]); +/* Store the character string representation of the http authenticate header */ +extern CONST_VAR(char, HttpAuthenticateHeader[]); + +/* Returns the length of the 401 handler that matches the specified * restricted handler, including the dynamically generated http header with - * correct realm and nonce (if digest auth). - */ -extern uint32_t http_401_handler_length(struct output_handler_t *restricted_handler); + * correct realm and nonce (if digest auth). */ +extern uint32_t http_401_handler_length(const struct output_handler_t *restricted_handler +#if HTTP_AUTH == HTTP_AUTH_DIGEST + , const uint8_t stale +#endif + ); -/* http_401_handler_length: - * - * Returns the length of the dynamically generated http header for a 401 handler. - */ -extern uint32_t http_401_handler_dynamic_header_length(struct output_handler_t *restricted_handler); - -#if HTTP_AUTH == HTTP_AUTH_BASIC +/* Returns the length of the dynamically generated http header for a 401 handler. */ +extern uint32_t http_401_handler_dynamic_header_length(const struct output_handler_t *restricted_handler +#if HTTP_AUTH == HTTP_AUTH_DIGEST + , const uint8_t stale +#endif + ); -extern CONST_VAR(char, HttpAuthenticateHeader[]); +/*************************************************************************/ +/********************* AUTHENTICATION KIND SPECIFICS *********************/ +/*************************************************************************/ + +/*********************** BASIC AUTHENTICATION DATA ***********************/ +#if HTTP_AUTH == HTTP_AUTH_BASIC -#define HTTP_AUTHENTICATE_HEADER_LEN 69 +#define HTTP_AUTHENTICATE_HEADER_LEN 68 +#define HTTP_AUTHENTICATE_HEADER_CHK 0x428Cu -#define HTTP_AUTHENTICATE_HEADER_CHK 0x109Eu +/* Returns 1 if given credentials are valid for the requested handler, 0 if not. */ +extern char http_basic_authenticate(const struct output_handler_t *handler, + unsigned const char *credentials); +/*********************** DIGEST AUTHENTICATION DATA **********************/ #elif HTTP_AUTH == HTTP_AUTH_DIGEST -extern CONST_VAR(char, HttpAuthenticateHeader[]); +#define HTTP_AUTHENTICATE_STALE -1 + +#define HTTP_AUTHENTICATE_HEADER_LEN 68 +#define HTTP_AUTHENTICATE_HEADER_CHK 0x9A92u + extern CONST_VAR(char, HttpAuthenticateHeaderNonce[]); +extern CONST_VAR(char, HttpAuthenticateHeaderStale[]); -#define HTTP_AUTHENTICATE_HEADER_LEN 70 #define HTTP_AUTHENTICATE_HEADER_NONCE_LEN 8 - -#define HTTP_AUTHENTICATE_HEADER_CHK 0x9A92u #define HTTP_AUTHENTICATE_HEADER_NONCE_CHK 0x6E30u -#endif +#define HTTP_AUTHENTICATE_HEADER_STALE_LEN 12 +#define HTTP_AUTHENTICATE_HEADER_STALE_CHK 0x5016u + +#define NONCE_LEN 32 + +/* Digest authentication data structure */ +typedef struct credentials_digest_s { + const uint8_t username_blob; /* Corresponding blob into users blob tree */ + const unsigned char *realm; /* Realm for which this credentials stand */ + const unsigned char user_digest[33]; /* Digest for this user & realm. */ +} credentials_digest_t; + +/* Nonce control structure */ +extern struct nonce_state_s { + struct connection *requester; /* Last connection requesting a nonce */ + unsigned char nonce[NONCE_LEN]; /* Last nonce generated */ +} nonce_state; + +/* Returns 1 if given credentials are valid for the requested handler, 0 if not. + * Returns -1 if nonce is not valid (response will contain "stale" in WWW-Auth) */ +extern char http_digest_authenticate(const struct connection *requester, + const struct output_handler_t *handler, + const struct auth_data_s *auth_data); + +/* Generate a new nonce based on a xorshift randomizer. */ +extern void http_auth_gen_nonce(); #endif +/**************************** END OF SPECIFICS ***************************/ + #endif + diff --git a/core/connections.h b/core/connections.h index 596f706..aa64bb9 100644 --- a/core/connections.h +++ b/core/connections.h @@ -36,6 +36,7 @@ #ifndef __CONNECTIONS_H__ #define __CONNECTIONS_H__ +#include "auth.h" #include "handlers.h" #include "coroutines.h" @@ -116,31 +117,73 @@ struct http_connection { unsigned const char * /*CONST_VAR*/ blob; struct generator_service_t *generator_service; -#ifdef HTTP_AUTH - uint8_t authenticated: 1; -#endif - #ifndef DISABLE_ARGS struct args_t *args; unsigned char *curr_arg; unsigned char arg_ref_index; #endif + #ifndef DISABLE_TIMERS unsigned char transmission_time; #endif - enum parsing_state_e {parsing_out, parsing_cmd, parsing_url, parsing_end + #ifndef DISABLE_POST - , parsing_post_attributes, parsing_post_end, parsing_post_content_type, parsing_post_args, parsing_post_data, parsing_boundary, parsing_init_buffer } parsing_state: 4; - uint8_t post_url_detected:1; /* bit used to know if url is post or get request */ + enum parsing_state_e { + parsing_out, + parsing_cmd, + parsing_url, + parsing_end, +#if defined(HTTP_AUTH) && HTTP_AUTH == HTTP_AUTH_DIGEST + parsing_authorization_username, +#endif + parsing_post_attributes, + parsing_post_end, + parsing_post_content_type, + parsing_post_args, + parsing_post_data, + parsing_boundary, + parsing_init_buffer, + } parsing_state : 4; + + uint8_t post_url_detected : 1; struct post_data_t *post_data; #else -} parsing_state/*: 2*/; /* this field will be on one byte anyway as previous fields are exactly 16 bits. - Using a full char does not change the struct size, but lower the code size needed to access it */ +#ifdef HTTP_AUTH + enum parsing_state_e { + parsing_out, + parsing_cmd, + parsing_url, + parsing_end, +#if HTTP_AUTH == HTTP_AUTH_DIGEST + parsing_authorization_username, +#endif + parsing_post_attributes, + parsing_post_end + } parsing_state : 3; +#else + enum parsing_state_e { + parsing_out, + parsing_cmd, + parsing_url, + parsing_end + } parsing_state : 2; #endif +#endif + +/* enum parsing_state_e {parsing_out, parsing_cmd, parsing_url, parsing_end */ +/* #ifndef DISABLE_POST */ +/* , parsing_post_attributes, parsing_post_end, parsing_post_content_type, parsing_post_args, parsing_post_data, parsing_boundary, parsing_init_buffer } parsing_state: 4; */ +/* uint8_t post_url_detected:1; /\* bit used to know if url is post or get request *\/ */ +/* struct post_data_t *post_data; */ +/* #else */ +/* } parsing_state/\*: 2*\/; /\* this field will be on one byte anyway as previous fields are exactly 16 bits. */ +/* Using a full char does not change the struct size, but lower the code size needed to access it *\/ */ +/* #endif */ + #ifndef DISABLE_COMET -unsigned char comet_passive: 1; -unsigned char comet_send_ack: 1; -unsigned char comet_streaming: 1; + unsigned char comet_passive: 1; + unsigned char comet_send_ack: 1; + unsigned char comet_streaming: 1; #endif }; diff --git a/core/handlers.h b/core/handlers.h index 11304c1..cbb466b 100644 --- a/core/handlers.h +++ b/core/handlers.h @@ -149,16 +149,15 @@ struct output_handler_t { }handler_mimes; #endif #ifdef HTTP_AUTH - #if HTTP_AUTH == HTTP_AUTH_BASIC - struct handler_restrictions_t { + struct handler_restriction_t { unsigned const char *realm; /* Realm of restriction : - no restriction if NULL */ uint8_t credentials_offset; /* Offset to the first credential */ uint8_t credentials_count; /* Credentials count from offset */ +#if HTTP_AUTH == HTTP_AUTH_DIGEST + unsigned const char resource_digest[33]; /* Digest of md5("METHOD:uri") */ +#endif } handler_restriction; - #elif HTTP_AUTH == HTTP_AUTH_DIGEST - /* TODO */ - #endif #endif }; diff --git a/core/input.c b/core/input.c index 39329b6..c1694e3 100644 --- a/core/input.c +++ b/core/input.c @@ -42,7 +42,14 @@ #include "coroutines.h" #include "blobs.h" #include "defines.h" +#include "utils.h" + +#ifdef HTTP_AUTH #include "auth.h" +#include "md5.h" +#endif + +#include /* Used to dump the runtime stack */ #ifdef STACK_DUMP @@ -54,17 +61,11 @@ unsigned char *stack_base; #define http_404_handler apps_httpCodes_404_html_handler extern CONST_VAR(struct output_handler_t, apps_httpCodes_404_html_handler); -/* 401 Authorization required handler */ -/* #ifdef HTTP_AUTH */ -/* #define http_401_handler apps_httpCodes_401_html_handler */ -/* extern CONST_VAR(struct output_handler_t, apps_httpCodes_401_html_handler); */ -/* #endif */ - /* Maximal TCP MSS */ #ifndef DEV_MTU #define MAX_MSS 0xffff #else -#define MAX_MSS (DEV_MTU - (IP_HEADER_SIZE+ TCP_HEADER_SIZE)) +#define MAX_MSS (DEV_MTU - (IP_HEADER_SIZE + TCP_HEADER_SIZE)) #endif /* IP and TCP constants */ @@ -80,7 +81,7 @@ extern CONST_VAR(struct output_handler_t, apps_httpCodes_404_html_handler); /* Initial sequence number */ #define BASIC_SEQNO 0x42b7a491 -#ifndef DISABLE_POST +#if !defined(DISABLE_POST) || defined(HTTP_AUTH) #define URL_POST_END 255 #define HEADER_POST_END 128 static CONST_VAR(unsigned char, blob_http_header_end[]) = {13,10,13,128}; @@ -304,6 +305,23 @@ char smews_receive(void) { unsigned char tmp_char; uint16_t x; unsigned char new_tcp_data; + +#ifdef HTTP_AUTH + /* (bacara): Temporary solution, this will need to be stored into a + * connection struct later */ + struct auth_data_s auth_data = { + .scheme_parsed = 0, + .parsing_offset = NO_OFFSET, +#if HTTP_AUTH == HTTP_AUTH_BASIC + .credentials = NULL, +#elif HTTP_AUTH == HTTP_AUTH_DIGEST + .username_blob = 0, + .response = { 0 }, + .nonce = { 0 }, +#endif + }; +#endif + #ifndef DISABLE_GP_IP_HANDLER uint8_t protocol; #endif @@ -327,6 +345,8 @@ char smews_receive(void) { if(!DEV_DATA_TO_READ) return 0; + printf("##### SMEWS IS RECEIVING A PACKET #####\n"); + #ifdef IPV6 /* Get IP Version (and 4 bits of the traffic class) */ DEV_GET(tmp_char); @@ -708,7 +728,11 @@ char smews_receive(void) { tmp_args_size_ref = CONST_UI16(output_handler->handler_args.args_size); } #endif - while(x < segment_length && output_handler != &http_404_handler + while(x < segment_length + && output_handler != &http_404_handler +#ifdef HTTP_AUTH + && output_handler != &http_401_handler +#endif #ifndef DISABLE_POST && output_handler != &http_505_handler #endif @@ -728,6 +752,7 @@ char smews_receive(void) { #endif x++; DEV_GETC(tmp_char); + #ifndef DISABLE_POST /* updating content length */ if((tmp_connection.protocol.http.parsing_state == parsing_init_buffer @@ -804,13 +829,33 @@ char smews_receive(void) { && output_handler != &http_505_handler #endif ) { -#ifndef DISABLE_POST + +#if !defined(DISABLE_POST) || defined(HTTP_AUTH) /* "\n\r\n\r" detected (end header or end part of multipart )*/ - if(tmp_connection.protocol.http.parsing_state == parsing_post_end){ - if(tmp_connection.protocol.http.post_data->content_type == (uint8_t)-1){ /* no content type */ + if(tmp_connection.protocol.http.parsing_state == parsing_post_end) { + +#ifdef HTTP_AUTH + /* HTTP-Auth: + * We are ready to send if there is no more header to parse and if + * requested url is not a post url. + */ + if (IS_RESTRICTED (output_handler) +#ifndef DISABLE_POST + && !tmp_connection.protocol.http.post_data +#endif + ) { + tmp_connection.protocol.http.parsing_state = parsing_end; + tmp_connection.protocol.http.ready_to_send = 1; + break; + } +#endif + +#ifndef DISABLE_POST + if(tmp_connection.protocol.http.post_data->content_type == (uint8_t)-1) { /* no content type */ output_handler = &http_505_handler; break; } + if(tmp_connection.protocol.http.post_data->boundary){ tmp_connection.protocol.http.post_data->boundary->ready_to_count = 1; /* parsing header part of multipart */ @@ -848,7 +893,12 @@ char smews_receive(void) { } tmp_connection.protocol.http.parsing_state = parsing_post_data; } +#endif +#endif +#if !defined(DISABLE_POST) || defined(HTTP_AUTH) } +#endif +#ifndef DISABLE_POST /* parsing post data => run coroutine */ if(tmp_connection.protocol.http.parsing_state == parsing_post_data){ /* starting buffer init */ @@ -922,6 +972,7 @@ char smews_receive(void) { continue; break; } + /* searching end character of post url detection */ if(tmp_connection.protocol.http.parsing_state == parsing_url && blob_curr == URL_POST_END){ if(tmp_connection.protocol.http.post_data){ @@ -933,8 +984,228 @@ char smews_receive(void) { break; } } +#endif + +#if defined(HTTP_AUTH) && HTTP_AUTH == HTTP_AUTH_DIGEST + if (tmp_connection.protocol.http.parsing_state == parsing_authorization_username) { + if (blob_curr >= 128) { + /* Username has been successfully parsed. */ + auth_data.username_blob = blob_curr; + blob = blob_http_header_content; + tmp_connection.protocol.http.parsing_state = parsing_post_attributes; + continue; /* Skip the final '"' */ + } + else { + /* Error: invalid username */ + tmp_connection.protocol.http.blob = (unsigned char *)(output_handler); + output_handler = &http_401_handler; + tmp_connection.protocol.http.parsing_state = parsing_end; + break; + } + } +#endif + +#if !defined(DISABLE_POST) || defined(HTTP_AUTH) /* parsing attributes of post header */ - if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes){ + if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes) { +#endif + +#ifdef HTTP_AUTH + if (blob_curr == ATTRIBUT_AUTHORIZATION_58_ + 128) { + + if (!auth_data.scheme_parsed) { + if (IS_LWS_CHAR (tmp_char)) { + /* Parsing did not start yet (escape unsignificant whitespaces) */ + if (auth_data.parsing_offset == NO_OFFSET) + continue; + + /* Parsing just ended */ + if (HttpAuthenticateScheme[auth_data.parsing_offset] == '\0') { + auth_data.scheme_parsed = 1; + auth_data.parsing_offset = NO_OFFSET; + continue; + } + } + else { + /* Parsing just started */ + if (auth_data.parsing_offset == NO_OFFSET) + auth_data.parsing_offset = 0; + + if (LOWER_CASE (tmp_char) + == HttpAuthenticateScheme[auth_data.parsing_offset]) { + ++auth_data.parsing_offset; + continue; + } + } + + /* If execution flow reach this point, scheme parsing failed. */ + tmp_connection.protocol.http.blob = (unsigned char *)(output_handler); + output_handler = &http_401_handler; + auth_data.parsing_offset = NO_OFFSET; + tmp_connection.protocol.http.parsing_state = parsing_end; + break; + } + else { +#if HTTP_AUTH == HTTP_AUTH_BASIC + /* If no space has been allocated to store given credentials, + * allocate it ! */ + if (!auth_data.credentials) { + auth_data.credentials = mem_alloc(10 * sizeof(char)); + if (!auth_data.credentials) { + output_handler = &http_404_handler; + break; + } + + auth_data.credentials[0] = 2; /* First byte is the used space */ + auth_data.credentials[1] = 10; /* Second byte is the allocated space */ + } + + /* Escape unsignificant whitespace */ + if (IS_LWS_CHAR(tmp_char) && auth_data.credentials[0] == 2) + continue; + + /* As long as the character read is a base64 character, fill + * credentials string */ + if (IS_B64_CHAR (tmp_char)) { + /* String container reallocation if needed */ + if (auth_data.credentials[0] == auth_data.credentials[1]) { + auth_data.credentials = mem_realloc(auth_data.credentials, + auth_data.credentials[0], + 10); + if (!auth_data.credentials) { + output_handler = &http_404_handler; + break; + } + + auth_data.credentials[1] += 10; + } + + /* Add current character */ + auth_data.credentials[auth_data.credentials[0]++] = tmp_char; + } + else { + uint8_t i = 0; + unsigned char *final_credentials; + + final_credentials = mem_alloc((auth_data.credentials[0] - 1) + * sizeof (unsigned char)); + if (!final_credentials) { + output_handler = &http_404_handler; + break; + } + + /* Copy credentials string into final buffer */ + for (i = 0; i < auth_data.credentials[0] - 2; ++i) + final_credentials[i] = auth_data.credentials[i + 2]; + final_credentials[i] = '\0'; + + mem_free (auth_data.credentials, + auth_data.credentials[1] * sizeof (unsigned char)); + + auth_data.credentials = final_credentials; + tmp_connection.protocol.http.parsing_state = parsing_post_attributes; + blob = blob_http_header_content; + } +#elif HTTP_AUTH == HTTP_AUTH_DIGEST + blob = blob_http_header_content; +#endif + } + } +#if HTTP_AUTH == HTTP_AUTH_DIGEST + else if (blob_curr == ATTRIBUT_USERNAME_61_ + 128) { + blob = usernames_tree; + tmp_connection.protocol.http.parsing_state = parsing_authorization_username; + continue; /* Skip the current '"' */ + } + else if (blob_curr == ATTRIBUT_NONCE_61_ + 128) { + + if (tmp_char == '"') { + switch (auth_data.parsing_offset) { + /* Parsing just started: set offset and skip the double quote */ + case NO_OFFSET: + auth_data.parsing_offset = 0; + continue; + + /* Parsing just ended: reset offset and skip the double quote */ + case NONCE_LEN: + auth_data.parsing_offset = NO_OFFSET; + blob = blob_http_header_content; + continue; + + /* Error: nonce value doesn't have a valid length */ + default: + tmp_connection.protocol.http.blob = (unsigned char *)(output_handler); + output_handler = &http_401_handler; + auth_data.parsing_offset = NO_OFFSET; + break; + } + } + else { + switch (auth_data.parsing_offset) { + /* Parsing didn't start yet, but tmp_char is not a double quote */ + case NO_OFFSET: // WARNING: Fall through + /* Parsed string has a length > NONCE_LEN */ + case NONCE_LEN: + tmp_connection.protocol.http.blob = (unsigned char *)(output_handler); + output_handler = &http_401_handler; + auth_data.parsing_offset = NO_OFFSET; + tmp_connection.protocol.http.ready_to_send = 1; + break; + + default: + auth_data.nonce[auth_data.parsing_offset++] = tmp_char; + break; + } + } + } + else if (blob_curr == ATTRIBUT_RESPONSE_61_ + 128) { + + if (tmp_char == '"') { + switch (auth_data.parsing_offset) { + /* Parsing just started: set offset and skip the double quote */ + case NO_OFFSET: + auth_data.parsing_offset = 0; + continue; + + /* Parsing just ended: reset offset and skip the double quote */ + case MD5_DIGEST_LEN: + auth_data.parsing_offset = NO_OFFSET; + blob = blob_http_header_content; + continue; + + /* Error: response value doesn't have a valid length */ + default: + auth_data.parsing_offset = NO_OFFSET; + tmp_connection.protocol.http.blob = (unsigned char *)(output_handler); + output_handler = &http_401_handler; + break; + } + } + else { + switch (auth_data.parsing_offset) { + /* Parsing didn't start yet, but tmp_char is not a double quote */ + case NO_OFFSET: // WARNING: Fall through + /* Parsed string has a length > NONCE_LEN */ + case MD5_DIGEST_LEN: + auth_data.parsing_offset = NO_OFFSET; + tmp_connection.protocol.http.blob = (unsigned char *)(output_handler); + output_handler = &http_401_handler; + break; + + default: + auth_data.response[auth_data.parsing_offset++] = tmp_char; + break; + } + } + } +#endif + +#ifndef DISABLE_POST + else +#endif +#endif + +#ifndef DISABLE_POST if(blob_curr == ATTRIBUT_CONTENT_45_TYPE + 128) { /* content-type */ blob = mimes_tree; tmp_connection.protocol.http.parsing_state = parsing_post_content_type; @@ -955,7 +1226,7 @@ char smews_receive(void) { break; } tmp_connection.protocol.http.post_data->filename[0] = 2; /* first value is the size of filename */ - tmp_connection.protocol.http.post_data->filename[1] = 10; /* seconde value is the size allocated */ + tmp_connection.protocol.http.post_data->filename[1] = 10; /* second value is the size allocated */ } else if(tmp_char == '\"'){ /* end of filename, the indexes are removed */ uint8_t i = 0; @@ -1032,7 +1303,11 @@ char smews_receive(void) { } } } +#endif +#if !defined(DISABLE_POST) || defined(HTTP_AUTH) } +#endif +#ifndef DISABLE_POST /* parsing content-type */ else if(tmp_connection.protocol.http.parsing_state == parsing_post_content_type){ if(blob_curr - 128 == CONTENT_TYPE_MULTIPART_47_FORM_45_DATA){ /* multipart */ @@ -1073,11 +1348,15 @@ char smews_receive(void) { } tmp_connection.protocol.http.parsing_state = parsing_post_attributes; blob = blob_http_header_content; - } else + } #endif +#if !defined(DISABLE_POST) || defined(HTTP_AUTH) + else +#endif + if(tmp_connection.protocol.http.parsing_state == parsing_cmd) { #ifndef DISABLE_POST - if(blob_curr == REQUEST_POST_32_ + 128){ /* post request */ + if(blob_curr == REQUEST_POST_32_ + 128) { /* post request */ /* allocating post data structure */ tmp_connection.protocol.http.post_data = mem_alloc(sizeof(struct post_data_t)); if(!tmp_connection.protocol.http.post_data){ @@ -1107,21 +1386,35 @@ char smews_receive(void) { output_handler = (struct output_handler_t*)CONST_ADDR(resources_index[blob_curr - 128]); tmp_blob = blob_curr - 128; } + +#if !defined(DISABLE_POST) || defined(HTTP_AUTH) + if ( #ifndef DISABLE_POST - if(tmp_connection.protocol.http.post_data){ + tmp_connection.protocol.http.post_data +#endif +#if !defined(DISABLE_POST) && defined(HTTP_AUTH) + || +#endif +#ifdef HTTP_AUTH + IS_RESTRICTED(output_handler) +#endif + ) { tmp_connection.protocol.http.parsing_state = parsing_post_attributes; blob = blob_http_header_content; tmp_connection.protocol.http.ready_to_send = 0; } - else{ + else { #endif tmp_connection.protocol.http.parsing_state = parsing_end; break; -#ifndef DISABLE_POST +#if !defined(DISABLE_POST) || defined(HTTP_AUTH) } +#endif +#ifndef DISABLE_POST } #endif } else { + #ifndef DISABLE_ARGS if(tmp_char == '?' #ifndef DISABLE_POST @@ -1172,7 +1465,8 @@ char smews_receive(void) { } blob_curr = CONST_READ_UI8(blob); } -#ifndef DISABLE_POST + +#if !defined(DISABLE_POST) || defined(HTTP_AUTH) /* end header detection */ if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && tmp_char == 13){ blob = blob_http_header_end; @@ -1191,11 +1485,13 @@ char smews_receive(void) { tmp_connection.protocol.http.arg_ref_index = 128; blob = (const unsigned char *)CONST_ADDR(output_handler->handler_args.args_tree); continue; - } else if(tmp_char == ' ') { + } + else if(tmp_char == ' ') { tmp_connection.protocol.http.ready_to_send = 1; tmp_connection.protocol.http.parsing_state = parsing_end; break; - } else { + } + else { switch(tmp_arg_ref.arg_type) { case arg_str: { unsigned char *tmp_size_ptr = ((unsigned char*)tmp_connection.protocol.http.curr_arg + tmp_arg_ref.arg_size - 1); @@ -1217,16 +1513,29 @@ char smews_receive(void) { } } else #endif -#ifndef DISABLE_POST +#if !defined(DISABLE_POST) || defined(HTTP_AUTH) if(tmp_connection.protocol.http.parsing_state == parsing_post_end && blob_curr != HEADER_POST_END){ blob++; } - else if( - !(tmp_connection.protocol.http.parsing_state == parsing_post_content_type && (tmp_char == 32 || tmp_char == 58)) - && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_CONTENT_45_LENGTH + 128) - && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_BOUNDARY_61_ + 128) - && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_FILENAME_61_ + 128) - ) + else if ( +#ifndef DISABLE_POST + !(tmp_connection.protocol.http.parsing_state == parsing_post_content_type && (tmp_char == 32 || tmp_char == 58)) + && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_CONTENT_45_LENGTH + 128) + && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_BOUNDARY_61_ + 128) + && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_FILENAME_61_ + 128) +#endif +#if !defined(DISABLE_POST) && defined(HTTP_AUTH) + && +#endif +#ifdef HTTP_AUTH + !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_AUTHORIZATION_58_ + 128) +#if HTTP_AUTH == HTTP_AUTH_DIGEST + && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_USERNAME_61_ + 128) + && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_NONCE_61_ + 128) + && !(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && blob_curr == ATTRIBUT_RESPONSE_61_ + 128) +#endif +#endif + ) #endif { do { @@ -1234,55 +1543,86 @@ char smews_receive(void) { unsigned char offsetInf = 0; unsigned char offsetEq = 0; unsigned char blob_next; -#ifndef DISABLE_POST - if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes && tmp_char < 123 && tmp_char > 96) + +#if !defined(DISABLE_POST) || defined(HTTP_AUTH) + if ((tmp_connection.protocol.http.parsing_state == parsing_post_attributes +#if defined(HTTP_AUTH) && HTTP_AUTH == HTTP_AUTH_DIGEST + || tmp_connection.protocol.http.parsing_state == parsing_authorization_username +#endif + ) + && tmp_char < 123 && tmp_char > 96) tmp_char = tmp_char - 'a' + 'A'; #endif + blob_curr = CONST_READ_UI8(blob); blob_next = CONST_READ_UI8(++blob); + if (tmp_char != blob_curr && blob_next >= 128) { blob_next = CONST_READ_UI8(++blob); } + if (blob_next < 32) { offsetInf += ((blob_next>>2) & 1) + ((blob_next>>1) & 1) + (blob_next & 1); offsetEq = offsetInf + ((blob_next & 2)?CONST_READ_UI8(blob+1):0); } + if (tmp_char == blob_curr) { if (blob_next < 32) { if (blob_next & 2) { blob += offsetEq; - } else { -#ifndef DISABLE_POST - if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes) + } + else { +#if !defined(DISABLE_POST) || defined(HTTP_AUTH) + if (tmp_connection.protocol.http.parsing_state == parsing_post_attributes) blob = blob_http_header_content; else +#endif +#if defined(HTTP_AUTH) && HTTP_AUTH == HTTP_AUTH_DIGEST + if (tmp_connection.protocol.http.parsing_state == parsing_authorization_username) + break; + else #endif output_handler = &http_404_handler; break; } } break; - } else if (tmp_char < blob_curr) { + } + else if (tmp_char < blob_curr) { if (blob_next < 32 && blob_next & 1) { blob += offsetInf; - } else { -#ifndef DISABLE_POST + } + else { +#if !defined(DISABLE_POST) || defined(HTTP_AUTH) if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes) blob = blob_http_header_content; else +#endif +#if defined(HTTP_AUTH) && HTTP_AUTH == HTTP_AUTH_DIGEST + if (tmp_connection.protocol.http.parsing_state == parsing_authorization_username) + break; + else #endif output_handler = &http_404_handler; break; } - } else { + } + else { if (blob_next < 32 && blob_next & 4) { unsigned char offsetSup = offsetEq + ((blob_next & 3)?CONST_READ_UI8(blob+(offsetInf-1)):0); blob += offsetSup; - } else { -#ifndef DISABLE_POST - if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes) + } + else { +#if !defined(DISABLE_POST) || defined(HTTP_AUTH) + if(tmp_connection.protocol.http.parsing_state == parsing_post_attributes) { blob = blob_http_header_content; + } else +#endif +#if defined(HTTP_AUTH) && HTTP_AUTH == HTTP_AUTH_DIGEST + if (tmp_connection.protocol.http.parsing_state == parsing_authorization_username) + break; + else #endif output_handler = &http_404_handler; break; @@ -1291,6 +1631,7 @@ char smews_receive(void) { } while(1); } } + /* detecting parsing_end */ if( #ifndef DISABLE_POST @@ -1300,41 +1641,84 @@ char smews_receive(void) { #ifndef DISABLE_POST || output_handler == &http_505_handler #endif - ) && tmp_connection.protocol.http.parsing_state != parsing_cmd)){ +#ifdef HTTP_AUTH + || output_handler == &http_401_handler +#endif + ) && tmp_connection.protocol.http.parsing_state != parsing_cmd)) { tmp_connection.protocol.http.parsing_state = parsing_end; tmp_connection.protocol.http.ready_to_send = 1; } - if(!output_handler) + + if(!output_handler) { tmp_connection.protocol.http.blob = blob; + } else { - #ifdef HTTP_AUTH - /* HTTP-Auth: If the requested handler is restricted, we compare the - * given credentials with expected ones. In case of failure, the - * output handler will be set on 401 handler, and requested resource - * index (blob - 128) will be stored into the blob field of the struct. */ - if (IS_RESTRICTED(output_handler)) { - tmp_connection.protocol.http.blob = (unsigned char*)(int)tmp_blob; + char stale; + + /* [HTTP-Auth]: + * If the requested handler is restricted, we set the output handler + * on 401 if credentials are bad or missing, in which case we need to + * store the requested resource's handler into the blob field of the + * connection structure, in order to be able to determine when sending + * later which uri was requested. */ + if (IS_RESTRICTED (output_handler) + && output_handler != &http_401_handler + && output_handler != &http_404_handler +#ifndef DISABLE_POST + && output_handler != &http_505_handler +#endif +#if HTTP_AUTH == HTTP_AUTH_BASIC + && (!auth_data.credentials + || (http_basic_authenticate (output_handler, + (unsigned const char *)(auth_data.credentials)) + != HTTP_AUTHENTICATE_SUCCESS)) +#elif HTTP_AUTH == HTTP_AUTH_DIGEST + && (!auth_data.scheme_parsed + || auth_data.username_blob < 128 + || auth_data.nonce[NONCE_LEN - 1] == '\0' + || auth_data.response[MD5_DIGEST_LEN - 1] == '\0' + || (stale = http_digest_authenticate (connection, + output_handler, + &auth_data) + ) != HTTP_AUTHENTICATE_SUCCESS) +#endif + ) { + + tmp_connection.protocol.http.blob = (unsigned char*)output_handler; output_handler = &http_401_handler; + + /* Set ready_to_send and parsing_state on correct values if not. */ + if (!tmp_connection.protocol.http.ready_to_send) + tmp_connection.protocol.http.ready_to_send = 1; + if (tmp_connection.protocol.http.parsing_state != parsing_end) + tmp_connection.protocol.http.parsing_state = parsing_end; + +#if HTTP_AUTH == HTTP_AUTH_BASIC + /* Compute credentials length and free memory */ + if (auth_data.credentials) { + uint8_t i = 0; + while (auth_data.credentials[i++] != '\0'); /* Don't forget to count the '\0' */ + mem_free (auth_data.credentials, i * sizeof (char)); + } +#endif } #endif - + if (tmp_connection.protocol.http.parsing_state != parsing_cmd) { tmp_connection.output_handler = output_handler; UI32(tmp_connection.protocol.http.next_outseqno) = UI32(current_inack); if (CONST_UI8(output_handler->handler_type) == type_file) { - #ifdef HTTP_AUTH - /* HTTP-Auth: Here, we need to compute the full length of the file, - * including the missing header that we dynamically generate. To do - * so, we need to pick the length of this header, add it the length - * of realm (and nonce if digest auth), before getting the file's blob - * size. */ + /* [HTTP-Auth]: + * Here, we need to compute the full length of the file, including + * the missing header that we dynamically generate. Please refer to + * http_401_handler_length definitions in auth.c for more details. */ if (output_handler == &http_401_handler) { UI32(tmp_connection.protocol.http.final_outseqno) = UI32(tmp_connection.protocol.http.next_outseqno) - + http_401_handler_length((struct output_handler_t*)tmp_connection.protocol.http.blob); + + http_401_handler_length ((struct output_handler_t*)tmp_connection.protocol.http.blob, 0); } else { UI32(tmp_connection.protocol.http.final_outseqno) = @@ -1346,17 +1730,18 @@ char smews_receive(void) { UI32(tmp_connection.protocol.http.next_outseqno) + CONST_UI32(GET_FILE(output_handler).length); #endif - } else { UI32(tmp_connection.protocol.http.final_outseqno) = UI32(tmp_connection.protocol.http.next_outseqno) - 1; } + #ifndef DISABLE_COMET tmp_connection.protocol.http.comet_send_ack = CONST_UI8(output_handler->handler_comet) ? 1 : 0; tmp_connection.protocol.http.comet_passive = 0; tmp_connection.protocol.http.comet_streaming = 0; #endif } + if(tmp_connection.protocol.http.parsing_state == parsing_end || tmp_connection.protocol.http.parsing_state == parsing_cmd){ #ifndef DISABLE_POST /* cleaning memory */ @@ -1376,6 +1761,7 @@ char smews_receive(void) { } tmp_connection.protocol.http.post_data->boundary = NULL; } + if(output_handler == &http_404_handler #ifndef DISABLE_POST || output_handler == &http_505_handler @@ -1398,6 +1784,10 @@ char smews_receive(void) { tmp_connection.protocol.http.parsing_state = parsing_out; #ifdef HTTP_AUTH + /* [HTTP-Auth]: + * If output_handler is the 401 handler, we cannot reset the blob field + * of the temporary connection structure for now, 'cause we need to + * keep a reference to the originally requested handler. */ if (output_handler != &http_401_handler) tmp_connection.protocol.http.blob = blob_http_rqt; #else @@ -1412,6 +1802,7 @@ char smews_receive(void) { tmp_connection.protocol.http.blob = blob; } } + /* drop remaining TCP data */ while(x++ < segment_length) DEV_GETC(tmp_char); @@ -1468,7 +1859,7 @@ char smews_receive(void) { tmp_connection.next = connection->next; } } - + if(!connection) { /* no valid connection has been found for this packet, send a reset */ UI32(tmp_connection.protocol.http.next_outseqno) = UI32(current_inack); @@ -1480,8 +1871,9 @@ char smews_receive(void) { UI16(rst_connection.port) = UI16(tmp_connection.protocol.http.port); UI32(rst_connection.current_inseqno) = UI32(tmp_connection.protocol.http.current_inseqno); UI32(rst_connection.next_outseqno) = UI32(tmp_connection.protocol.http.next_outseqno); - } else { - if(tmp_connection.protocol.http.tcp_state == tcp_listen) { + } + else { + if (tmp_connection.protocol.http.tcp_state == tcp_listen) { #ifdef DISABLE_COROUTINES if (curr_output.dynamic_service_state != none && tmp_connection.protocol.http.generator_service == curr_output.service) { @@ -1495,12 +1887,17 @@ char smews_receive(void) { } #endif free_connection(connection); - } else { + } + else { /* update the current connection */ *connection = tmp_connection; #ifdef HTTP_AUTH - if (tmp_connection.output_handler == &http_401_handler) + /* [HTTP-Auth]: + * Reset the blob field of the temporary connection structure, + * 'cause it wasn't done before if output handler is 401. */ + if (tmp_connection.output_handler == &http_401_handler) { tmp_connection.protocol.http.blob = blob_http_rqt; + } #endif #ifdef IPV6 @@ -1509,5 +1906,6 @@ char smews_receive(void) { } } } + return 1; } diff --git a/core/output.c b/core/output.c index 79c9718..ea9745d 100644 --- a/core/output.c +++ b/core/output.c @@ -44,8 +44,12 @@ #include "input.h" /* for *_HEADER_SIZE defines */ #include "handlers.h" #include "defines.h" -#include "auth.h" +#ifdef HTTP_AUTH +#include "auth.h" /* For authentication header and nonce control */ +#endif + +#include /* IPV6 Common values */ #ifdef IPV6 @@ -262,6 +266,13 @@ void smews_send_packet (struct connection *connection) unsigned char full_ipv6_addr[16]; #endif +#ifdef HTTP_AUTH + /* Buffer for dynamic header length */ + uint16_t dynamic_header_length; + /* Buffer for static file length */ + uint16_t static_length; +#endif + #ifdef SMEWS_SENDING SMEWS_SENDING; #endif @@ -312,10 +323,18 @@ void smews_send_packet (struct connection *connection) segment_length = file_remaining_bytes > max_out_size ? max_out_size : file_remaining_bytes; #ifdef HTTP_AUTH - if (output_handler == &http_401_handler) - index_in_file = - http_401_handler_length ((struct output_handler_t*)connection->protocol.http.blob) + if (output_handler == &http_401_handler) { + /* Compute generated header length */ + dynamic_header_length = http_401_handler_dynamic_header_length + ((struct output_handler_t*)connection->protocol.http.blob, 0); + /* Compute index in file while aware of the generated length */ + index_in_file = + CONST_UI32 (GET_FILE (output_handler).length) + + dynamic_header_length - file_remaining_bytes; + /* Save static length */ + static_length = segment_length - dynamic_header_length; + } else index_in_file = CONST_UI32 (GET_FILE (output_handler).length) - file_remaining_bytes; #else @@ -402,7 +421,7 @@ void smews_send_packet (struct connection *connection) } else #endif - UI16 (current_checksum) = BASIC_IP_CHK; + UI16 (current_checksum) = BASIC_IP_CHK; checksum_add32 (local_ip_addr); checksum_add16 (segment_length + IP_HEADER_SIZE + TCP_HEADER_SIZE); @@ -547,56 +566,67 @@ void smews_send_packet (struct connection *connection) uint16_t i; uint32_t tmp_sum = 0; - /* TODO: (bacara) Beware of bugs with index_in_file */ uint16_t *tmpptr = - (uint16_t *) CONST_ADDR (GET_FILE (output_handler).chk) + - DIV_BY_CHUNCKS_SIZE (index_in_file); + (uint16_t *) CONST_ADDR (GET_FILE (output_handler).chk) + + DIV_BY_CHUNCKS_SIZE (index_in_file); #ifdef HTTP_AUTH + /* [HTTP-Auth]: + * Checksum the dynamically generated header. Be careful + * about odd alignment solved by space insertion. */ if (output_handler == &http_401_handler) { /* Getting the realm */ - unsigned const char *char_p = - ((struct output_handler_t*)connection->protocol.http.blob)->handler_restriction.realm; + unsigned const char *char_p = + ((struct output_handler_t*)(connection->protocol.http.blob))->handler_restriction.realm; -#if HTTP_AUTH == HTTP_AUTH_BASIC /* Precalculated checksum */ checksum_add16 (HTTP_AUTHENTICATE_HEADER_CHK); /* Checksum the double quoted realm */ - checksum_add ('\"'); - while (*char_p) + checksum_add ('"'); + while (*char_p){ checksum_add (*char_p++); - checksum_add ('\"'); + ++tmp_sum; + } + checksum_add ('"'); + + /* Add a space after the double quote if realm is odd string */ + if (tmp_sum % 2) + checksum_add (0x20); -#elif HTTP_AUTH == HTTP_AUTH_DIGEST - /* Precalculated checksum */ - checksum_add16 (HTTP_AUTHENTICATE_HEADER_CHK); - - /* Checksum the double quoted realm */ - checksum_add ('\"'); - while (*char_p) - checksum_add (*char_p++); - checksum_add ('\"'); - +#if HTTP_AUTH == HTTP_AUTH_DIGEST /* Precalculated checksum */ checksum_add16 (HTTP_AUTHENTICATE_HEADER_NONCE_CHK); /* Checksum the double quoted nonce */ - char_p = http_auth_nonce; - checksum_add ('\"'); - while (*char_p) - checksum_add (*char_p++); - checksum_add ('\"'); + checksum_add ('"'); + for (i = 0; i < NONCE_LEN; ++i) + checksum_add (nonce_state.nonce[i]); + checksum_add ('"'); #endif } -#endif + else + /* If output_handler is not the 401 handler, static length + * is segment length. */ + static_length = segment_length; + tmp_sum = 0; + for (i = 0; i < GET_NB_BLOCKS (static_length); i++) + { + tmp_sum += CONST_READ_UI16 (tmpptr++); + } + + checksum_add32 ((const unsigned char *) &tmp_sum); + break; +#else for (i = 0; i < GET_NB_BLOCKS (segment_length); i++) { tmp_sum += CONST_READ_UI16 (tmpptr++); } + checksum_add32 ((const unsigned char *) &tmp_sum); break; +#endif } default: /* Should never happen but avoids compile warnings */ return; @@ -648,43 +678,53 @@ void smews_send_packet (struct connection *connection) { /* Send the payload of the packet */ unsigned const char *tmpptr; - #ifdef HTTP_AUTH + unsigned const char *param; uint8_t tmp; - /* Sending the http header */ - DEV_PUTN_CONST (HttpAuthenticateHeader, HTTP_AUTHENTICATE_HEADER_LEN); + if (output_handler == &http_401_handler) { + /* Sending the http header */ + DEV_PUTN_CONST (HttpAuthenticateHeader, HTTP_AUTHENTICATE_HEADER_LEN); - /* Sending the double quoted realm */ - tmpptr = - ((struct output_handler_t*)(connection->protocol.http.blob))->handler_restriction.realm; - tmp = 0; - while (*tmpptr++) - ++tmp; + /* Sending the double quoted realm */ + param = + ((struct output_handler_t*)(connection->protocol.http.blob))->handler_restriction.realm; + tmpptr = param; + tmp = 0; + while (*tmpptr++) + ++tmp; - DEV_PUT ('\"'); - DEV_PUTN_CONST (tmpptr, tmp); - DEV_PUT ('\"'); + DEV_PUT ('"'); + DEV_PUTN_CONST (param, tmp); + DEV_PUT ('"'); -#if HTTP_AUTH != HTTP_AUTH_BASIC - /* Sending the second part of the http header */ - DEV_PUTN_CONST (HttpAuthenticateHeaderNonce, HTTP_AUTHENTICATE_HEADER_NONCE_LEN); + if (tmp % 2) + DEV_PUT (' '); + +#if HTTP_AUTH == HTTP_AUTH_DIGEST + /* Sending the second part of the http header */ + DEV_PUTN_CONST (HttpAuthenticateHeaderNonce, HTTP_AUTHENTICATE_HEADER_NONCE_LEN); - /* Sending the double quoted nonce */ - tmpptr = http_auth_nonce; - tmp = 0; - while (*tmpptr++) - ++tmp; - - DEV_PUT ('\"'); - DEV_PUTN (http_auth_nonce, sizeof (http_auth_nonce)); - DEV_PUT ('\"'); + /* Sending the double quoted nonce */ + DEV_PUT ('"'); + DEV_PUTN (nonce_state.nonce, NONCE_LEN); + DEV_PUT ('"'); #endif + } #endif tmpptr = (unsigned const char*)(CONST_ADDR (GET_FILE (output_handler).data) + index_in_file); +#ifdef HTTP_AUTH + if (output_handler == &http_401_handler) { + DEV_PUTN_CONST (tmpptr, static_length); + } + else { + DEV_PUTN_CONST (tmpptr, segment_length); + } +#else DEV_PUTN_CONST (tmpptr, segment_length); +#endif break; } default: /* Should never happen but avoid warnings */ @@ -763,7 +803,6 @@ char smews_send (void) struct connection *active_connection = NULL; const struct output_handler_t * /*CONST_VAR */ old_output_handler = NULL; - /* sending reset has the highest priority */ if (UI16 (rst_connection.port)) { @@ -963,6 +1002,7 @@ char smews_send (void) * This step has to be done before before any context_restore or context_backup */ if (cr_prepare (&curr_output.service->coroutine) == NULL) return 1; + #else /* If no coroutines, then having smews_send called while in a handler is equivalent to retransmit the buffer (as the type is forced to persistent) diff --git a/core/smews.h b/core/smews.h index d58cef9..edcce16 100644 --- a/core/smews.h +++ b/core/smews.h @@ -43,6 +43,10 @@ #include "timers.h" #include "memory.h" +#if defined(HTTP_AUTH) && HTTP_AUTH == HTTP_AUTH_DIGEST +#include "auth.h" +#endif + extern void smews_retransmit(void); /* Inlinged because used only once */ @@ -102,6 +106,9 @@ static inline void smews_init(void) { #ifndef DISABLE_TIMERS set_timer(&smews_retransmit,1000); #endif +#if defined(HTTP_AUTH) && HTTP_AUTH == HTTP_AUTH_DIGEST + http_auth_gen_nonce(); +#endif } #endif /* __SMEWS_H__ */ diff --git a/tools/GenApps.py b/tools/GenApps.py index 4ccb365..730992a 100755 --- a/tools/GenApps.py +++ b/tools/GenApps.py @@ -34,6 +34,7 @@ import xml.parsers.expat import GenBlob +import GenAuthData import os import gzip import StringIO @@ -42,6 +43,7 @@ import time import datetime import re +import hashlib # initialization propertiesInfos = None @@ -53,8 +55,7 @@ requestList = [] attributListFileName = os.path.join('tools','supportedPostAttributList') attributList = [] -httpAuthTypeListFileName = os.path.join('tools', 'supportedHttpAuthTypeList') -httpAuthTypeList = [] +httpAuthUsedUsernames = GenAuthData.getUsedUsernamesList() contentTypeList = [] postList = [] # doGet:1 doPost:2 doPostIn:4 doPostOut:8 args:16 content-types:32 doPacketIn:64 doPacketOut: 128 @@ -99,13 +100,6 @@ if(len(split) > 0): attributList.append(split[0]) -for line in open(httpAuthTypeListFileName, 'r'): - indice = line.find('#') - if indice >= 0: - line = line[:indice] - if (len(line) > 0): - httpAuthTypeList.append(line) - def generateBlobsH(dstFile): hOut = open(dstFile,'w') writeHeader(hOut,0) @@ -114,6 +108,12 @@ def generateBlobsH(dstFile): GenBlob.genBlobTree(hOut,requestList,'blob_http_rqt',False) GenBlob.genBlobTree(hOut,mimesListPost,'mimes_tree',False) GenBlob.genBlobTree(hOut,attributList,'blob_http_header_content',False) + + userList = [ ] + for user in httpAuthUsedUsernames: + userList.append(user.upper()) + GenBlob.genBlobTree(hOut,userList,'usernames_tree',False) + hOut.write('\n#endif\n') hOut.close() @@ -145,16 +145,6 @@ def writeDefinesH(dstFile): att = att.replace(c,'_'+str(ord(c))+'_') hOut.write('#define \t ATTRIBUT_' + att + '\t' + str(attributList.index(m)) + '\n') - # HTTP_AUTH_BASIC & HTTP_AUTH_DIGEST definitions - value = 0 - for m in httpAuthTypeList: - type = m.upper() - for c in type: - if (re.search('\W', c)): - type = type.replace(c, '_' + str(ord(c)) + '_') - hOut.write('#define \t HTTP_AUTH_' + type + '\t' + str(value) + '\n') - value += 1 - hOut.write('\n#endif\n') hOut.close() @@ -386,18 +376,20 @@ def generateResourceProps(srcFile,dstFileInfos): pOut.close() # launches a Web applicative resource file generation -def generateResource(srcFile,dstFile,chuncksNbits,gzipped,dstFileInfos,realm,offsetCountCouple): +def generateResource(srcFile,dstFile,chuncksNbits,gzipped,dstFileInfos,authType,realm,offsetCountCouple,finalUri): if getResourceType(srcFile) == DynamicResource: - generateDynamicResource(srcFile,dstFile,dstFileInfos,realm,offsetCountCouple) + generateDynamicResource(srcFile,dstFile,dstFileInfos,authType,realm,offsetCountCouple,finalUri) else: - generateStaticResource(srcFile,dstFile,chuncksNbits,gzipped,realm,offsetCountCouple) + generateStaticResource(srcFile,dstFile,chuncksNbits,gzipped,authType,realm,offsetCountCouple,finalUri) # dynamic resources: the generator file is enriched def generateDynamicResource(srcFile, dstFile, dstFileInfos, + authType, realm, - offsetCountCouple): + offsetCountCouple, + finalUri): # extract the properties from the XML (if needed) fileData = extractPropsFromXml(srcFile,dstFileInfos) # if the c/h file does not conatin any XML, we simply copy it: this is not a generator @@ -551,18 +543,45 @@ def generateDynamicResource(srcFile, generatedOutputHandler += '\t.handler_restriction = {\n' generatedOutputHandler += '\t\t.realm = (unsigned char*)&' + realmVar.lower() + ',\n' generatedOutputHandler += '\t\t.credentials_offset = ' + str(offsetCountCouple[0]) + ',\n' - generatedOutputHandler += '\t\t.credentials_count = ' + str(offsetCountCouple[1]) + '\n' + generatedOutputHandler += '\t\t.credentials_count = ' + str(offsetCountCouple[1]) + if authType == 'digest': + generatedOutputHandler += ',\n' + # Setting method string + if (dstFileInfos.has_key('doPost') + or dstFileInfos.has_key('doPostIn') + or dstFileInfos.has_key('doPostOut')): + method = 'POST' + else: + method = 'GET' + + (fileName, fileExt) = os.path.splitext(finalUri) + if (fileExt == '.c' or fileExt == '.h'): + print 'URI EXTENSION STRIP !!!' + finalUri = fileName + md5 = hashlib.md5() + md5.update('%s:%s' % (method, finalUri)) + hexdigest = md5.hexdigest() + generatedOutputHandler += '\t\t.resource_digest = "%s"\n' % hexdigest + # DEBUG PURPOSE + print 'MD5 Digest: %s (%s:%s)' % (hexdigest, method, finalUri) + else: + generatedOutputHandler += "\n" + generatedOutputHandler += '\t}\n' else: generatedOutputHandler += '\t.handler_restriction = {\n' generatedOutputHandler += '\t\t.realm = NULL,\n' generatedOutputHandler += '\t\t.credentials_offset = 0,\n' - generatedOutputHandler += '\t\t.credentials_count = 0\n' + generatedOutputHandler += '\t\t.credentials_count = 0' + if authType == 'digest': + generatedOutputHandler += ',\n' + generatedOutputHandler += '\t\t.resource_digest = ""\n' + else: + generatedOutputHandler += '\n' generatedOutputHandler += '\t}\n' generatedOutputHandler += '#endif\n' - generatedOutputHandler += '};\n' if (dstFileInfos.has_key('doPost') or dstFileInfos.has_key('doPostIn')): @@ -645,7 +664,7 @@ def generateDynamicResource(srcFile, # static file: the file is completely pre-processed into a c file: # HTTP header insertion # chuncks checksums calculation -def generateStaticResource(srcFile,dstFile,chuncksNbits,gzipped,realm,offsetCountCouple): +def generateStaticResource(srcFile,dstFile,chuncksNbits,gzipped,authType,realm,offsetCountCouple,finalUri): # HTTP header generation and concatenation with file data try: # open the source file @@ -739,14 +758,26 @@ def generateStaticResource(srcFile,dstFile,chuncksNbits,gzipped,realm,offsetCoun cOut.write('\t.handler_restriction = {\n' + '\t\t.realm = (unsigned char*)&' + realmVar.lower() + ',\n' + '\t\t.credentials_offset = ' + str(offsetCountCouple[0]) + ',\n' - + '\t\t.credentials_count = ' + str(offsetCountCouple[1]) + '\n' - + '\t}\n') + + '\t\t.credentials_count = ' + str(offsetCountCouple[1]) + ',\n') + if authType == 'digest': + m = hashlib.md5() + m.update('%s:%s' % ('GET', finalUri)) + hexdigest = m.hexdigest() + cOut.write('\t\t.resource_digest = "%s"\n' % hexdigest) + # DEBUG PURPOSE + print 'MD5 Digest: %s (GET:%s)' % (hexdigest, finalUri) + cOut.write('\t}\n') else: cOut.write('\t.handler_restriction = {\n' + '\t\t.realm = NULL,\n' + '\t\t.credentials_offset = 0,\n' - + '\t\t.credentials_count = 0\n' - + '\t}\n') + + '\t\t.credentials_count = 0') + if authType == 'digest': + cOut.write(',\n\t\t.resource_digest = ""\n') + else: + cOut.write('\n') + cOut.write('\t}\n') + cOut.write('#endif\n') cOut.write('};\n') diff --git a/tools/GenAuthData.py b/tools/GenAuthData.py index 067eb13..716bc8b 100644 --- a/tools/GenAuthData.py +++ b/tools/GenAuthData.py @@ -4,8 +4,10 @@ import base64 import re +import hashlib import GenApps +import GenBlob # -------------------------------------------------- # # Class: User @@ -20,8 +22,8 @@ def toBasicHTTPAuthString(self): # -------------------------------------------------- # # Script initialisation -authenticationType = 'basic' # 'basic' | 'digest' -realmUrisMap = { } # Read parseAuthData description for details +authenticationType = '' # 'basic' | 'digest' +realmUrisMap = { } # Read parseAuthData description for details # XML Tag Names rootNodeTag = "http-authentication" @@ -31,9 +33,13 @@ def toBasicHTTPAuthString(self): realmNodeTag = "realm" uriNodeTag = "uri" +# Effectively used usernames list +usedUsernames = [ ] + # parseAuthData(xmlFilename): # Parse authentication data from the corresponding XML file. -def parseAuthData(xmlFilename): +def parseXMLFile(xmlFilename): + global authenticationType # Getting XML document xmlDoc = minidom.parse(xmlFilename) @@ -57,21 +63,15 @@ def parseAuthData(xmlFilename): restNode = restNodeList[0] # Checking authentication type - authType = rootNode.attributes['type'].value.lower() - - if (authType.lower() == 'basic'): # Basic Authentication - authenticationType = authType - parseBasicHTTPAuthData(restNode, parseUsersList(authNode)) - - elif (authType.lower() == 'digest'): # Digest authentication -# authenticationType = authType - print 'Digest HTTP Authentication Data parsing is not yet supported' + authenticationType = rootNode.attributes['type'].value.lower() - else: # Not handled - print 'Unknown HTTP authentication type : %s' % (authType) - exit() + if authenticationType != 'basic' and authenticationType != 'digest': + print '%s: Unknown authentication scheme: %s' % (xmlFilename, authenticationType) + exit() + + parseAuthData(restNode, parseUsersList(authNode)) - return authType + return authenticationType # parseUsersList(authorizationNode): # Parse the authorization node and return a list of User objects corresponding @@ -99,13 +99,13 @@ def parseUsersList(authorizationNode): return usersList -# parseBasicHTTPAuthData(realmNodeList, usersList): # Parse the data corresponding to the specified restriction node, and # build the data structure according to the previously parsed users list. # # The returned data structure looks like this : # -# val = { 'realm' : [ { 'uri' : [credentials] } ] } +# /!\ HTTP BASIC AUTHENTICATION /!\ +# ret = { 'realm' : [ { 'uri' : [credentials] } ] } # where credentials is the base 64 encoding of the string "username:passwd" # . # |- realm1 @@ -119,12 +119,33 @@ def parseUsersList(authorizationNode): # |- credentials4 # `- credentials5 # -def parseBasicHTTPAuthData(restrictionNode, usersList): +# /!\ HTTP DIGEST AUTHENTICATION /*\ +# ret = { ('users' | 'realms') } +# where the value associated with users is a dictionnary with username as key +# and password as value +# AND +# where the value associated with realms is the same data structure as for +# basic authentication, except about the credentials list which becomes now +# a username list. +# . +# |- users +# | `- { username : password } +# `- realms +# |- uri1 +# | |- user1 +# | `- user2 +# |- uri2 +# | `- user1 +# `- uri3 +# |- user1 +# `- user3 +# +def parseAuthData(restrictionNode, usersList): global realmUrisMap realmNodeList = restrictionNode.getElementsByTagName(realmNodeTag) mapBuilt = { } - # Parsing each restriction list (1xlist/realm) + # Parsing each restriction list (1x list/realm) for realmNode in realmNodeList: uriCredentialsMap = { } realmName = realmNode.attributes['name'].value @@ -158,17 +179,42 @@ def parseBasicHTTPAuthData(restrictionNode, usersList): # Getting corresponding string for user in usersList: if user.username == username: - credentials.append(user.toBasicHTTPAuthString()) + if authenticationType == 'basic': + credentials.append(user.toBasicHTTPAuthString()) + elif authenticationType == 'digest': + credentials.append(username) uriCredentialsMap[uri] = credentials mapBuilt[realmName] = uriCredentialsMap + if authenticationType == 'digest': + digestMap = { } + digestMap['users'] = { } + for user in usersList: + digestMap['users'][user.username] = user.password + digestMap['realms'] = mapBuilt; + mapBuilt = digestMap; + + # DEBUG PURPOSE +# print 'USERS:' +# for user in mapBuilt['users']: +# print '\t' + user + ' : ' + mapBuilt['users'][user] +# +# print 'REALMS:' +# for realm in mapBuilt['realms']: +# print '\t' + realm + ':' +# for uri in mapBuilt['realms'][realm]: +# print '\t\t' + uri +# for user in mapBuilt['realms'][realm][uri]: +# print '\t\t\t' + user + realmUrisMap = mapBuilt # Saving result for code generation purpose return mapBuilt def genHTTPAuthDataFile(dstFile): global realmUrisMap + global authenticationType realmUrisCredentialsDataMap = { } uriDatasMap = { } @@ -177,11 +223,12 @@ def genHTTPAuthDataFile(dstFile): generatedHeader = '' generatedCode = '' - defHeader = '__HTTP_AUTH_DATA_H__' # TODO: Automated gen + defHeader = '__HTTP_AUTH_DATA_H__' generatedHeader += '#ifndef ' + defHeader + '\n' generatedHeader += '#define ' + defHeader + '\n' generatedHeader += '\n' generatedHeader += '#include "types.h"\n' + generatedHeader += '#include "auth.h"\n' generatedHeader += '\n' generatedCode += '#include "http_auth_data.h"\n\n' @@ -189,8 +236,8 @@ def genHTTPAuthDataFile(dstFile): # Basic authentication if (authenticationType == 'basic'): credentialsList = [] # Ordered list of credentials, app by app - credentialsVarMap = { } # Dictionnary where key is a credentials, - # and value the credentials C string name. + credentialsVarMap = { } # Dictionnary where key is a credentials string, + # and value the credentials' C identifier string. credentialsCount = 0 # Write realm string declarations @@ -247,17 +294,102 @@ def genHTTPAuthDataFile(dstFile): generatedCode += commentary generatedHeader += 'extern CONST_VAR(%s*, http_auth_basic_credentials_data[]);' % (uChar) - generatedCode += 'CONST_VAR(%s*, http_auth_basic_credentials_data[]) = {\n' %(uChar) + generatedCode += 'CONST_VAR(%s *, http_auth_basic_credentials_data[]) = {\n' %(uChar) for credentials in credentialsList: - generatedCode += '\t(%s*)&%s,\n' % (uChar, credentialsVarMap[credentials]) + generatedCode += '\t(%s *)&%s,\n' % (uChar, credentialsVarMap[credentials]) generatedHeader += '\n' generatedCode += '};\n' # Digest authentication elif (authenticationType == 'digest'): - print 'HTTP Digest Authentication data generation is not yet supported.' - exit() + credentialsByUserRealm = { } # Key is (user,realm) + + # Write realm string declarations + commentary = '/*********** Realm List ***********/\n' + generatedHeader += commentary + generatedCode += commentary + + realmVars = { } # Will store the name of the character string for realms + realmUsers = { } # Will store the usernames used for each realm + global usedUsernames + + for realm in realmUrisMap['realms']: + # Building the string identifier + realmVar = realm.lower() + for c in realmVar: + if (re.search('\W', c)): + realmVar = realmVar.replace(c, '_' + str(ord(c)) + '_') + # Generating the string + generatedHeader += 'extern CONST_VAR(%s, %s[]);\n' % (uChar, realmVar) + generatedCode += 'CONST_VAR(%s, %s[]) = "%s";\n' % (uChar, realmVar, realm) + + realmVars[realm] = realmVar + + # Building the list of users per realm + realmUsers[realm] = [ ] + for uri in realmUrisMap['realms'][realm]: + for user in realmUrisMap['realms'][realm][uri]: + realmUsers[realm].append(user) + if user not in usedUsernames: + usedUsernames.append(user) + + # Building the map where (user,realm) is a key corresponding to a credentials digest + for realm in realmUsers: + for user in realmUsers[realm]: + m = hashlib.md5() + m.update(user + ':' + realm + ':' + realmUrisMap['users'][user]) + credentialsByUserRealm[(user,realm)] = m.hexdigest() + + # Write credentials digest struct table + commentary = '\n/*********** Credentials digest table ***********/\n' + generatedHeader += commentary + generatedCode += commentary + + generatedHeader += '#define USER_DIGEST_COUNT %s\n' % len(credentialsByUserRealm) + + generatedHeader += 'extern CONST_VAR(%s, %s[]);\n' % ('credentials_digest_t', 'http_auth_digest_credentials_data') + + generatedCode += 'CONST_VAR(%s, %s[]) = {\n' % ('credentials_digest_t', 'http_auth_digest_credentials_data') + + for key in credentialsByUserRealm: + generatedCode += '\t{\n' + generatedCode += '\t\t.username_blob = %s,\n' % str(usedUsernames.index(key[0])) + generatedCode += '\t\t.realm = (unsigned const char *)&%s,\n' % realmVars[key[1]] + generatedCode += '\t\t.user_digest = "%s"\n' % credentialsByUserRealm[key] + generatedCode += '\t},\n' + + generatedCode += '};\n' + + # Write credentials table + commentary = '\n/*********** Credentials table ***********/\n' + generatedHeader += commentary + generatedCode += commentary + + generatedHeader += 'extern CONST_VAR(%s, %s[]);\n' % ('uint8_t', 'http_auth_digest_credentials_table') + + generatedCode += 'CONST_VAR(%s, %s[]) = {\n' % ('uint8_t', 'http_auth_digest_credentials_table') + offset = 0; + + for realm in realmUrisMap['realms']: + uriDatasMap = { } + for uri in realmUrisMap['realms'][realm]: + count = 0; + for user in realmUrisMap['realms'][realm][uri]: + generatedCode += '\t%s, /* %s => %s */\n' % (usedUsernames.index(user), uri, user) + count += 1 + + uriDatasMap[uri] = (offset, count) + offset += count + + realmUrisCredentialsDataMap[realm] = uriDatasMap + + generatedCode += '};\n' + + generatedHeader += '\n/*********** Username blobs ***********/\n' + for u in usedUsernames: + user = u.upper() + generatedHeader += '#define HTTP_AUTH_USERNAME_%s\t%s\n' % (user, usedUsernames.index(u)) generatedHeader += '\n#endif\n' @@ -270,3 +402,7 @@ def genHTTPAuthDataFile(dstFile): cFile.write(generatedCode) return realmUrisCredentialsDataMap + +def getUsedUsernamesList(): + global userUsernames + return usedUsernames diff --git a/tools/access.xml b/tools/access.xml index 58b2292..a4cfeae 100644 --- a/tools/access.xml +++ b/tools/access.xml @@ -1,11 +1,18 @@ - + - + + + + - - + + + + + - \ No newline at end of file + diff --git a/tools/supportedHttpAuthTypeList b/tools/supportedHttpAuthTypeList index f1e935d..29e858b 100644 --- a/tools/supportedHttpAuthTypeList +++ b/tools/supportedHttpAuthTypeList @@ -1 +1,2 @@ -Basic \ No newline at end of file +Basic +Digest \ No newline at end of file diff --git a/tools/supportedPostAttributList b/tools/supportedPostAttributList index cc2dcc3..26602c0 100644 --- a/tools/supportedPostAttributList +++ b/tools/supportedPostAttributList @@ -1,4 +1,8 @@ CONTENT-TYPE CONTENT-LENGTH +AUTHORIZATION: FILENAME= BOUNDARY= +USERNAME= +NONCE= +RESPONSE= From 27c771ac12157dbe94af3d8e52fc225e89e9c3f0 Mon Sep 17 00:00:00 2001 From: bacara Date: Fri, 30 Aug 2013 15:03:19 +0200 Subject: [PATCH 4/4] Final version of http authentication * Security breach in . Refer to the code for more details. * Depending on the disabled options and the runtime activity, Smews may crash sometimes, due to issues inside . * Userguide has been updated. --- tools/access.xml => accessfile_example.xml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/access.xml => accessfile_example.xml (100%) diff --git a/tools/access.xml b/accessfile_example.xml similarity index 100% rename from tools/access.xml rename to accessfile_example.xml