build: omit forward declarations
No binary (build failed)
—
(log yok)
| @@ -53,39 +53,6 @@ | |
| * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. | |
| */ | |
| - | static int inet_pton4(const char *src, unsigned char *dst); |
| - | static int inet_pton6(const char *src, unsigned char *dst); |
| - | |
| - | /* int |
| - | * inet_pton(af, src, dst) |
| - | * convert from presentation format (which usually means ASCII printable) |
| - | * to network format (which is usually some kind of binary format). |
| - | * return: |
| - | * 1 if the address was valid for the specified address family |
| - | * 0 if the address was not valid (`dst' is untouched in this case) |
| - | * -1 if some other error occurred (`dst' is untouched in this case, too) |
| - | * notice: |
| - | * On Windows we store the error in the thread errno, not |
| - | * in the Winsock error code. This is to avoid losing the |
| - | * actual last Winsock error. When this function returns |
| - | * -1, check errno not SOCKERRNO. |
| - | * author: |
| - | * Paul Vixie, 1996. |
| - | */ |
| - | int curlx_inet_pton(int af, const char *src, void *dst) |
| - | { |
| - | switch(af) { |
| - | case AF_INET: |
| - | return inet_pton4(src, (unsigned char *)dst); |
| - | case AF_INET6: |
| - | return inet_pton6(src, (unsigned char *)dst); |
| - | default: |
| - | errno = SOCKEAFNOSUPPORT; |
| - | return -1; |
| - | } |
| - | /* NOTREACHED */ |
| - | } |
| - | |
| /* int | |
| * inet_pton4(src, dst) | |
| * like inet_aton() but without all the hexadecimal and shorthand. | |
| @@ -225,4 +192,34 @@ static int inet_pton6(const char *src, unsigned char *dst) | |
| return 1; | |
| } | |
| + | /* int |
| + | * inet_pton(af, src, dst) |
| + | * convert from presentation format (which usually means ASCII printable) |
| + | * to network format (which is usually some kind of binary format). |
| + | * return: |
| + | * 1 if the address was valid for the specified address family |
| + | * 0 if the address was not valid (`dst' is untouched in this case) |
| + | * -1 if some other error occurred (`dst' is untouched in this case, too) |
| + | * notice: |
| + | * On Windows we store the error in the thread errno, not |
| + | * in the Winsock error code. This is to avoid losing the |
| + | * actual last Winsock error. When this function returns |
| + | * -1, check errno not SOCKERRNO. |
| + | * author: |
| + | * Paul Vixie, 1996. |
| + | */ |
| + | int curlx_inet_pton(int af, const char *src, void *dst) |
| + | { |
| + | switch(af) { |
| + | case AF_INET: |
| + | return inet_pton4(src, (unsigned char *)dst); |
| + | case AF_INET6: |
| + | return inet_pton6(src, (unsigned char *)dst); |
| + | default: |
| + | errno = SOCKEAFNOSUPPORT; |
| + | return -1; |
| + | } |
| + | /* NOTREACHED */ |
| + | } |
| + | |
| #endif /* HAVE_INET_PTON */ | |
| @@ -112,8 +112,6 @@ | |
| * CURLRES_* defines based on the config*.h and curl_setup.h defines. | |
| */ | |
| - | static void dnscache_entry_free(struct Curl_dns_entry *dns); |
| - | |
| #ifndef CURL_DISABLE_VERBOSE_STRINGS | |
| static void show_resolve_info(struct Curl_easy *data, | |
| struct Curl_dns_entry *dns); | |
| @@ -121,6 +119,18 @@ static void show_resolve_info(struct Curl_easy *data, | |
| #define show_resolve_info(x, y) Curl_nop_stmt | |
| #endif | |
| + | static void dnscache_entry_free(struct Curl_dns_entry *dns) |
| + | { |
| + | Curl_freeaddrinfo(dns->addr); |
| + | #ifdef USE_HTTPSRR |
| + | if(dns->hinfo) { |
| + | Curl_httpsrr_cleanup(dns->hinfo); |
| + | curlx_free(dns->hinfo); |
| + | } |
| + | #endif |
| + | curlx_free(dns); |
| + | } |
| + | |
| /* | |
| * Curl_printable_address() stores a printable version of the 1st address | |
| * given in the 'ai' argument. The result will be stored in the buf that is | |
| @@ -1160,18 +1170,6 @@ clean_up: | |
| return result; | |
| } | |
| - | static void dnscache_entry_free(struct Curl_dns_entry *dns) |
| - | { |
| - | Curl_freeaddrinfo(dns->addr); |
| - | #ifdef USE_HTTPSRR |
| - | if(dns->hinfo) { |
| - | Curl_httpsrr_cleanup(dns->hinfo); |
| - | curlx_free(dns->hinfo); |
| - | } |
| - | #endif |
| - | curlx_free(dns); |
| - | } |
| - | |
| /* | |
| * Curl_resolv_unlink() releases a reference to the given cached DNS entry. | |
| * When the reference count reaches 0, the entry is destroyed. It is important | |
| @@ -62,20 +62,6 @@ struct pair { | |
| struct dynbuf value; | |
| }; | |
| - | static void dyn_array_free(struct dynbuf *db, size_t num_elements); |
| - | static void pair_array_free(struct pair *pair_array, size_t num_elements); |
| - | static CURLcode split_to_dyn_array(const char *source, |
| - | struct dynbuf db[MAX_QUERY_COMPONENTS], |
| - | size_t *num_splits); |
| - | static bool is_reserved_char(const char c); |
| - | static CURLcode uri_encode_path(struct Curl_str *original_path, |
| - | struct dynbuf *new_path); |
| - | static CURLcode encode_query_component(char *component, size_t len, |
| - | struct dynbuf *db); |
| - | static CURLcode http_aws_decode_encode(const char *in, size_t in_len, |
| - | struct dynbuf *out); |
| - | static bool should_urlencode(struct Curl_str *service_name); |
| - | |
| static void sha256_to_hex(char *dst, unsigned char *sha) | |
| { | |
| Curl_hexencode(sha, CURL_SHA256_DIGEST_LENGTH, | |
| @@ -129,6 +115,171 @@ static void trim_headers(struct curl_slist *head) | |
| } | |
| } | |
| + | /* |
| + | * Frees all allocated strings in a dynbuf pair array, and the dynbuf itself |
| + | */ |
| + | static void pair_array_free(struct pair *pair_array, size_t num_elements) |
| + | { |
| + | size_t index; |
| + | |
| + | for(index = 0; index != num_elements; index++) { |
| + | curlx_dyn_free(&pair_array[index].key); |
| + | curlx_dyn_free(&pair_array[index].value); |
| + | } |
| + | } |
| + | |
| + | /* |
| + | * Frees all allocated strings in a split dynbuf, and the dynbuf itself |
| + | */ |
| + | static void dyn_array_free(struct dynbuf *db, size_t num_elements) |
| + | { |
| + | size_t index; |
| + | |
| + | for(index = 0; index < num_elements; index++) |
| + | curlx_dyn_free((&db[index])); |
| + | } |
| + | |
| + | /* |
| + | * Splits source string by SPLIT_BY, and creates an array of dynbuf in db. |
| + | * db is initialized by this function. |
| + | * Caller is responsible for freeing the array elements with dyn_array_free |
| + | */ |
| + | |
| + | #define SPLIT_BY '&' |
| + | |
| + | static CURLcode split_to_dyn_array(const char *source, |
| + | struct dynbuf db[MAX_QUERY_COMPONENTS], |
| + | size_t *num_splits_out) |
| + | { |
| + | CURLcode result = CURLE_OK; |
| + | size_t len = strlen(source); |
| + | size_t pos; /* Position in result buffer */ |
| + | size_t start = 0; /* Start of current segment */ |
| + | size_t segment_length = 0; |
| + | size_t index = 0; |
| + | size_t num_splits = 0; |
| + | |
| + | /* Split source_ptr on SPLIT_BY and store the segment offsets and length in |
| + | * array */ |
| + | for(pos = 0; pos < len; pos++) { |
| + | if(source[pos] == SPLIT_BY) { |
| + | if(segment_length) { |
| + | curlx_dyn_init(&db[index], segment_length + 1); |
| + | result = curlx_dyn_addn(&db[index], &source[start], segment_length); |
| + | if(result) |
| + | goto fail; |
| + | |
| + | segment_length = 0; |
| + | index++; |
| + | if(++num_splits == MAX_QUERY_COMPONENTS) { |
| + | result = CURLE_TOO_LARGE; |
| + | goto fail; |
| + | } |
| + | } |
| + | start = pos + 1; |
| + | } |
| + | else { |
| + | segment_length++; |
| + | } |
| + | } |
| + | |
| + | if(segment_length) { |
| + | curlx_dyn_init(&db[index], segment_length + 1); |
| + | result = curlx_dyn_addn(&db[index], &source[start], segment_length); |
| + | if(!result) { |
| + | if(++num_splits == MAX_QUERY_COMPONENTS) |
| + | result = CURLE_TOO_LARGE; |
| + | } |
| + | } |
| + | fail: |
| + | *num_splits_out = num_splits; |
| + | return result; |
| + | } |
| + | |
| + | static bool is_reserved_char(const char c) |
| + | { |
| + | return (ISALNUM(c) || ISURLPUNTCS(c)); |
| + | } |
| + | |
| + | static CURLcode uri_encode_path(struct Curl_str *original_path, |
| + | struct dynbuf *new_path) |
| + | { |
| + | const char *p = curlx_str(original_path); |
| + | size_t i; |
| + | |
| + | for(i = 0; i < curlx_strlen(original_path); i++) { |
| + | /* Do not encode slashes or unreserved chars from RFC 3986 */ |
| + | CURLcode result = CURLE_OK; |
| + | unsigned char c = p[i]; |
| + | if(is_reserved_char(c) || c == '/') |
| + | result = curlx_dyn_addn(new_path, &c, 1); |
| + | else |
| + | result = curlx_dyn_addf(new_path, "%%%02X", c); |
| + | if(result) |
| + | return result; |
| + | } |
| + | |
| + | return CURLE_OK; |
| + | } |
| + | |
| + | static CURLcode encode_query_component(char *component, size_t len, |
| + | struct dynbuf *db) |
| + | { |
| + | size_t i; |
| + | for(i = 0; i < len; i++) { |
| + | CURLcode result = CURLE_OK; |
| + | unsigned char this_char = component[i]; |
| + | |
| + | if(is_reserved_char(this_char)) |
| + | /* Escape unreserved chars from RFC 3986 */ |
| + | result = curlx_dyn_addn(db, &this_char, 1); |
| + | else if(this_char == '+') |
| + | /* Encode '+' as space */ |
| + | result = curlx_dyn_add(db, "%20"); |
| + | else |
| + | result = curlx_dyn_addf(db, "%%%02X", this_char); |
| + | if(result) |
| + | return result; |
| + | } |
| + | |
| + | return CURLE_OK; |
| + | } |
| + | |
| + | /* |
| + | * Populates a dynbuf containing url_encode(url_decode(in)) |
| + | */ |
| + | static CURLcode http_aws_decode_encode(const char *in, size_t in_len, |
| + | struct dynbuf *out) |
| + | { |
| + | char *out_s; |
| + | size_t out_s_len; |
| + | CURLcode result = |
| + | Curl_urldecode(in, in_len, &out_s, &out_s_len, REJECT_NADA); |
| + | |
| + | if(!result) { |
| + | result = encode_query_component(out_s, out_s_len, out); |
| + | Curl_safefree(out_s); |
| + | } |
| + | return result; |
| + | } |
| + | |
| + | static bool should_urlencode(struct Curl_str *service_name) |
| + | { |
| + | /* |
| + | * These services require unmodified (not additionally URL-encoded) URL |
| + | * paths. |
| + | * should_urlencode == true is equivalent to should_urlencode_uri_path |
| + | * from the AWS SDK. Urls are already normalized by the curl URL parser |
| + | */ |
| + | |
| + | if(curlx_str_cmp(service_name, "s3") || |
| + | curlx_str_cmp(service_name, "s3-express") || |
| + | curlx_str_cmp(service_name, "s3-outposts")) { |
| + | return false; |
| + | } |
| + | return true; |
| + | } |
| + | |
| /* maximum length for the aws sivg4 parts */ | |
| #define MAX_SIGV4_LEN 64 | |
| #define DATE_HDR_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Date")) | |
| @@ -973,172 +1124,4 @@ fail: | |
| return result; | |
| } | |
| - | /* |
| - | * Frees all allocated strings in a dynbuf pair array, and the dynbuf itself |
| - | */ |
| - | |
| - | static void pair_array_free(struct pair *pair_array, size_t num_elements) |
| - | { |
| - | size_t index; |
| - | |
| - | for(index = 0; index != num_elements; index++) { |
| - | curlx_dyn_free(&pair_array[index].key); |
| - | curlx_dyn_free(&pair_array[index].value); |
| - | } |
| - | } |
| - | |
| - | /* |
| - | * Frees all allocated strings in a split dynbuf, and the dynbuf itself |
| - | */ |
| - | |
| - | static void dyn_array_free(struct dynbuf *db, size_t num_elements) |
| - | { |
| - | size_t index; |
| - | |
| - | for(index = 0; index < num_elements; index++) |
| - | curlx_dyn_free((&db[index])); |
| - | } |
| - | |
| - | /* |
| - | * Splits source string by SPLIT_BY, and creates an array of dynbuf in db. |
| - | * db is initialized by this function. |
| - | * Caller is responsible for freeing the array elements with dyn_array_free |
| - | */ |
| - | |
| - | #define SPLIT_BY '&' |
| - | |
| - | static CURLcode split_to_dyn_array(const char *source, |
| - | struct dynbuf db[MAX_QUERY_COMPONENTS], |
| - | size_t *num_splits_out) |
| - | { |
| - | CURLcode result = CURLE_OK; |
| - | size_t len = strlen(source); |
| - | size_t pos; /* Position in result buffer */ |
| - | size_t start = 0; /* Start of current segment */ |
| - | size_t segment_length = 0; |
| - | size_t index = 0; |
| - | size_t num_splits = 0; |
| - | |
| - | /* Split source_ptr on SPLIT_BY and store the segment offsets and length in |
| - | * array */ |
| - | for(pos = 0; pos < len; pos++) { |
| - | if(source[pos] == SPLIT_BY) { |
| - | if(segment_length) { |
| - | curlx_dyn_init(&db[index], segment_length + 1); |
| - | result = curlx_dyn_addn(&db[index], &source[start], segment_length); |
| - | if(result) |
| - | goto fail; |
| - | |
| - | segment_length = 0; |
| - | index++; |
| - | if(++num_splits == MAX_QUERY_COMPONENTS) { |
| - | result = CURLE_TOO_LARGE; |
| - | goto fail; |
| - | } |
| - | } |
| - | start = pos + 1; |
| - | } |
| - | else { |
| - | segment_length++; |
| - | } |
| - | } |
| - | |
| - | if(segment_length) { |
| - | curlx_dyn_init(&db[index], segment_length + 1); |
| - | result = curlx_dyn_addn(&db[index], &source[start], segment_length); |
| - | if(!result) { |
| - | if(++num_splits == MAX_QUERY_COMPONENTS) |
| - | result = CURLE_TOO_LARGE; |
| - | } |
| - | } |
| - | fail: |
| - | *num_splits_out = num_splits; |
| - | return result; |
| - | } |
| - | |
| - | static bool is_reserved_char(const char c) |
| - | { |
| - | return (ISALNUM(c) || ISURLPUNTCS(c)); |
| - | } |
| - | |
| - | static CURLcode uri_encode_path(struct Curl_str *original_path, |
| - | struct dynbuf *new_path) |
| - | { |
| - | const char *p = curlx_str(original_path); |
| - | size_t i; |
| - | |
| - | for(i = 0; i < curlx_strlen(original_path); i++) { |
| - | /* Do not encode slashes or unreserved chars from RFC 3986 */ |
| - | CURLcode result = CURLE_OK; |
| - | unsigned char c = p[i]; |
| - | if(is_reserved_char(c) || c == '/') |
| - | result = curlx_dyn_addn(new_path, &c, 1); |
| - | else |
| - | result = curlx_dyn_addf(new_path, "%%%02X", c); |
| - | if(result) |
| - | return result; |
| - | } |
| - | |
| - | return CURLE_OK; |
| - | } |
| - | |
| - | static CURLcode encode_query_component(char *component, size_t len, |
| - | struct dynbuf *db) |
| - | { |
| - | size_t i; |
| - | for(i = 0; i < len; i++) { |
| - | CURLcode result = CURLE_OK; |
| - | unsigned char this_char = component[i]; |
| - | |
| - | if(is_reserved_char(this_char)) |
| - | /* Escape unreserved chars from RFC 3986 */ |
| - | result = curlx_dyn_addn(db, &this_char, 1); |
| - | else if(this_char == '+') |
| - | /* Encode '+' as space */ |
| - | result = curlx_dyn_add(db, "%20"); |
| - | else |
| - | result = curlx_dyn_addf(db, "%%%02X", this_char); |
| - | if(result) |
| - | return result; |
| - | } |
| - | |
| - | return CURLE_OK; |
| - | } |
| - | |
| - | /* |
| - | * Populates a dynbuf containing url_encode(url_decode(in)) |
| - | */ |
| - | |
| - | static CURLcode http_aws_decode_encode(const char *in, size_t in_len, |
| - | struct dynbuf *out) |
| - | { |
| - | char *out_s; |
| - | size_t out_s_len; |
| - | CURLcode result = |
| - | Curl_urldecode(in, in_len, &out_s, &out_s_len, REJECT_NADA); |
| - | |
| - | if(!result) { |
| - | result = encode_query_component(out_s, out_s_len, out); |
| - | Curl_safefree(out_s); |
| - | } |
| - | return result; |
| - | } |
| - | |
| - | static bool should_urlencode(struct Curl_str *service_name) |
| - | { |
| - | /* |
| - | * These services require unmodified (not additionally URL-encoded) URL |
| - | * paths. |
| - | * should_urlencode == true is equivalent to should_urlencode_uri_path |
| - | * from the AWS SDK. Urls are already normalized by the curl URL parser |
| - | */ |
| - | |
| - | if(curlx_str_cmp(service_name, "s3") || |
| - | curlx_str_cmp(service_name, "s3-express") || |
| - | curlx_str_cmp(service_name, "s3-outposts")) { |
| - | return false; |
| - | } |
| - | return true; |
| - | } |
| - | |
| #endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_AWS */ | |
| @@ -80,19 +80,6 @@ | |
| */ | |
| - | /* |
| - | * parsedate() |
| - | * |
| - | * Returns: |
| - | * |
| - | * PARSEDATE_OK - a fine conversion |
| - | * PARSEDATE_FAIL - failed to convert |
| - | * PARSEDATE_LATER - time overflow at the far end of time_t |
| - | * PARSEDATE_SOONER - time underflow at the low end of time_t |
| - | */ |
| - | |
| - | static int parsedate(const char *date, time_t *output); |
| - | |
| #define PARSEDATE_OK 0 | |
| #define PARSEDATE_FAIL -1 | |
| #define PARSEDATE_LATER 1 | |
| @@ -107,8 +107,6 @@ | |
| #include <openssl/store.h> | |
| /* this is used in the following conditions to make them easier to read */ | |
| #define OPENSSL_HAS_PROVIDERS | |
| - | |
| - | static void ossl_provider_cleanup(struct Curl_easy *data); |
| #endif | |
| /* AWS-LC fixed a bug with large buffers in v1.61.0 which also introduced | |
| @@ -181,8 +179,6 @@ typedef unsigned long sslerr_t; | |
| #endif | |
| #define ossl_valsize_t numcert_t | |
| - | static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl); |
| - | |
| static CURLcode push_certinfo(struct Curl_easy *data, | |
| BIO *mem, const char *label, int num) | |
| WARN_UNUSED_RESULT; | |
| @@ -36,8 +36,6 @@ | |
| #include "tool_libinfo.h" | |
| #include "tool_strdup.h" | |
| - | static char *parse_filename(const char *ptr, size_t len); |
| - | |
| #ifdef _WIN32 | |
| #define BOLD "\x1b[1m" | |
| #define BOLDOFF "\x1b[22m" | |
| @@ -54,10 +52,175 @@ static char *parse_filename(const char *ptr, size_t len); | |
| #endif | |
| #ifdef LINK | |
| + | /* |
| + | * Treat the Location: header specially, by writing a special escape |
| + | * sequence that adds a hyperlink to the displayed text. This makes |
| + | * the absolute URL of the redirect clickable in supported terminals, |
| + | * which could not happen otherwise for relative URLs. The Location: |
| + | * header is supposed to always be absolute so this theoretically |
| + | * should not be needed but the real world returns plenty of relative |
| + | * URLs here. |
| + | */ |
| static void write_linked_location(CURL *curl, const char *location, | |
| - | size_t loclen, FILE *stream); |
| + | size_t loclen, FILE *stream) |
| + | { |
| + | /* This would so simple if CURLINFO_REDIRECT_URL were available here */ |
| + | CURLU *u = NULL; |
| + | char *copyloc = NULL, *locurl = NULL, *scheme = NULL, *finalurl = NULL; |
| + | const char *loc = location; |
| + | size_t llen = loclen; |
| + | int space_skipped = 0; |
| + | const char *vver = getenv("VTE_VERSION"); |
| + | |
| + | if(vver) { |
| + | curl_off_t num; |
| + | if(curlx_str_number(&vver, &num, CURL_OFF_T_MAX) || |
| + | /* Skip formatting for old versions of VTE <= 0.48.1 (Mar 2017) since |
| + | some of those versions have formatting bugs. (#10428) */ |
| + | (num <= 4801)) |
| + | goto locout; |
| + | } |
| + | |
| + | /* Strip leading whitespace of the redirect URL */ |
| + | while(llen && (*loc == ' ' || *loc == '\t')) { |
| + | ++loc; |
| + | --llen; |
| + | ++space_skipped; |
| + | } |
| + | |
| + | /* Strip the trailing end-of-line characters, normally "\r\n" */ |
| + | while(llen && (loc[llen - 1] == '\n' || loc[llen - 1] == '\r')) |
| + | --llen; |
| + | |
| + | /* CURLU makes it easy to handle the relative URL case */ |
| + | u = curl_url(); |
| + | if(!u) |
| + | goto locout; |
| + | |
| + | /* Create a null-terminated and whitespace-stripped copy of Location: */ |
| + | copyloc = memdup0(loc, llen); |
| + | if(!copyloc) |
| + | goto locout; |
| + | |
| + | /* The original URL to use as a base for a relative redirect URL */ |
| + | if(curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &locurl)) |
| + | goto locout; |
| + | if(curl_url_set(u, CURLUPART_URL, locurl, 0)) |
| + | goto locout; |
| + | |
| + | /* Redirected location. This can be either absolute or relative. */ |
| + | if(curl_url_set(u, CURLUPART_URL, copyloc, 0)) |
| + | goto locout; |
| + | |
| + | if(curl_url_get(u, CURLUPART_URL, &finalurl, CURLU_NO_DEFAULT_PORT)) |
| + | goto locout; |
| + | |
| + | if(curl_url_get(u, CURLUPART_SCHEME, &scheme, 0)) |
| + | goto locout; |
| + | |
| + | if(!strcmp("http", scheme) || |
| + | !strcmp("https", scheme) || |
| + | !strcmp("ftp", scheme) || |
| + | !strcmp("ftps", scheme)) { |
| + | curl_mfprintf(stream, "%.*s" LINK "%s" LINKST "%.*s" LINKOFF, |
| + | space_skipped, location, |
| + | finalurl, |
| + | (int)loclen - space_skipped, loc); |
| + | goto locdone; |
| + | } |
| + | |
| + | /* Not a "safe" URL: do not linkify it */ |
| + | |
| + | locout: |
| + | /* Write the normal output in case of error or unsafe */ |
| + | fwrite(location, loclen, 1, stream); |
| + | |
| + | locdone: |
| + | if(u) { |
| + | curl_free(finalurl); |
| + | curl_free(scheme); |
| + | curl_url_cleanup(u); |
| + | curlx_free(copyloc); |
| + | } |
| + | } |
| #endif | |
| + | /* |
| + | * Copies a filename part and returns an ALLOCATED data buffer. |
| + | */ |
| + | static char *parse_filename(const char *ptr, size_t len) |
| + | { |
| + | char *copy; |
| + | char *p; |
| + | char *q; |
| + | char stop = '\0'; |
| + | |
| + | copy = memdup0(ptr, len); |
| + | if(!copy) |
| + | return NULL; |
| + | |
| + | p = copy; |
| + | if(*p == '\'' || *p == '"') { |
| + | /* store the starting quote */ |
| + | stop = *p; |
| + | p++; |
| + | } |
| + | else |
| + | stop = ';'; |
| + | |
| + | /* scan for the end letter and stop there */ |
| + | q = strchr(p, stop); |
| + | if(q) |
| + | *q = '\0'; |
| + | |
| + | /* if the filename contains a path, only use filename portion */ |
| + | q = strrchr(p, '/'); |
| + | if(q) { |
| + | p = q + 1; |
| + | if(!*p) { |
| + | tool_safefree(copy); |
| + | return NULL; |
| + | } |
| + | } |
| + | |
| + | /* If the filename contains a backslash, only use filename portion. The idea |
| + | is that even systems that do not handle backslashes as path separators |
| + | probably want the path removed for convenience. */ |
| + | q = strrchr(p, '\\'); |
| + | if(q) { |
| + | p = q + 1; |
| + | if(!*p) { |
| + | tool_safefree(copy); |
| + | return NULL; |
| + | } |
| + | } |
| + | |
| + | /* make sure the filename does not end in \r or \n */ |
| + | q = strchr(p, '\r'); |
| + | if(q) |
| + | *q = '\0'; |
| + | |
| + | q = strchr(p, '\n'); |
| + | if(q) |
| + | *q = '\0'; |
| + | |
| + | if(copy != p) |
| + | memmove(copy, p, strlen(p) + 1); |
| + | |
| + | #if defined(_WIN32) || defined(MSDOS) |
| + | { |
| + | char *sanitized; |
| + | SANITIZEcode sc = sanitize_file_name(&sanitized, copy, 0); |
| + | tool_safefree(copy); |
| + | if(sc) |
| + | return NULL; |
| + | copy = sanitized; |
| + | } |
| + | #endif /* _WIN32 || MSDOS */ |
| + | |
| + | return copy; |
| + | } |
| + | |
| int tool_write_headers(struct HdrCbData *hdrcbdata, FILE *stream) | |
| { | |
| struct curl_slist *h = hdrcbdata->headlist; | |
| @@ -310,173 +473,3 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) | |
| } | |
| return cb; | |
| } | |
| - | |
| - | /* |
| - | * Copies a filename part and returns an ALLOCATED data buffer. |
| - | */ |
| - | static char *parse_filename(const char *ptr, size_t len) |
| - | { |
| - | char *copy; |
| - | char *p; |
| - | char *q; |
| - | char stop = '\0'; |
| - | |
| - | copy = memdup0(ptr, len); |
| - | if(!copy) |
| - | return NULL; |
| - | |
| - | p = copy; |
| - | if(*p == '\'' || *p == '"') { |
| - | /* store the starting quote */ |
| - | stop = *p; |
| - | p++; |
| - | } |
| - | else |
| - | stop = ';'; |
| - | |
| - | /* scan for the end letter and stop there */ |
| - | q = strchr(p, stop); |
| - | if(q) |
| - | *q = '\0'; |
| - | |
| - | /* if the filename contains a path, only use filename portion */ |
| - | q = strrchr(p, '/'); |
| - | if(q) { |
| - | p = q + 1; |
| - | if(!*p) { |
| - | tool_safefree(copy); |
| - | return NULL; |
| - | } |
| - | } |
| - | |
| - | /* If the filename contains a backslash, only use filename portion. The idea |
| - | is that even systems that do not handle backslashes as path separators |
| - | probably want the path removed for convenience. */ |
| - | q = strrchr(p, '\\'); |
| - | if(q) { |
| - | p = q + 1; |
| - | if(!*p) { |
| - | tool_safefree(copy); |
| - | return NULL; |
| - | } |
| - | } |
| - | |
| - | /* make sure the filename does not end in \r or \n */ |
| - | q = strchr(p, '\r'); |
| - | if(q) |
| - | *q = '\0'; |
| - | |
| - | q = strchr(p, '\n'); |
| - | if(q) |
| - | *q = '\0'; |
| - | |
| - | if(copy != p) |
| - | memmove(copy, p, strlen(p) + 1); |
| - | |
| - | #if defined(_WIN32) || defined(MSDOS) |
| - | { |
| - | char *sanitized; |
| - | SANITIZEcode sc = sanitize_file_name(&sanitized, copy, 0); |
| - | tool_safefree(copy); |
| - | if(sc) |
| - | return NULL; |
| - | copy = sanitized; |
| - | } |
| - | #endif /* _WIN32 || MSDOS */ |
| - | |
| - | return copy; |
| - | } |
| - | |
| - | #ifdef LINK |
| - | /* |
| - | * Treat the Location: header specially, by writing a special escape |
| - | * sequence that adds a hyperlink to the displayed text. This makes |
| - | * the absolute URL of the redirect clickable in supported terminals, |
| - | * which could not happen otherwise for relative URLs. The Location: |
| - | * header is supposed to always be absolute so this theoretically |
| - | * should not be needed but the real world returns plenty of relative |
| - | * URLs here. |
| - | */ |
| - | static void write_linked_location(CURL *curl, const char *location, |
| - | size_t loclen, FILE *stream) |
| - | { |
| - | /* This would so simple if CURLINFO_REDIRECT_URL were available here */ |
| - | CURLU *u = NULL; |
| - | char *copyloc = NULL, *locurl = NULL, *scheme = NULL, *finalurl = NULL; |
| - | const char *loc = location; |
| - | size_t llen = loclen; |
| - | int space_skipped = 0; |
| - | const char *vver = getenv("VTE_VERSION"); |
| - | |
| - | if(vver) { |
| - | curl_off_t num; |
| - | if(curlx_str_number(&vver, &num, CURL_OFF_T_MAX) || |
| - | /* Skip formatting for old versions of VTE <= 0.48.1 (Mar 2017) since |
| - | some of those versions have formatting bugs. (#10428) */ |
| - | (num <= 4801)) |
| - | goto locout; |
| - | } |
| - | |
| - | /* Strip leading whitespace of the redirect URL */ |
| - | while(llen && (*loc == ' ' || *loc == '\t')) { |
| - | ++loc; |
| - | --llen; |
| - | ++space_skipped; |
| - | } |
| - | |
| - | /* Strip the trailing end-of-line characters, normally "\r\n" */ |
| - | while(llen && (loc[llen - 1] == '\n' || loc[llen - 1] == '\r')) |
| - | --llen; |
| - | |
| - | /* CURLU makes it easy to handle the relative URL case */ |
| - | u = curl_url(); |
| - | if(!u) |
| - | goto locout; |
| - | |
| - | /* Create a null-terminated and whitespace-stripped copy of Location: */ |
| - | copyloc = memdup0(loc, llen); |
| - | if(!copyloc) |
| - | goto locout; |
| - | |
| - | /* The original URL to use as a base for a relative redirect URL */ |
| - | if(curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &locurl)) |
| - | goto locout; |
| - | if(curl_url_set(u, CURLUPART_URL, locurl, 0)) |
| - | goto locout; |
| - | |
| - | /* Redirected location. This can be either absolute or relative. */ |
| - | if(curl_url_set(u, CURLUPART_URL, copyloc, 0)) |
| - | goto locout; |
| - | |
| - | if(curl_url_get(u, CURLUPART_URL, &finalurl, CURLU_NO_DEFAULT_PORT)) |
| - | goto locout; |
| - | |
| - | if(curl_url_get(u, CURLUPART_SCHEME, &scheme, 0)) |
| - | goto locout; |
| - | |
| - | if(!strcmp("http", scheme) || |
| - | !strcmp("https", scheme) || |
| - | !strcmp("ftp", scheme) || |
| - | !strcmp("ftps", scheme)) { |
| - | curl_mfprintf(stream, "%.*s" LINK "%s" LINKST "%.*s" LINKOFF, |
| - | space_skipped, location, |
| - | finalurl, |
| - | (int)loclen - space_skipped, loc); |
| - | goto locdone; |
| - | } |
| - | |
| - | /* Not a "safe" URL: do not linkify it */ |
| - | |
| - | locout: |
| - | /* Write the normal output in case of error or unsafe */ |
| - | fwrite(location, loclen, 1, stream); |
| - | |
| - | locdone: |
| - | if(u) { |
| - | curl_free(finalurl); |
| - | curl_free(scheme); |
| - | curl_url_cleanup(u); |
| - | curlx_free(copyloc); |
| - | } |
| - | } |
| - | #endif |
| @@ -35,66 +35,6 @@ | |
| #include <http_request.h> | |
| #include <http_log.h> | |
| - | static void curltest_hooks(apr_pool_t *pool); |
| - | static int curltest_echo_handler(request_rec *r); |
| - | static int curltest_put_handler(request_rec *r); |
| - | static int curltest_tweak_handler(request_rec *r); |
| - | static int curltest_1_1_required(request_rec *r); |
| - | static int curltest_sslinfo_handler(request_rec *r); |
| - | |
| - | AP_DECLARE_MODULE(curltest) = |
| - | { |
| - | STANDARD20_MODULE_STUFF, |
| - | NULL, /* func to create per-directory config */ |
| - | NULL, /* func to merge per-directory config */ |
| - | NULL, /* func to create per-server config */ |
| - | NULL, /* func to merge per-server config */ |
| - | NULL, /* command handlers */ |
| - | curltest_hooks, |
| - | #ifdef AP_MODULE_FLAG_NONE |
| - | AP_MODULE_FLAG_ALWAYS_MERGE |
| - | #endif |
| - | }; |
| - | |
| - | static int curltest_post_config(apr_pool_t *p, apr_pool_t *plog, |
| - | apr_pool_t *ptemp, server_rec *s) |
| - | { |
| - | void *data = NULL; |
| - | const char *key = "mod_curltest_init_counter"; |
| - | |
| - | (void)p; |
| - | (void)plog; |
| - | (void)ptemp; |
| - | |
| - | apr_pool_userdata_get(&data, key, s->process->pool); |
| - | if(!data) { |
| - | /* dry run */ |
| - | apr_pool_userdata_set((const void *)1, key, |
| - | apr_pool_cleanup_null, s->process->pool); |
| - | return APR_SUCCESS; |
| - | } |
| - | |
| - | /* mess with the overall server here */ |
| - | |
| - | return APR_SUCCESS; |
| - | } |
| - | |
| - | static void curltest_hooks(apr_pool_t *pool) |
| - | { |
| - | ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks"); |
| - | |
| - | /* Run once after configuration is set, but before mpm children initialize. |
| - | */ |
| - | ap_hook_post_config(curltest_post_config, NULL, NULL, APR_HOOK_MIDDLE); |
| - | |
| - | /* curl test handlers */ |
| - | ap_hook_handler(curltest_echo_handler, NULL, NULL, APR_HOOK_MIDDLE); |
| - | ap_hook_handler(curltest_put_handler, NULL, NULL, APR_HOOK_MIDDLE); |
| - | ap_hook_handler(curltest_tweak_handler, NULL, NULL, APR_HOOK_MIDDLE); |
| - | ap_hook_handler(curltest_1_1_required, NULL, NULL, APR_HOOK_MIDDLE); |
| - | ap_hook_handler(curltest_sslinfo_handler, NULL, NULL, APR_HOOK_MIDDLE); |
| - | } |
| - | |
| #define SECS_PER_HOUR (60 * 60) | |
| #define SECS_PER_DAY (24 * SECS_PER_HOUR) | |
| @@ -883,3 +823,56 @@ cleanup: | |
| } | |
| return DECLINED; | |
| } | |
| + | |
| + | static int curltest_post_config(apr_pool_t *p, apr_pool_t *plog, |
| + | apr_pool_t *ptemp, server_rec *s) |
| + | { |
| + | void *data = NULL; |
| + | const char *key = "mod_curltest_init_counter"; |
| + | |
| + | (void)p; |
| + | (void)plog; |
| + | (void)ptemp; |
| + | |
| + | apr_pool_userdata_get(&data, key, s->process->pool); |
| + | if(!data) { |
| + | /* dry run */ |
| + | apr_pool_userdata_set((const void *)1, key, |
| + | apr_pool_cleanup_null, s->process->pool); |
| + | return APR_SUCCESS; |
| + | } |
| + | |
| + | /* mess with the overall server here */ |
| + | |
| + | return APR_SUCCESS; |
| + | } |
| + | |
| + | static void curltest_hooks(apr_pool_t *pool) |
| + | { |
| + | ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks"); |
| + | |
| + | /* Run once after configuration is set, but before mpm children initialize. |
| + | */ |
| + | ap_hook_post_config(curltest_post_config, NULL, NULL, APR_HOOK_MIDDLE); |
| + | |
| + | /* curl test handlers */ |
| + | ap_hook_handler(curltest_echo_handler, NULL, NULL, APR_HOOK_MIDDLE); |
| + | ap_hook_handler(curltest_put_handler, NULL, NULL, APR_HOOK_MIDDLE); |
| + | ap_hook_handler(curltest_tweak_handler, NULL, NULL, APR_HOOK_MIDDLE); |
| + | ap_hook_handler(curltest_1_1_required, NULL, NULL, APR_HOOK_MIDDLE); |
| + | ap_hook_handler(curltest_sslinfo_handler, NULL, NULL, APR_HOOK_MIDDLE); |
| + | } |
| + | |
| + | AP_DECLARE_MODULE(curltest) = |
| + | { |
| + | STANDARD20_MODULE_STUFF, |
| + | NULL, /* func to create per-directory config */ |
| + | NULL, /* func to merge per-directory config */ |
| + | NULL, /* func to create per-server config */ |
| + | NULL, /* func to merge per-server config */ |
| + | NULL, /* command handlers */ |
| + | curltest_hooks, |
| + | #ifdef AP_MODULE_FLAG_NONE |
| + | AP_MODULE_FLAG_ALWAYS_MERGE |
| + | #endif |
| + | }; |
| @@ -770,182 +770,6 @@ storerequest_cleanup: | |
| errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); | |
| } | |
| - | static void init_httprequest(struct sws_httprequest *req) |
| - | { |
| - | req->checkindex = 0; |
| - | req->offset = 0; |
| - | req->testno = DOCNUMBER_NOTHING; |
| - | req->partno = 0; |
| - | req->connect_request = FALSE; |
| - | req->open = TRUE; |
| - | req->auth_req = FALSE; |
| - | req->auth = FALSE; |
| - | req->cl = 0; |
| - | req->digest = FALSE; |
| - | req->ntlm = FALSE; |
| - | req->skip = 0; |
| - | req->skipall = FALSE; |
| - | req->noexpect = FALSE; |
| - | req->delay = 0; |
| - | req->writedelay = 0; |
| - | req->rcmd = RCMD_NORMALREQ; |
| - | req->prot_version = 0; |
| - | req->callcount = 0; |
| - | req->connect_port = 0; |
| - | req->done_processing = 0; |
| - | req->upgrade = 0; |
| - | req->upgrade_request = 0; |
| - | } |
| - | |
| - | static int sws_send_doc(curl_socket_t sock, struct sws_httprequest *req); |
| - | |
| - | /* returns 1 if the connection should be serviced again immediately, 0 if there |
| - | is no data waiting, or < 0 if it should be closed */ |
| - | static int sws_get_request(curl_socket_t sock, struct sws_httprequest *req) |
| - | { |
| - | int fail = 0; |
| - | char *reqbuf = req->reqbuf; |
| - | ssize_t got = 0; |
| - | int overflow = 0; |
| - | |
| - | if(req->upgrade_request) { |
| - | /* upgraded connection, work it differently until end of connection */ |
| - | logmsg("Upgraded connection, this is no longer HTTP/1"); |
| - | sws_send_doc(sock, req); |
| - | |
| - | /* dump the request received so far to the external file */ |
| - | reqbuf[req->offset] = '\0'; |
| - | sws_storerequest(reqbuf, req->offset); |
| - | req->offset = 0; |
| - | |
| - | /* read websocket traffic */ |
| - | if(req->open) { |
| - | logmsg("wait for websocket traffic"); |
| - | do { |
| - | got = sread(sock, reqbuf + req->offset, |
| - | sizeof(req->reqbuf) - req->offset); |
| - | if(got > 0) { |
| - | req->offset += got; |
| - | logmsg("Got %zu bytes from client", got); |
| - | } |
| - | |
| - | if((got == -1) && |
| - | ((SOCKERRNO == EAGAIN) || (SOCKERRNO == SOCKEWOULDBLOCK))) { |
| - | int rc; |
| - | fd_set input; |
| - | fd_set output; |
| - | struct timeval timeout = { 0 }; |
| - | timeout.tv_sec = 1; /* 1000 ms */ |
| - | |
| - | logmsg("Got EAGAIN from sread"); |
| - | FD_ZERO(&input); |
| - | FD_ZERO(&output); |
| - | got = 0; |
| - | #ifdef __DJGPP__ |
| - | #pragma GCC diagnostic push |
| - | #pragma GCC diagnostic ignored "-Warith-conversion" |
| - | #endif |
| - | FD_SET(sock, &input); |
| - | #ifdef __DJGPP__ |
| - | #pragma GCC diagnostic pop |
| - | #endif |
| - | do { |
| - | logmsg("Wait until readable"); |
| - | rc = select((int)sock + 1, &input, &output, NULL, &timeout); |
| - | } while(rc < 0 && SOCKERRNO == SOCKEINTR && !got_exit_signal); |
| - | logmsg("readable %d", rc); |
| - | if(rc) |
| - | got = 1; |
| - | } |
| - | } while(got > 0); |
| - | } |
| - | else { |
| - | logmsg("NO wait for websocket traffic"); |
| - | } |
| - | if(req->offset) { |
| - | logmsg("log the websocket traffic"); |
| - | /* dump the incoming websocket traffic to the external file */ |
| - | reqbuf[req->offset] = '\0'; |
| - | sws_storerequest(reqbuf, req->offset); |
| - | req->offset = 0; |
| - | } |
| - | init_httprequest(req); |
| - | |
| - | return -1; |
| - | } |
| - | |
| - | if(req->offset >= sizeof(req->reqbuf) - 1) { |
| - | /* buffer is already full; do nothing */ |
| - | overflow = 1; |
| - | } |
| - | else { |
| - | if(req->skip) |
| - | /* we are instructed to not read the entire thing, so we make sure to |
| - | only read what we are supposed to and NOT read the entire thing the |
| - | client wants to send! */ |
| - | got = sread(sock, reqbuf + req->offset, req->cl); |
| - | else |
| - | got = sread(sock, reqbuf + req->offset, |
| - | sizeof(req->reqbuf) - 1 - req->offset); |
| - | |
| - | if(got_exit_signal) |
| - | return -1; |
| - | if(got == 0) { |
| - | logmsg("Connection closed by client"); |
| - | fail = 1; |
| - | } |
| - | else if(got < 0) { |
| - | char errbuf[STRERROR_LEN]; |
| - | int error = SOCKERRNO; |
| - | if(EAGAIN == error || SOCKEWOULDBLOCK == error) { |
| - | /* nothing to read at the moment */ |
| - | return 0; |
| - | } |
| - | logmsg("recv() returned error (%d) %s", |
| - | error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| - | fail = 1; |
| - | } |
| - | if(fail) { |
| - | /* dump the request received so far to the external file */ |
| - | reqbuf[req->offset] = '\0'; |
| - | sws_storerequest(reqbuf, req->offset); |
| - | return -1; |
| - | } |
| - | |
| - | logmsg("Read %zd bytes", got); |
| - | |
| - | req->offset += (size_t)got; |
| - | reqbuf[req->offset] = '\0'; |
| - | |
| - | req->done_processing = sws_ProcessRequest(req); |
| - | if(got_exit_signal) |
| - | return -1; |
| - | } |
| - | |
| - | if(overflow || (req->offset == sizeof(req->reqbuf) - 1 && got > 0)) { |
| - | logmsg("Request would overflow buffer, closing connection"); |
| - | /* dump request received so far to external file anyway */ |
| - | reqbuf[sizeof(req->reqbuf) - 1] = '\0'; |
| - | fail = 1; |
| - | } |
| - | else if(req->offset > sizeof(req->reqbuf) - 1) { |
| - | logmsg("Request buffer overflow, closing connection"); |
| - | /* dump request received so far to external file anyway */ |
| - | reqbuf[sizeof(req->reqbuf) - 1] = '\0'; |
| - | fail = 1; |
| - | } |
| - | else |
| - | reqbuf[req->offset] = '\0'; |
| - | |
| - | /* at the end of a request dump it to an external file */ |
| - | if(fail || req->done_processing) |
| - | sws_storerequest(reqbuf, req->offset); |
| - | if(got_exit_signal) |
| - | return -1; |
| - | |
| - | return fail ? -1 : 1; |
| - | } |
| - | |
| /* returns -1 on failure */ | |
| static int sws_send_doc(curl_socket_t sock, struct sws_httprequest *req) | |
| { | |
| @@ -1225,6 +1049,180 @@ retry: | |
| return 0; | |
| } | |
| + | static void init_httprequest(struct sws_httprequest *req) |
| + | { |
| + | req->checkindex = 0; |
| + | req->offset = 0; |
| + | req->testno = DOCNUMBER_NOTHING; |
| + | req->partno = 0; |
| + | req->connect_request = FALSE; |
| + | req->open = TRUE; |
| + | req->auth_req = FALSE; |
| + | req->auth = FALSE; |
| + | req->cl = 0; |
| + | req->digest = FALSE; |
| + | req->ntlm = FALSE; |
| + | req->skip = 0; |
| + | req->skipall = FALSE; |
| + | req->noexpect = FALSE; |
| + | req->delay = 0; |
| + | req->writedelay = 0; |
| + | req->rcmd = RCMD_NORMALREQ; |
| + | req->prot_version = 0; |
| + | req->callcount = 0; |
| + | req->connect_port = 0; |
| + | req->done_processing = 0; |
| + | req->upgrade = 0; |
| + | req->upgrade_request = 0; |
| + | } |
| + | |
| + | /* returns 1 if the connection should be serviced again immediately, 0 if there |
| + | is no data waiting, or < 0 if it should be closed */ |
| + | static int sws_get_request(curl_socket_t sock, struct sws_httprequest *req) |
| + | { |
| + | int fail = 0; |
| + | char *reqbuf = req->reqbuf; |
| + | ssize_t got = 0; |
| + | int overflow = 0; |
| + | |
| + | if(req->upgrade_request) { |
| + | /* upgraded connection, work it differently until end of connection */ |
| + | logmsg("Upgraded connection, this is no longer HTTP/1"); |
| + | sws_send_doc(sock, req); |
| + | |
| + | /* dump the request received so far to the external file */ |
| + | reqbuf[req->offset] = '\0'; |
| + | sws_storerequest(reqbuf, req->offset); |
| + | req->offset = 0; |
| + | |
| + | /* read websocket traffic */ |
| + | if(req->open) { |
| + | logmsg("wait for websocket traffic"); |
| + | do { |
| + | got = sread(sock, reqbuf + req->offset, |
| + | sizeof(req->reqbuf) - req->offset); |
| + | if(got > 0) { |
| + | req->offset += got; |
| + | logmsg("Got %zu bytes from client", got); |
| + | } |
| + | |
| + | if((got == -1) && |
| + | ((SOCKERRNO == EAGAIN) || (SOCKERRNO == SOCKEWOULDBLOCK))) { |
| + | int rc; |
| + | fd_set input; |
| + | fd_set output; |
| + | struct timeval timeout = { 0 }; |
| + | timeout.tv_sec = 1; /* 1000 ms */ |
| + | |
| + | logmsg("Got EAGAIN from sread"); |
| + | FD_ZERO(&input); |
| + | FD_ZERO(&output); |
| + | got = 0; |
| + | #ifdef __DJGPP__ |
| + | #pragma GCC diagnostic push |
| + | #pragma GCC diagnostic ignored "-Warith-conversion" |
| + | #endif |
| + | FD_SET(sock, &input); |
| + | #ifdef __DJGPP__ |
| + | #pragma GCC diagnostic pop |
| + | #endif |
| + | do { |
| + | logmsg("Wait until readable"); |
| + | rc = select((int)sock + 1, &input, &output, NULL, &timeout); |
| + | } while(rc < 0 && SOCKERRNO == SOCKEINTR && !got_exit_signal); |
| + | logmsg("readable %d", rc); |
| + | if(rc) |
| + | got = 1; |
| + | } |
| + | } while(got > 0); |
| + | } |
| + | else { |
| + | logmsg("NO wait for websocket traffic"); |
| + | } |
| + | if(req->offset) { |
| + | logmsg("log the websocket traffic"); |
| + | /* dump the incoming websocket traffic to the external file */ |
| + | reqbuf[req->offset] = '\0'; |
| + | sws_storerequest(reqbuf, req->offset); |
| + | req->offset = 0; |
| + | } |
| + | init_httprequest(req); |
| + | |
| + | return -1; |
| + | } |
| + | |
| + | if(req->offset >= sizeof(req->reqbuf) - 1) { |
| + | /* buffer is already full; do nothing */ |
| + | overflow = 1; |
| + | } |
| + | else { |
| + | if(req->skip) |
| + | /* we are instructed to not read the entire thing, so we make sure to |
| + | only read what we are supposed to and NOT read the entire thing the |
| + | client wants to send! */ |
| + | got = sread(sock, reqbuf + req->offset, req->cl); |
| + | else |
| + | got = sread(sock, reqbuf + req->offset, |
| + | sizeof(req->reqbuf) - 1 - req->offset); |
| + | |
| + | if(got_exit_signal) |
| + | return -1; |
| + | if(got == 0) { |
| + | logmsg("Connection closed by client"); |
| + | fail = 1; |
| + | } |
| + | else if(got < 0) { |
| + | char errbuf[STRERROR_LEN]; |
| + | int error = SOCKERRNO; |
| + | if(EAGAIN == error || SOCKEWOULDBLOCK == error) { |
| + | /* nothing to read at the moment */ |
| + | return 0; |
| + | } |
| + | logmsg("recv() returned error (%d) %s", |
| + | error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| + | fail = 1; |
| + | } |
| + | if(fail) { |
| + | /* dump the request received so far to the external file */ |
| + | reqbuf[req->offset] = '\0'; |
| + | sws_storerequest(reqbuf, req->offset); |
| + | return -1; |
| + | } |
| + | |
| + | logmsg("Read %zd bytes", got); |
| + | |
| + | req->offset += (size_t)got; |
| + | reqbuf[req->offset] = '\0'; |
| + | |
| + | req->done_processing = sws_ProcessRequest(req); |
| + | if(got_exit_signal) |
| + | return -1; |
| + | } |
| + | |
| + | if(overflow || (req->offset == sizeof(req->reqbuf) - 1 && got > 0)) { |
| + | logmsg("Request would overflow buffer, closing connection"); |
| + | /* dump request received so far to external file anyway */ |
| + | reqbuf[sizeof(req->reqbuf) - 1] = '\0'; |
| + | fail = 1; |
| + | } |
| + | else if(req->offset > sizeof(req->reqbuf) - 1) { |
| + | logmsg("Request buffer overflow, closing connection"); |
| + | /* dump request received so far to external file anyway */ |
| + | reqbuf[sizeof(req->reqbuf) - 1] = '\0'; |
| + | fail = 1; |
| + | } |
| + | else |
| + | reqbuf[req->offset] = '\0'; |
| + | |
| + | /* at the end of a request dump it to an external file */ |
| + | if(fail || req->done_processing) |
| + | sws_storerequest(reqbuf, req->offset); |
| + | if(got_exit_signal) |
| + | return -1; |
| + | |
| + | return fail ? -1 : 1; |
| + | } |
| + | |
| static curl_socket_t connect_to(const char *ipaddr, unsigned short port) | |
| { | |
| srvr_sockaddr_union_t serveraddr; | |
| @@ -207,52 +207,12 @@ static int tftpd_wroteportfile = 0; | |
| static sigjmp_buf timeoutbuf; | |
| #endif | |
| - | #if defined(HAVE_ALARM) && defined(SIGALRM) |
| - | static const unsigned int rexmtval = TIMEOUT; |
| - | #endif |
| - | |
| - | /***************************************************************************** |
| - | * FUNCTION PROTOTYPES * |
| - | *****************************************************************************/ |
| - | |
| - | static struct tftphdr *rw_init(int); |
| - | |
| - | static struct tftphdr *w_init(void); |
| - | |
| - | static struct tftphdr *r_init(void); |
| - | |
| - | static void read_ahead(struct testcase *test, int convert); |
| - | |
| - | static ssize_t write_behind(struct testcase *test, int convert); |
| - | |
| - | static int synchnet(curl_socket_t); |
| - | |
| - | static int do_tftp(struct testcase *test, struct tftphdr *tp, ssize_t size); |
| - | |
| - | static int validate_access(struct testcase *test, |
| - | const char *filename, unsigned short mode); |
| - | |
| - | static void sendtftp(struct testcase *test, const struct formats *pf); |
| - | |
| - | static void recvtftp(struct testcase *test, const struct formats *pf); |
| - | |
| - | static void nak(int error); |
| - | |
| - | #if defined(HAVE_ALARM) && defined(SIGALRM) |
| - | |
| - | static void mysignal(int sig, void (*handler)(int)); |
| - | |
| - | static void timer(int signum); |
| - | |
| - | static void justtimeout(int signum); |
| - | |
| - | #endif /* HAVE_ALARM && SIGALRM */ |
| - | |
| /***************************************************************************** | |
| * FUNCTION IMPLEMENTATIONS * | |
| *****************************************************************************/ | |
| #if defined(HAVE_ALARM) && defined(SIGALRM) | |
| + | static const unsigned int rexmtval = TIMEOUT; |
| /* | |
| * Like signal(), but with well-defined semantics. | |
| @@ -302,6 +262,36 @@ static void justtimeout(int signum) | |
| #endif /* HAVE_ALARM && SIGALRM */ | |
| + | /* |
| + | * Send a nak packet (error message). Error code passed in is one of the |
| + | * standard TFTP codes, or a Unix errno offset by 100. |
| + | */ |
| + | static void nak(int error) |
| + | { |
| + | struct tftphdr *tp; |
| + | int length; |
| + | struct errmsg *pe; |
| + | |
| + | tp = &trsbuf.hdr; |
| + | tp->th_opcode = htons(opcode_ERROR); |
| + | tp->th_code = htons((unsigned short)error); |
| + | for(pe = errmsgs; pe->e_code >= 0; pe++) |
| + | if(pe->e_code == error) |
| + | break; |
| + | if(pe->e_code < 0) { |
| + | curlx_strerror(error - 100, pe->e_msg, sizeof(pe->e_msg)); |
| + | tp->th_code = TFTP_EUNDEF; /* set 'undef' errorcode */ |
| + | } |
| + | length = (int)strlen(pe->e_msg); |
| + | |
| + | /* we use memcpy() instead of strcpy() in order to avoid buffer overflow |
| + | * report from glibc with FORTIFY_SOURCE */ |
| + | memcpy(tp->th_msg, pe->e_msg, length + 1); |
| + | length += 5; |
| + | if(swrite(peer, &trsbuf.storage[0], length) != length) |
| + | logmsg("nak: fail\n"); |
| + | } |
| + | |
| /* | |
| * init for either read-ahead or write-behind. | |
| * zero for write-behind, one for read-head. | |
| @@ -327,25 +317,6 @@ static struct tftphdr *r_init(void) | |
| return rw_init(1); /* read-ahead */ | |
| } | |
| - | /* Have emptied current buffer by sending to net and getting ack. |
| - | Free it and return next buffer filled with data. |
| - | */ |
| - | static int readit(struct testcase *test, struct tftphdr * volatile *dpp, |
| - | int convert /* if true, convert to ASCII */) |
| - | { |
| - | struct bf *b; |
| - | |
| - | bfs[current].counter = BF_FREE; /* free old one */ |
| - | current = !current; /* "incr" current */ |
| - | |
| - | b = &bfs[current]; /* look at new buffer */ |
| - | if(b->counter == BF_FREE) /* if empty */ |
| - | read_ahead(test, convert); /* fill it */ |
| - | |
| - | *dpp = &b->buf.hdr; /* set caller's ptr */ |
| - | return b->counter; |
| - | } |
| - | |
| /* | |
| * fill the input buffer, doing ASCII conversions if requested | |
| * conversions are lf -> cr, lf and cr -> cr, nul | |
| @@ -407,19 +378,23 @@ static void read_ahead(struct testcase *test, | |
| b->counter = (int)(p - dp->th_data); | |
| } | |
| - | /* Update count associated with the buffer, get new buffer from the queue. |
| - | Calls write_behind only if next buffer not available. |
| + | /* Have emptied current buffer by sending to net and getting ack. |
| + | Free it and return next buffer filled with data. |
| */ | |
| - | static int writeit(struct testcase *test, struct tftphdr * volatile *dpp, |
| - | int ct, int convert) |
| + | static int readit(struct testcase *test, struct tftphdr * volatile *dpp, |
| + | int convert /* if true, convert to ASCII */) |
| { | |
| - | bfs[current].counter = ct; /* set size of data to write */ |
| - | current = !current; /* switch to other buffer */ |
| - | if(bfs[current].counter != BF_FREE) /* if not free */ |
| - | write_behind(test, convert); /* flush it */ |
| - | bfs[current].counter = BF_ALLOC; /* mark as allocated */ |
| - | *dpp = &bfs[current].buf.hdr; |
| - | return ct; /* this is a lie of course */ |
| + | struct bf *b; |
| + | |
| + | bfs[current].counter = BF_FREE; /* free old one */ |
| + | current = !current; /* "incr" current */ |
| + | |
| + | b = &bfs[current]; /* look at new buffer */ |
| + | if(b->counter == BF_FREE) /* if empty */ |
| + | read_ahead(test, convert); /* fill it */ |
| + | |
| + | *dpp = &b->buf.hdr; /* set caller's ptr */ |
| + | return b->counter; |
| } | |
| /* | |
| @@ -496,6 +471,21 @@ skipit: | |
| return count; | |
| } | |
| + | /* Update count associated with the buffer, get new buffer from the queue. |
| + | Calls write_behind only if next buffer not available. |
| + | */ |
| + | static int writeit(struct testcase *test, struct tftphdr * volatile *dpp, |
| + | int ct, int convert) |
| + | { |
| + | bfs[current].counter = ct; /* set size of data to write */ |
| + | current = !current; /* switch to other buffer */ |
| + | if(bfs[current].counter != BF_FREE) /* if not free */ |
| + | write_behind(test, convert); /* flush it */ |
| + | bfs[current].counter = BF_ALLOC; /* mark as allocated */ |
| + | *dpp = &bfs[current].buf.hdr; |
| + | return ct; /* this is a lie of course */ |
| + | } |
| + | |
| /* When an error has occurred, it is possible that the two sides are out of | |
| * synch. Ie: that what I think is the other side's response to packet N is | |
| * really their response to packet N-1. | |
| @@ -543,343 +533,354 @@ static int synchnet(curl_socket_t f /* socket to flush */) | |
| return j; | |
| } | |
| - | static int test_tftpd(int argc, char **argv) |
| + | /* Based on the testno, parse the correct server commands. */ |
| + | static int tftpd_parse_servercmd(struct testcase *req) |
| { | |
| - | srvr_sockaddr_union_t me; |
| - | struct tftphdr *tp; |
| - | ssize_t n = 0; |
| - | int arg = 1; |
| - | unsigned short port = 8999; /* UDP */ |
| - | curl_socket_t sock = CURL_SOCKET_BAD; |
| - | int flag; |
| - | int rc; |
| + | FILE *stream; |
| int error; | |
| - | char errbuf[STRERROR_LEN]; |
| - | struct testcase test; |
| - | int result = 0; |
| - | srvr_sockaddr_union_t from; |
| - | curl_socklen_t fromlen; |
| - | |
| - | memset(&test, 0, sizeof(test)); |
| - | pidname = ".tftpd.pid"; |
| - | serverlogfile = "log/tftpd.log"; |
| - | serverlogslocked = 0; |
| + | stream = test2fopen(req->testno, logdir); |
| + | if(!stream) { |
| + | char errbuf[STRERROR_LEN]; |
| + | error = errno; |
| + | logmsg("fopen() failed with error (%d) %s", |
| + | error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| + | logmsg(" Could not open test file %ld", req->testno); |
| + | return 1; /* done */ |
| + | } |
| + | else { |
| + | char *orgcmd = NULL; |
| + | char *cmd = NULL; |
| + | size_t cmdsize = 0; |
| + | int num = 0; |
| - | while(argc > arg) { |
| - | const char *opt; |
| - | curl_off_t num; |
| - | if(!strcmp("--version", argv[arg])) { |
| - | printf("tftpd IPv4%s\n", |
| - | #ifdef USE_IPV6 |
| - | "/IPv6" |
| - | #else |
| - | "" |
| - | #endif |
| - | ); |
| - | return 0; |
| - | } |
| - | else if(!strcmp("--pidfile", argv[arg])) { |
| - | arg++; |
| - | if(argc > arg) |
| - | pidname = argv[arg++]; |
| - | } |
| - | else if(!strcmp("--portfile", argv[arg])) { |
| - | arg++; |
| - | if(argc > arg) |
| - | portname = argv[arg++]; |
| - | } |
| - | else if(!strcmp("--logfile", argv[arg])) { |
| - | arg++; |
| - | if(argc > arg) |
| - | serverlogfile = argv[arg++]; |
| - | } |
| - | else if(!strcmp("--logdir", argv[arg])) { |
| - | arg++; |
| - | if(argc > arg) |
| - | logdir = argv[arg++]; |
| - | } |
| - | else if(!strcmp("--ipv4", argv[arg])) { |
| - | #ifdef USE_IPV6 |
| - | ipv_inuse = "IPv4"; |
| - | use_ipv6 = FALSE; |
| - | #endif |
| - | arg++; |
| - | } |
| - | else if(!strcmp("--ipv6", argv[arg])) { |
| - | #ifdef USE_IPV6 |
| - | ipv_inuse = "IPv6"; |
| - | use_ipv6 = TRUE; |
| - | #endif |
| - | arg++; |
| + | /* get the custom server control "commands" */ |
| + | error = getpart(&orgcmd, &cmdsize, "reply", "servercmd", stream); |
| + | curlx_fclose(stream); |
| + | if(error) { |
| + | logmsg("getpart() failed with error (%d)", error); |
| + | return 1; /* done */ |
| } | |
| - | else if(!strcmp("--port", argv[arg])) { |
| - | arg++; |
| - | if(argc > arg) { |
| - | opt = argv[arg]; |
| - | if(!curlx_str_number(&opt, &num, 0xffff)) |
| - | port = (unsigned short)num; |
| - | arg++; |
| + | |
| + | cmd = orgcmd; |
| + | while(cmd && cmdsize) { |
| + | char *check; |
| + | if(sscanf(cmd, "writedelay: %d", &num) == 1) { |
| + | logmsg("instructed to delay %d secs between packets", num); |
| + | req->writedelay = num; |
| } | |
| - | } |
| - | else if(!strcmp("--srcdir", argv[arg])) { |
| - | arg++; |
| - | if(argc > arg) { |
| - | srcpath = argv[arg]; |
| - | arg++; |
| + | else { |
| + | logmsg("Unknown <servercmd> instruction found: %s", cmd); |
| } | |
| - | } |
| - | else { |
| - | puts("Usage: tftpd [option]\n" |
| - | " --version\n" |
| - | " --logfile [file]\n" |
| - | " --logdir [directory]\n" |
| - | " --pidfile [file]\n" |
| - | " --portfile [file]\n" |
| - | " --ipv4\n" |
| - | " --ipv6\n" |
| - | " --port [port]\n" |
| - | " --srcdir [path]"); |
| - | return 0; |
| - | } |
| - | } |
| - | |
| - | snprintf(loglockfile, sizeof(loglockfile), "%s/%s/tftp-%s.lock", |
| - | logdir, SERVERLOGS_LOCKDIR, ipv_inuse); |
| + | /* try to deal with CRLF or just LF */ |
| + | check = strchr(cmd, '\r'); |
| + | if(!check) |
| + | check = strchr(cmd, '\n'); |
| - | install_signal_handlers(true); |
| + | if(check) { |
| + | /* get to the letter following the newline */ |
| + | while((*check == '\r') || (*check == '\n')) |
| + | check++; |
| - | #ifdef USE_IPV6 |
| - | if(!use_ipv6) |
| - | #endif |
| - | sock = socket(AF_INET, SOCK_DGRAM, 0); |
| - | #ifdef USE_IPV6 |
| - | else |
| - | sock = socket(AF_INET6, SOCK_DGRAM, 0); |
| - | #endif |
| - | |
| - | if(CURL_SOCKET_BAD == sock) { |
| - | error = SOCKERRNO; |
| - | logmsg("Error creating socket (%d) %s", |
| - | error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| - | result = 1; |
| - | goto tftpd_cleanup; |
| + | if(!*check) |
| + | /* if we reached a zero, get out */ |
| + | break; |
| + | cmd = check; |
| + | } |
| + | else |
| + | break; |
| + | } |
| + | free(orgcmd); |
| } | |
| - | flag = 1; |
| - | if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&flag, sizeof(flag))) { |
| - | error = SOCKERRNO; |
| - | logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s", |
| - | error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| - | result = 1; |
| - | goto tftpd_cleanup; |
| - | } |
| + | return 0; /* OK! */ |
| + | } |
| - | #ifdef USE_IPV6 |
| - | if(!use_ipv6) { |
| - | #endif |
| - | memset(&me.sa4, 0, sizeof(me.sa4)); |
| - | me.sa4.sin_family = AF_INET; |
| - | me.sa4.sin_addr.s_addr = INADDR_ANY; |
| - | me.sa4.sin_port = htons(port); |
| - | rc = bind(sock, &me.sa, sizeof(me.sa4)); |
| - | #ifdef USE_IPV6 |
| - | } |
| - | else { |
| - | memset(&me.sa6, 0, sizeof(me.sa6)); |
| - | me.sa6.sin6_family = AF_INET6; |
| - | me.sa6.sin6_addr = in6addr_any; |
| - | me.sa6.sin6_port = htons(port); |
| - | rc = bind(sock, &me.sa, sizeof(me.sa6)); |
| - | } |
| - | #endif /* USE_IPV6 */ |
| - | if(rc) { |
| - | error = SOCKERRNO; |
| - | logmsg("Error binding socket on port %hu (%d) %s", port, |
| - | error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| - | result = 1; |
| - | goto tftpd_cleanup; |
| - | } |
| + | /* |
| + | * Validate file access. |
| + | */ |
| + | static int validate_access(struct testcase *test, |
| + | const char *filename, unsigned short mode) |
| + | { |
| + | char *ptr; |
| - | if(!port) { |
| - | /* The system was supposed to choose a port number, figure out which |
| - | port we actually got and update the listener port value with it. */ |
| - | curl_socklen_t la_size; |
| - | srvr_sockaddr_union_t localaddr; |
| - | #ifdef USE_IPV6 |
| - | if(!use_ipv6) |
| - | #endif |
| - | la_size = sizeof(localaddr.sa4); |
| - | #ifdef USE_IPV6 |
| - | else |
| - | la_size = sizeof(localaddr.sa6); |
| - | #endif |
| - | memset(&localaddr.sa, 0, (size_t)la_size); |
| - | if(getsockname(sock, &localaddr.sa, &la_size) < 0) { |
| - | error = SOCKERRNO; |
| - | logmsg("getsockname() failed with error (%d) %s", |
| - | error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| - | sclose(sock); |
| - | goto tftpd_cleanup; |
| - | } |
| - | switch(localaddr.sa.sa_family) { |
| - | case AF_INET: |
| - | port = ntohs(localaddr.sa4.sin_port); |
| - | break; |
| - | #ifdef USE_IPV6 |
| - | case AF_INET6: |
| - | port = ntohs(localaddr.sa6.sin6_port); |
| - | break; |
| - | #endif |
| - | default: |
| - | break; |
| - | } |
| - | if(!port) { |
| - | /* Real failure, listener port shall not be zero beyond this point. */ |
| - | logmsg("Apparently getsockname() succeeded, with listener port zero."); |
| - | logmsg("A valid reason for this failure is a binary built without"); |
| - | logmsg("proper network library linkage. This might not be the only"); |
| - | logmsg("reason, but double check it before anything else."); |
| - | result = 2; |
| - | goto tftpd_cleanup; |
| - | } |
| - | } |
| + | logmsg("trying to get file: %s mode %x", filename, mode); |
| - | tftpd_wrotepidfile = write_pidfile(pidname); |
| - | if(!tftpd_wrotepidfile) { |
| - | result = 1; |
| - | goto tftpd_cleanup; |
| - | } |
| + | if(!strncmp("verifiedserver", filename, 14)) { |
| + | char weare[128]; |
| + | size_t count = snprintf(weare, sizeof(weare), "WE ROOLZ: %ld\r\n", |
| + | (long)our_getpid()); |
| - | if(portname) { |
| - | tftpd_wroteportfile = write_portfile(portname, port); |
| - | if(!tftpd_wroteportfile) { |
| - | result = 1; |
| - | goto tftpd_cleanup; |
| - | } |
| + | logmsg("Are-we-friendly question received"); |
| + | test->buffer = strdup(weare); |
| + | test->rptr = test->buffer; /* set read pointer */ |
| + | test->bufsize = count; /* set total count */ |
| + | test->rcount = count; /* set data left to read */ |
| + | return 0; /* fine */ |
| } | |
| - | logmsg("Running %s version on port UDP/%d", ipv_inuse, (int)port); |
| - | |
| - | for(;;) { |
| - | fromlen = sizeof(from); |
| - | #ifdef USE_IPV6 |
| - | if(!use_ipv6) |
| - | #endif |
| - | fromlen = sizeof(from.sa4); |
| - | #ifdef USE_IPV6 |
| - | else |
| - | fromlen = sizeof(from.sa6); |
| - | #endif |
| - | n = (ssize_t)recvfrom(sock, &trsbuf.storage[0], sizeof(trsbuf.storage), 0, |
| - | &from.sa, &fromlen); |
| - | if(got_exit_signal) |
| - | break; |
| - | if(n < 0) { |
| - | logmsg("recvfrom"); |
| - | result = 3; |
| - | break; |
| - | } |
| + | /* find the last slash */ |
| + | ptr = strrchr(filename, '/'); |
| - | set_advisor_read_lock(loglockfile); |
| - | serverlogslocked = 1; |
| + | if(ptr) { |
| + | char partbuf[80] = "data"; |
| + | long partno; |
| + | long testno; |
| + | const char *pval; |
| + | curl_off_t num; |
| + | FILE *stream; |
| - | #ifdef USE_IPV6 |
| - | if(!use_ipv6) { |
| - | #endif |
| - | from.sa4.sin_family = AF_INET; |
| - | peer = socket(AF_INET, SOCK_DGRAM, 0); |
| - | if(CURL_SOCKET_BAD == peer) { |
| - | logmsg("socket"); |
| - | result = 2; |
| - | break; |
| - | } |
| - | if(connect(peer, &from.sa, sizeof(from.sa4)) < 0) { |
| - | logmsg("connect: fail"); |
| - | result = 1; |
| - | break; |
| - | } |
| - | #ifdef USE_IPV6 |
| - | } |
| - | else { |
| - | from.sa6.sin6_family = AF_INET6; |
| - | peer = socket(AF_INET6, SOCK_DGRAM, 0); |
| - | if(CURL_SOCKET_BAD == peer) { |
| - | logmsg("socket"); |
| - | result = 2; |
| - | break; |
| - | } |
| - | if(connect(peer, &from.sa, sizeof(from.sa6)) < 0) { |
| - | logmsg("connect: fail"); |
| - | result = 1; |
| - | break; |
| - | } |
| - | } |
| - | #endif |
| + | ptr++; /* skip the slash */ |
| - | maxtimeout = 5 * TIMEOUT; |
| + | /* skip all non-numericals following the slash */ |
| + | while(*ptr && !ISDIGIT(*ptr)) |
| + | ptr++; |
| - | tp = &trsbuf.hdr; |
| - | tp->th_opcode = ntohs(tp->th_opcode); |
| - | if(tp->th_opcode == opcode_RRQ || tp->th_opcode == opcode_WRQ) { |
| - | memset(&test, 0, sizeof(test)); |
| - | if(do_tftp(&test, tp, n) < 0) |
| - | break; |
| - | free(test.buffer); |
| + | /* get the number */ |
| + | pval = ptr; |
| + | if(!curlx_str_number(&pval, &num, INT_MAX)) |
| + | testno = (long)num; |
| + | else { |
| + | logmsg("tftpd: failed to read the test number from '%s'", filename); |
| + | return TFTP_EACCESS; |
| } | |
| - | sclose(peer); |
| - | peer = CURL_SOCKET_BAD; |
| - | |
| - | if(got_exit_signal) |
| - | break; |
| - | if(serverlogslocked) { |
| - | serverlogslocked = 0; |
| - | clear_advisor_read_lock(loglockfile); |
| + | if(testno > 10000) { |
| + | partno = testno % 10000; |
| + | testno /= 10000; |
| } | |
| + | else |
| + | partno = 0; |
| - | logmsg("end of one transfer"); |
| - | } |
| - | |
| - | tftpd_cleanup: |
| - | |
| - | if(test.ofile > 0) |
| - | close(test.ofile); |
| - | |
| - | if((peer != sock) && (peer != CURL_SOCKET_BAD)) |
| - | sclose(peer); |
| - | |
| - | if(sock != CURL_SOCKET_BAD) |
| - | sclose(sock); |
| + | logmsg("requested test number %ld part %ld", testno, partno); |
| - | if(got_exit_signal) |
| - | logmsg("signalled to die"); |
| + | test->testno = testno; |
| - | if(tftpd_wrotepidfile) |
| - | unlink(pidname); |
| - | if(tftpd_wroteportfile) |
| - | unlink(portname); |
| + | (void)tftpd_parse_servercmd(test); |
| - | if(serverlogslocked) { |
| - | serverlogslocked = 0; |
| - | clear_advisor_read_lock(loglockfile); |
| - | } |
| + | stream = test2fopen(testno, logdir); |
| - | restore_signal_handlers(true); |
| + | if(partno) |
| + | snprintf(partbuf, sizeof(partbuf), "data%ld", partno); |
| - | if(got_exit_signal) { |
| - | logmsg("========> %s tftpd (port: %d pid: %ld) exits with signal (%d)", |
| - | ipv_inuse, (int)port, (long)our_getpid(), exit_signal); |
| - | /* |
| - | * To properly set the return status of the process we |
| - | * must raise the same signal SIGINT or SIGTERM that we |
| - | * caught and let the old handler take care of it. |
| - | */ |
| - | raise(exit_signal); |
| + | if(!stream) { |
| + | char errbuf[STRERROR_LEN]; |
| + | int error = errno; |
| + | logmsg("fopen() failed with error (%d) %s", |
| + | error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| + | logmsg("Could not open test file for test: %ld", testno); |
| + | return TFTP_EACCESS; |
| + | } |
| + | else { |
| + | size_t count; |
| + | int error = getpart(&test->buffer, &count, "reply", partbuf, stream); |
| + | curlx_fclose(stream); |
| + | if(error) { |
| + | logmsg("getpart() failed with error (%d)", error); |
| + | return TFTP_EACCESS; |
| + | } |
| + | if(test->buffer) { |
| + | test->rptr = test->buffer; /* set read pointer */ |
| + | test->bufsize = count; /* set total count */ |
| + | test->rcount = count; /* set data left to read */ |
| + | } |
| + | else |
| + | return TFTP_EACCESS; |
| + | } |
| + | } |
| + | else { |
| + | logmsg("no slash found in path"); |
| + | return TFTP_EACCESS; /* failure */ |
| } | |
| - | logmsg("========> tftpd quits"); |
| - | return result; |
| + | logmsg("file opened and all is good"); |
| + | return 0; |
| + | } |
| + | |
| + | /* |
| + | * Send the requested file. |
| + | */ |
| + | static void sendtftp(struct testcase *test, const struct formats *pf) |
| + | { |
| + | int size; |
| + | ssize_t n; |
| + | /* These are volatile to live through a siglongjmp */ |
| + | volatile unsigned short sendblock; /* block count */ |
| + | struct tftphdr * volatile sdp = r_init(); /* data buffer */ |
| + | struct tftphdr * const sap = &ackbuf.hdr; /* ack buffer */ |
| + | |
| + | sendblock = 1; |
| + | #if defined(HAVE_ALARM) && defined(SIGALRM) |
| + | mysignal(SIGALRM, timer); |
| + | #endif |
| + | do { |
| + | size = readit(test, (struct tftphdr * volatile *)&sdp, pf->f_convert); |
| + | if(size < 0) { |
| + | nak(errno + 100); |
| + | return; |
| + | } |
| + | sdp->th_opcode = htons(opcode_DATA); |
| + | sdp->th_block = htons(sendblock); |
| + | timeout = 0; |
| + | #ifdef HAVE_SIGSETJMP |
| + | (void)sigsetjmp(timeoutbuf, 1); |
| + | #endif |
| + | if(test->writedelay) { |
| + | logmsg("Pausing %d seconds before %d bytes", test->writedelay, size); |
| + | curlx_wait_ms(1000 * test->writedelay); |
| + | } |
| + | |
| + | send_data: |
| + | logmsg("write"); |
| + | if(swrite(peer, sdp, size + 4) != size + 4) { |
| + | logmsg("write: fail"); |
| + | return; |
| + | } |
| + | read_ahead(test, pf->f_convert); |
| + | for(;;) { |
| + | #ifdef HAVE_ALARM |
| + | alarm(rexmtval); /* read the ack */ |
| + | #endif |
| + | logmsg("read"); |
| + | n = sread(peer, &ackbuf.storage[0], sizeof(ackbuf.storage)); |
| + | logmsg("read: %zd", n); |
| + | #ifdef HAVE_ALARM |
| + | alarm(0); |
| + | #endif |
| + | if(got_exit_signal) |
| + | return; |
| + | if(n < 0) { |
| + | logmsg("read: fail"); |
| + | return; |
| + | } |
| + | sap->th_opcode = ntohs(sap->th_opcode); |
| + | sap->th_block = ntohs(sap->th_block); |
| + | |
| + | if(sap->th_opcode == opcode_ERROR) { |
| + | logmsg("got ERROR"); |
| + | return; |
| + | } |
| + | |
| + | if(sap->th_opcode == opcode_ACK) { |
| + | if(sap->th_block == sendblock) { |
| + | break; |
| + | } |
| + | /* Re-synchronize with the other side */ |
| + | (void)synchnet(peer); |
| + | if(sap->th_block == (sendblock - 1)) { |
| + | goto send_data; |
| + | } |
| + | } |
| + | } |
| + | sendblock++; |
| + | } while(size == SEGSIZE); |
| + | } |
| + | |
| + | /* |
| + | * Receive a file. |
| + | */ |
| + | static void recvtftp(struct testcase *test, const struct formats *pf) |
| + | { |
| + | ssize_t n, size; |
| + | /* These are volatile to live through a siglongjmp */ |
| + | volatile unsigned short recvblock; /* block count */ |
| + | struct tftphdr * volatile rdp; /* data buffer */ |
| + | struct tftphdr *rap; /* ack buffer */ |
| + | |
| + | recvblock = 0; |
| + | rdp = w_init(); |
| + | #if defined(HAVE_ALARM) && defined(SIGALRM) |
| + | mysignal(SIGALRM, timer); |
| + | #endif |
| + | rap = &ackbuf.hdr; |
| + | do { |
| + | timeout = 0; |
| + | rap->th_opcode = htons(opcode_ACK); |
| + | rap->th_block = htons(recvblock); |
| + | recvblock++; |
| + | #ifdef HAVE_SIGSETJMP |
| + | (void)sigsetjmp(timeoutbuf, 1); |
| + | #endif |
| + | send_ack: |
| + | logmsg("write"); |
| + | if(swrite(peer, &ackbuf.storage[0], 4) != 4) { |
| + | logmsg("write: fail"); |
| + | goto abort; |
| + | } |
| + | write_behind(test, pf->f_convert); |
| + | for(;;) { |
| + | #ifdef HAVE_ALARM |
| + | alarm(rexmtval); |
| + | #endif |
| + | logmsg("read"); |
| + | n = sread(peer, rdp, PKTSIZE); |
| + | logmsg("read: %zd", n); |
| + | #ifdef HAVE_ALARM |
| + | alarm(0); |
| + | #endif |
| + | if(got_exit_signal) |
| + | goto abort; |
| + | if(n < 0) { /* really? */ |
| + | logmsg("read: fail"); |
| + | goto abort; |
| + | } |
| + | rdp->th_opcode = ntohs(rdp->th_opcode); |
| + | rdp->th_block = ntohs(rdp->th_block); |
| + | if(rdp->th_opcode == opcode_ERROR) |
| + | goto abort; |
| + | if(rdp->th_opcode == opcode_DATA) { |
| + | if(rdp->th_block == recvblock) { |
| + | break; /* normal */ |
| + | } |
| + | /* Re-synchronize with the other side */ |
| + | (void)synchnet(peer); |
| + | if(rdp->th_block == (recvblock - 1)) |
| + | goto send_ack; /* rexmit */ |
| + | } |
| + | } |
| + | |
| + | size = writeit(test, &rdp, (int)(n - 4), pf->f_convert); |
| + | if(size != (n - 4)) { /* ahem */ |
| + | if(size < 0) |
| + | nak(errno + 100); |
| + | else |
| + | nak(TFTP_ENOSPACE); |
| + | goto abort; |
| + | } |
| + | } while(size == SEGSIZE); |
| + | write_behind(test, pf->f_convert); |
| + | /* close the output file as early as possible after upload completion */ |
| + | if(test->ofile > 0) { |
| + | close(test->ofile); |
| + | test->ofile = 0; |
| + | } |
| + | |
| + | rap->th_opcode = htons(opcode_ACK); /* send the "final" ack */ |
| + | rap->th_block = htons(recvblock); |
| + | (void)swrite(peer, &ackbuf.storage[0], 4); |
| + | #if defined(HAVE_ALARM) && defined(SIGALRM) |
| + | mysignal(SIGALRM, justtimeout); /* just abort read on timeout */ |
| + | alarm(rexmtval); |
| + | #endif |
| + | /* normally times out and quits */ |
| + | n = sread(peer, &trsbuf.storage[0], sizeof(trsbuf.storage)); |
| + | #ifdef HAVE_ALARM |
| + | alarm(0); |
| + | #endif |
| + | if(got_exit_signal) |
| + | goto abort; |
| + | if(n >= 4 && /* if read some data */ |
| + | rdp->th_opcode == opcode_DATA && /* and got a data block */ |
| + | recvblock == rdp->th_block) { /* then my last ack was lost */ |
| + | (void)swrite(peer, &ackbuf.storage[0], 4); /* resend final ack */ |
| + | } |
| + | abort: |
| + | /* make sure the output file is closed in case of abort */ |
| + | if(test->ofile > 0) { |
| + | close(test->ofile); |
| + | test->ofile = 0; |
| + | } |
| + | return; |
| } | |
| /* | |
| @@ -1005,382 +1006,341 @@ static int do_tftp(struct testcase *test, struct tftphdr *tp, ssize_t size) | |
| return 0; | |
| } | |
| - | /* Based on the testno, parse the correct server commands. */ |
| - | static int tftpd_parse_servercmd(struct testcase *req) |
| + | static int test_tftpd(int argc, char **argv) |
| { | |
| - | FILE *stream; |
| + | srvr_sockaddr_union_t me; |
| + | struct tftphdr *tp; |
| + | ssize_t n = 0; |
| + | int arg = 1; |
| + | unsigned short port = 8999; /* UDP */ |
| + | curl_socket_t sock = CURL_SOCKET_BAD; |
| + | int flag; |
| + | int rc; |
| int error; | |
| + | char errbuf[STRERROR_LEN]; |
| + | struct testcase test; |
| + | int result = 0; |
| + | srvr_sockaddr_union_t from; |
| + | curl_socklen_t fromlen; |
| - | stream = test2fopen(req->testno, logdir); |
| - | if(!stream) { |
| - | char errbuf[STRERROR_LEN]; |
| - | error = errno; |
| - | logmsg("fopen() failed with error (%d) %s", |
| - | error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| - | logmsg(" Could not open test file %ld", req->testno); |
| - | return 1; /* done */ |
| - | } |
| - | else { |
| - | char *orgcmd = NULL; |
| - | char *cmd = NULL; |
| - | size_t cmdsize = 0; |
| - | int num = 0; |
| + | memset(&test, 0, sizeof(test)); |
| - | /* get the custom server control "commands" */ |
| - | error = getpart(&orgcmd, &cmdsize, "reply", "servercmd", stream); |
| - | curlx_fclose(stream); |
| - | if(error) { |
| - | logmsg("getpart() failed with error (%d)", error); |
| - | return 1; /* done */ |
| - | } |
| + | pidname = ".tftpd.pid"; |
| + | serverlogfile = "log/tftpd.log"; |
| + | serverlogslocked = 0; |
| - | cmd = orgcmd; |
| - | while(cmd && cmdsize) { |
| - | char *check; |
| - | if(sscanf(cmd, "writedelay: %d", &num) == 1) { |
| - | logmsg("instructed to delay %d secs between packets", num); |
| - | req->writedelay = num; |
| - | } |
| - | else { |
| - | logmsg("Unknown <servercmd> instruction found: %s", cmd); |
| + | while(argc > arg) { |
| + | const char *opt; |
| + | curl_off_t num; |
| + | if(!strcmp("--version", argv[arg])) { |
| + | printf("tftpd IPv4%s\n", |
| + | #ifdef USE_IPV6 |
| + | "/IPv6" |
| + | #else |
| + | "" |
| + | #endif |
| + | ); |
| + | return 0; |
| + | } |
| + | else if(!strcmp("--pidfile", argv[arg])) { |
| + | arg++; |
| + | if(argc > arg) |
| + | pidname = argv[arg++]; |
| + | } |
| + | else if(!strcmp("--portfile", argv[arg])) { |
| + | arg++; |
| + | if(argc > arg) |
| + | portname = argv[arg++]; |
| + | } |
| + | else if(!strcmp("--logfile", argv[arg])) { |
| + | arg++; |
| + | if(argc > arg) |
| + | serverlogfile = argv[arg++]; |
| + | } |
| + | else if(!strcmp("--logdir", argv[arg])) { |
| + | arg++; |
| + | if(argc > arg) |
| + | logdir = argv[arg++]; |
| + | } |
| + | else if(!strcmp("--ipv4", argv[arg])) { |
| + | #ifdef USE_IPV6 |
| + | ipv_inuse = "IPv4"; |
| + | use_ipv6 = FALSE; |
| + | #endif |
| + | arg++; |
| + | } |
| + | else if(!strcmp("--ipv6", argv[arg])) { |
| + | #ifdef USE_IPV6 |
| + | ipv_inuse = "IPv6"; |
| + | use_ipv6 = TRUE; |
| + | #endif |
| + | arg++; |
| + | } |
| + | else if(!strcmp("--port", argv[arg])) { |
| + | arg++; |
| + | if(argc > arg) { |
| + | opt = argv[arg]; |
| + | if(!curlx_str_number(&opt, &num, 0xffff)) |
| + | port = (unsigned short)num; |
| + | arg++; |
| } | |
| - | /* try to deal with CRLF or just LF */ |
| - | check = strchr(cmd, '\r'); |
| - | if(!check) |
| - | check = strchr(cmd, '\n'); |
| - | |
| - | if(check) { |
| - | /* get to the letter following the newline */ |
| - | while((*check == '\r') || (*check == '\n')) |
| - | check++; |
| - | |
| - | if(!*check) |
| - | /* if we reached a zero, get out */ |
| - | break; |
| - | cmd = check; |
| + | } |
| + | else if(!strcmp("--srcdir", argv[arg])) { |
| + | arg++; |
| + | if(argc > arg) { |
| + | srcpath = argv[arg]; |
| + | arg++; |
| } | |
| - | else |
| - | break; |
| } | |
| - | free(orgcmd); |
| - | } |
| - | |
| - | return 0; /* OK! */ |
| - | } |
| - | |
| - | /* |
| - | * Validate file access. |
| - | */ |
| - | static int validate_access(struct testcase *test, |
| - | const char *filename, unsigned short mode) |
| - | { |
| - | char *ptr; |
| - | |
| - | logmsg("trying to get file: %s mode %x", filename, mode); |
| - | |
| - | if(!strncmp("verifiedserver", filename, 14)) { |
| - | char weare[128]; |
| - | size_t count = snprintf(weare, sizeof(weare), "WE ROOLZ: %ld\r\n", |
| - | (long)our_getpid()); |
| - | |
| - | logmsg("Are-we-friendly question received"); |
| - | test->buffer = strdup(weare); |
| - | test->rptr = test->buffer; /* set read pointer */ |
| - | test->bufsize = count; /* set total count */ |
| - | test->rcount = count; /* set data left to read */ |
| - | return 0; /* fine */ |
| - | } |
| - | |
| - | /* find the last slash */ |
| - | ptr = strrchr(filename, '/'); |
| - | |
| - | if(ptr) { |
| - | char partbuf[80] = "data"; |
| - | long partno; |
| - | long testno; |
| - | const char *pval; |
| - | curl_off_t num; |
| - | FILE *stream; |
| - | |
| - | ptr++; /* skip the slash */ |
| - | |
| - | /* skip all non-numericals following the slash */ |
| - | while(*ptr && !ISDIGIT(*ptr)) |
| - | ptr++; |
| - | |
| - | /* get the number */ |
| - | pval = ptr; |
| - | if(!curlx_str_number(&pval, &num, INT_MAX)) |
| - | testno = (long)num; |
| else { | |
| - | logmsg("tftpd: failed to read the test number from '%s'", filename); |
| - | return TFTP_EACCESS; |
| - | } |
| - | |
| - | if(testno > 10000) { |
| - | partno = testno % 10000; |
| - | testno /= 10000; |
| + | puts("Usage: tftpd [option]\n" |
| + | " --version\n" |
| + | " --logfile [file]\n" |
| + | " --logdir [directory]\n" |
| + | " --pidfile [file]\n" |
| + | " --portfile [file]\n" |
| + | " --ipv4\n" |
| + | " --ipv6\n" |
| + | " --port [port]\n" |
| + | " --srcdir [path]"); |
| + | return 0; |
| } | |
| - | else |
| - | partno = 0; |
| + | } |
| - | logmsg("requested test number %ld part %ld", testno, partno); |
| + | snprintf(loglockfile, sizeof(loglockfile), "%s/%s/tftp-%s.lock", |
| + | logdir, SERVERLOGS_LOCKDIR, ipv_inuse); |
| - | test->testno = testno; |
| + | install_signal_handlers(true); |
| - | (void)tftpd_parse_servercmd(test); |
| + | #ifdef USE_IPV6 |
| + | if(!use_ipv6) |
| + | #endif |
| + | sock = socket(AF_INET, SOCK_DGRAM, 0); |
| + | #ifdef USE_IPV6 |
| + | else |
| + | sock = socket(AF_INET6, SOCK_DGRAM, 0); |
| + | #endif |
| - | stream = test2fopen(testno, logdir); |
| + | if(CURL_SOCKET_BAD == sock) { |
| + | error = SOCKERRNO; |
| + | logmsg("Error creating socket (%d) %s", |
| + | error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| + | result = 1; |
| + | goto tftpd_cleanup; |
| + | } |
| - | if(partno) |
| - | snprintf(partbuf, sizeof(partbuf), "data%ld", partno); |
| + | flag = 1; |
| + | if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&flag, sizeof(flag))) { |
| + | error = SOCKERRNO; |
| + | logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s", |
| + | error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| + | result = 1; |
| + | goto tftpd_cleanup; |
| + | } |
| - | if(!stream) { |
| - | char errbuf[STRERROR_LEN]; |
| - | int error = errno; |
| - | logmsg("fopen() failed with error (%d) %s", |
| - | error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| - | logmsg("Could not open test file for test: %ld", testno); |
| - | return TFTP_EACCESS; |
| - | } |
| - | else { |
| - | size_t count; |
| - | int error = getpart(&test->buffer, &count, "reply", partbuf, stream); |
| - | curlx_fclose(stream); |
| - | if(error) { |
| - | logmsg("getpart() failed with error (%d)", error); |
| - | return TFTP_EACCESS; |
| - | } |
| - | if(test->buffer) { |
| - | test->rptr = test->buffer; /* set read pointer */ |
| - | test->bufsize = count; /* set total count */ |
| - | test->rcount = count; /* set data left to read */ |
| - | } |
| - | else |
| - | return TFTP_EACCESS; |
| - | } |
| + | #ifdef USE_IPV6 |
| + | if(!use_ipv6) { |
| + | #endif |
| + | memset(&me.sa4, 0, sizeof(me.sa4)); |
| + | me.sa4.sin_family = AF_INET; |
| + | me.sa4.sin_addr.s_addr = INADDR_ANY; |
| + | me.sa4.sin_port = htons(port); |
| + | rc = bind(sock, &me.sa, sizeof(me.sa4)); |
| + | #ifdef USE_IPV6 |
| } | |
| else { | |
| - | logmsg("no slash found in path"); |
| - | return TFTP_EACCESS; /* failure */ |
| + | memset(&me.sa6, 0, sizeof(me.sa6)); |
| + | me.sa6.sin6_family = AF_INET6; |
| + | me.sa6.sin6_addr = in6addr_any; |
| + | me.sa6.sin6_port = htons(port); |
| + | rc = bind(sock, &me.sa, sizeof(me.sa6)); |
| + | } |
| + | #endif /* USE_IPV6 */ |
| + | if(rc) { |
| + | error = SOCKERRNO; |
| + | logmsg("Error binding socket on port %hu (%d) %s", port, |
| + | error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| + | result = 1; |
| + | goto tftpd_cleanup; |
| } | |
| - | logmsg("file opened and all is good"); |
| - | return 0; |
| - | } |
| - | |
| - | /* |
| - | * Send the requested file. |
| - | */ |
| - | static void sendtftp(struct testcase *test, const struct formats *pf) |
| - | { |
| - | int size; |
| - | ssize_t n; |
| - | /* These are volatile to live through a siglongjmp */ |
| - | volatile unsigned short sendblock; /* block count */ |
| - | struct tftphdr * volatile sdp = r_init(); /* data buffer */ |
| - | struct tftphdr * const sap = &ackbuf.hdr; /* ack buffer */ |
| - | |
| - | sendblock = 1; |
| - | #if defined(HAVE_ALARM) && defined(SIGALRM) |
| - | mysignal(SIGALRM, timer); |
| + | if(!port) { |
| + | /* The system was supposed to choose a port number, figure out which |
| + | port we actually got and update the listener port value with it. */ |
| + | curl_socklen_t la_size; |
| + | srvr_sockaddr_union_t localaddr; |
| + | #ifdef USE_IPV6 |
| + | if(!use_ipv6) |
| #endif | |
| - | do { |
| - | size = readit(test, (struct tftphdr * volatile *)&sdp, pf->f_convert); |
| - | if(size < 0) { |
| - | nak(errno + 100); |
| - | return; |
| - | } |
| - | sdp->th_opcode = htons(opcode_DATA); |
| - | sdp->th_block = htons(sendblock); |
| - | timeout = 0; |
| - | #ifdef HAVE_SIGSETJMP |
| - | (void)sigsetjmp(timeoutbuf, 1); |
| + | la_size = sizeof(localaddr.sa4); |
| + | #ifdef USE_IPV6 |
| + | else |
| + | la_size = sizeof(localaddr.sa6); |
| #endif | |
| - | if(test->writedelay) { |
| - | logmsg("Pausing %d seconds before %d bytes", test->writedelay, size); |
| - | curlx_wait_ms(1000 * test->writedelay); |
| - | } |
| - | |
| - | send_data: |
| - | logmsg("write"); |
| - | if(swrite(peer, sdp, size + 4) != size + 4) { |
| - | logmsg("write: fail"); |
| - | return; |
| + | memset(&localaddr.sa, 0, (size_t)la_size); |
| + | if(getsockname(sock, &localaddr.sa, &la_size) < 0) { |
| + | error = SOCKERRNO; |
| + | logmsg("getsockname() failed with error (%d) %s", |
| + | error, curlx_strerror(error, errbuf, sizeof(errbuf))); |
| + | sclose(sock); |
| + | goto tftpd_cleanup; |
| } | |
| - | read_ahead(test, pf->f_convert); |
| - | for(;;) { |
| - | #ifdef HAVE_ALARM |
| - | alarm(rexmtval); /* read the ack */ |
| - | #endif |
| - | logmsg("read"); |
| - | n = sread(peer, &ackbuf.storage[0], sizeof(ackbuf.storage)); |
| - | logmsg("read: %zd", n); |
| - | #ifdef HAVE_ALARM |
| - | alarm(0); |
| + | switch(localaddr.sa.sa_family) { |
| + | case AF_INET: |
| + | port = ntohs(localaddr.sa4.sin_port); |
| + | break; |
| + | #ifdef USE_IPV6 |
| + | case AF_INET6: |
| + | port = ntohs(localaddr.sa6.sin6_port); |
| + | break; |
| #endif | |
| - | if(got_exit_signal) |
| - | return; |
| - | if(n < 0) { |
| - | logmsg("read: fail"); |
| - | return; |
| - | } |
| - | sap->th_opcode = ntohs(sap->th_opcode); |
| - | sap->th_block = ntohs(sap->th_block); |
| + | default: |
| + | break; |
| + | } |
| + | if(!port) { |
| + | /* Real failure, listener port shall not be zero beyond this point. */ |
| + | logmsg("Apparently getsockname() succeeded, with listener port zero."); |
| + | logmsg("A valid reason for this failure is a binary built without"); |
| + | logmsg("proper network library linkage. This might not be the only"); |
| + | logmsg("reason, but double check it before anything else."); |
| + | result = 2; |
| + | goto tftpd_cleanup; |
| + | } |
| + | } |
| - | if(sap->th_opcode == opcode_ERROR) { |
| - | logmsg("got ERROR"); |
| - | return; |
| - | } |
| + | tftpd_wrotepidfile = write_pidfile(pidname); |
| + | if(!tftpd_wrotepidfile) { |
| + | result = 1; |
| + | goto tftpd_cleanup; |
| + | } |
| - | if(sap->th_opcode == opcode_ACK) { |
| - | if(sap->th_block == sendblock) { |
| - | break; |
| - | } |
| - | /* Re-synchronize with the other side */ |
| - | (void)synchnet(peer); |
| - | if(sap->th_block == (sendblock - 1)) { |
| - | goto send_data; |
| - | } |
| - | } |
| + | if(portname) { |
| + | tftpd_wroteportfile = write_portfile(portname, port); |
| + | if(!tftpd_wroteportfile) { |
| + | result = 1; |
| + | goto tftpd_cleanup; |
| } | |
| - | sendblock++; |
| - | } while(size == SEGSIZE); |
| - | } |
| + | } |
| - | /* |
| - | * Receive a file. |
| - | */ |
| - | static void recvtftp(struct testcase *test, const struct formats *pf) |
| - | { |
| - | ssize_t n, size; |
| - | /* These are volatile to live through a siglongjmp */ |
| - | volatile unsigned short recvblock; /* block count */ |
| - | struct tftphdr * volatile rdp; /* data buffer */ |
| - | struct tftphdr *rap; /* ack buffer */ |
| + | logmsg("Running %s version on port UDP/%d", ipv_inuse, (int)port); |
| - | recvblock = 0; |
| - | rdp = w_init(); |
| - | #if defined(HAVE_ALARM) && defined(SIGALRM) |
| - | mysignal(SIGALRM, timer); |
| + | for(;;) { |
| + | fromlen = sizeof(from); |
| + | #ifdef USE_IPV6 |
| + | if(!use_ipv6) |
| #endif | |
| - | rap = &ackbuf.hdr; |
| - | do { |
| - | timeout = 0; |
| - | rap->th_opcode = htons(opcode_ACK); |
| - | rap->th_block = htons(recvblock); |
| - | recvblock++; |
| - | #ifdef HAVE_SIGSETJMP |
| - | (void)sigsetjmp(timeoutbuf, 1); |
| + | fromlen = sizeof(from.sa4); |
| + | #ifdef USE_IPV6 |
| + | else |
| + | fromlen = sizeof(from.sa6); |
| #endif | |
| - | send_ack: |
| - | logmsg("write"); |
| - | if(swrite(peer, &ackbuf.storage[0], 4) != 4) { |
| - | logmsg("write: fail"); |
| - | goto abort; |
| + | n = (ssize_t)recvfrom(sock, &trsbuf.storage[0], sizeof(trsbuf.storage), 0, |
| + | &from.sa, &fromlen); |
| + | if(got_exit_signal) |
| + | break; |
| + | if(n < 0) { |
| + | logmsg("recvfrom"); |
| + | result = 3; |
| + | break; |
| } | |
| - | write_behind(test, pf->f_convert); |
| - | for(;;) { |
| - | #ifdef HAVE_ALARM |
| - | alarm(rexmtval); |
| - | #endif |
| - | logmsg("read"); |
| - | n = sread(peer, rdp, PKTSIZE); |
| - | logmsg("read: %zd", n); |
| - | #ifdef HAVE_ALARM |
| - | alarm(0); |
| + | |
| + | set_advisor_read_lock(loglockfile); |
| + | serverlogslocked = 1; |
| + | |
| + | #ifdef USE_IPV6 |
| + | if(!use_ipv6) { |
| #endif | |
| - | if(got_exit_signal) |
| - | goto abort; |
| - | if(n < 0) { /* really? */ |
| - | logmsg("read: fail"); |
| - | goto abort; |
| + | from.sa4.sin_family = AF_INET; |
| + | peer = socket(AF_INET, SOCK_DGRAM, 0); |
| + | if(CURL_SOCKET_BAD == peer) { |
| + | logmsg("socket"); |
| + | result = 2; |
| + | break; |
| } | |
| - | rdp->th_opcode = ntohs(rdp->th_opcode); |
| - | rdp->th_block = ntohs(rdp->th_block); |
| - | if(rdp->th_opcode == opcode_ERROR) |
| - | goto abort; |
| - | if(rdp->th_opcode == opcode_DATA) { |
| - | if(rdp->th_block == recvblock) { |
| - | break; /* normal */ |
| - | } |
| - | /* Re-synchronize with the other side */ |
| - | (void)synchnet(peer); |
| - | if(rdp->th_block == (recvblock - 1)) |
| - | goto send_ack; /* rexmit */ |
| + | if(connect(peer, &from.sa, sizeof(from.sa4)) < 0) { |
| + | logmsg("connect: fail"); |
| + | result = 1; |
| + | break; |
| + | } |
| + | #ifdef USE_IPV6 |
| + | } |
| + | else { |
| + | from.sa6.sin6_family = AF_INET6; |
| + | peer = socket(AF_INET6, SOCK_DGRAM, 0); |
| + | if(CURL_SOCKET_BAD == peer) { |
| + | logmsg("socket"); |
| + | result = 2; |
| + | break; |
| + | } |
| + | if(connect(peer, &from.sa, sizeof(from.sa6)) < 0) { |
| + | logmsg("connect: fail"); |
| + | result = 1; |
| + | break; |
| } | |
| } | |
| + | #endif |
| - | size = writeit(test, &rdp, (int)(n - 4), pf->f_convert); |
| - | if(size != (n - 4)) { /* ahem */ |
| - | if(size < 0) |
| - | nak(errno + 100); |
| - | else |
| - | nak(TFTP_ENOSPACE); |
| - | goto abort; |
| + | maxtimeout = 5 * TIMEOUT; |
| + | |
| + | tp = &trsbuf.hdr; |
| + | tp->th_opcode = ntohs(tp->th_opcode); |
| + | if(tp->th_opcode == opcode_RRQ || tp->th_opcode == opcode_WRQ) { |
| + | memset(&test, 0, sizeof(test)); |
| + | if(do_tftp(&test, tp, n) < 0) |
| + | break; |
| + | free(test.buffer); |
| } | |
| - | } while(size == SEGSIZE); |
| - | write_behind(test, pf->f_convert); |
| - | /* close the output file as early as possible after upload completion */ |
| - | if(test->ofile > 0) { |
| - | close(test->ofile); |
| - | test->ofile = 0; |
| + | sclose(peer); |
| + | peer = CURL_SOCKET_BAD; |
| + | |
| + | if(got_exit_signal) |
| + | break; |
| + | |
| + | if(serverlogslocked) { |
| + | serverlogslocked = 0; |
| + | clear_advisor_read_lock(loglockfile); |
| + | } |
| + | |
| + | logmsg("end of one transfer"); |
| } | |
| - | rap->th_opcode = htons(opcode_ACK); /* send the "final" ack */ |
| - | rap->th_block = htons(recvblock); |
| - | (void)swrite(peer, &ackbuf.storage[0], 4); |
| - | #if defined(HAVE_ALARM) && defined(SIGALRM) |
| - | mysignal(SIGALRM, justtimeout); /* just abort read on timeout */ |
| - | alarm(rexmtval); |
| - | #endif |
| - | /* normally times out and quits */ |
| - | n = sread(peer, &trsbuf.storage[0], sizeof(trsbuf.storage)); |
| - | #ifdef HAVE_ALARM |
| - | alarm(0); |
| - | #endif |
| + | tftpd_cleanup: |
| + | |
| + | if(test.ofile > 0) |
| + | close(test.ofile); |
| + | |
| + | if((peer != sock) && (peer != CURL_SOCKET_BAD)) |
| + | sclose(peer); |
| + | |
| + | if(sock != CURL_SOCKET_BAD) |
| + | sclose(sock); |
| + | |
| if(got_exit_signal) | |
| - | goto abort; |
| - | if(n >= 4 && /* if read some data */ |
| - | rdp->th_opcode == opcode_DATA && /* and got a data block */ |
| - | recvblock == rdp->th_block) { /* then my last ack was lost */ |
| - | (void)swrite(peer, &ackbuf.storage[0], 4); /* resend final ack */ |
| - | } |
| - | abort: |
| - | /* make sure the output file is closed in case of abort */ |
| - | if(test->ofile > 0) { |
| - | close(test->ofile); |
| - | test->ofile = 0; |
| + | logmsg("signalled to die"); |
| + | |
| + | if(tftpd_wrotepidfile) |
| + | unlink(pidname); |
| + | if(tftpd_wroteportfile) |
| + | unlink(portname); |
| + | |
| + | if(serverlogslocked) { |
| + | serverlogslocked = 0; |
| + | clear_advisor_read_lock(loglockfile); |
| } | |
| - | return; |
| - | } |
| - | /* |
| - | * Send a nak packet (error message). Error code passed in is one of the |
| - | * standard TFTP codes, or a Unix errno offset by 100. |
| - | */ |
| - | static void nak(int error) |
| - | { |
| - | struct tftphdr *tp; |
| - | int length; |
| - | struct errmsg *pe; |
| + | restore_signal_handlers(true); |
| - | tp = &trsbuf.hdr; |
| - | tp->th_opcode = htons(opcode_ERROR); |
| - | tp->th_code = htons((unsigned short)error); |
| - | for(pe = errmsgs; pe->e_code >= 0; pe++) |
| - | if(pe->e_code == error) |
| - | break; |
| - | if(pe->e_code < 0) { |
| - | curlx_strerror(error - 100, pe->e_msg, sizeof(pe->e_msg)); |
| - | tp->th_code = TFTP_EUNDEF; /* set 'undef' errorcode */ |
| + | if(got_exit_signal) { |
| + | logmsg("========> %s tftpd (port: %d pid: %ld) exits with signal (%d)", |
| + | ipv_inuse, (int)port, (long)our_getpid(), exit_signal); |
| + | /* |
| + | * To properly set the return status of the process we |
| + | * must raise the same signal SIGINT or SIGTERM that we |
| + | * caught and let the old handler take care of it. |
| + | */ |
| + | raise(exit_signal); |
| } | |
| - | length = (int)strlen(pe->e_msg); |
| - | /* we use memcpy() instead of strcpy() in order to avoid buffer overflow |
| - | * report from glibc with FORTIFY_SOURCE */ |
| - | memcpy(tp->th_msg, pe->e_msg, length + 1); |
| - | length += 5; |
| - | if(swrite(peer, &trsbuf.storage[0], length) != length) |
| - | logmsg("nak: fail\n"); |
| + | logmsg("========> tftpd quits"); |
| + | return result; |
| } | |