From: Viktor Szakats Date: Tue, 13 Jan 2026 14:40:09 +0000 (+0100) Subject: build: omit forward declarations X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=60f9d3dd7b52ec6eed890e74856de767de6b6a34;p=thirdparty%2Fcurl.git build: omit forward declarations - drop redundant forward declarations. - reorder local functions to not need forward declarations. - tftpd: merge two `ifdef` blocks. Closes #20297 --- diff --git a/lib/curlx/inet_pton.c b/lib/curlx/inet_pton.c index 703a7b96cb..448f1357c6 100644 --- a/lib/curlx/inet_pton.c +++ b/lib/curlx/inet_pton.c @@ -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 */ diff --git a/lib/hostip.c b/lib/hostip.c index a0e1d40c81..ced2ba2547 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -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 diff --git a/lib/http_aws_sigv4.c b/lib/http_aws_sigv4.c index 598d8cbe5f..38e81fca43 100644 --- a/lib/http_aws_sigv4.c +++ b/lib/http_aws_sigv4.c @@ -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 */ diff --git a/lib/parsedate.c b/lib/parsedate.c index 9b4c483858..9a3060bd50 100644 --- a/lib/parsedate.c +++ b/lib/parsedate.c @@ -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 diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 84e6eaadd5..7390af8c64 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -107,8 +107,6 @@ #include /* 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; diff --git a/src/tool_cb_hdr.c b/src/tool_cb_hdr.c index 3b84f65d1f..e65e617a15 100644 --- a/src/tool_cb_hdr.c +++ b/src/tool_cb_hdr.c @@ -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 diff --git a/tests/http/testenv/mod_curltest/mod_curltest.c b/tests/http/testenv/mod_curltest/mod_curltest.c index 28fa5ff57d..c27596a37e 100644 --- a/tests/http/testenv/mod_curltest/mod_curltest.c +++ b/tests/http/testenv/mod_curltest/mod_curltest.c @@ -35,66 +35,6 @@ #include #include -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 +}; diff --git a/tests/server/sws.c b/tests/server/sws.c index de0f096d46..59f41c8e03 100644 --- a/tests/server/sws.c +++ b/tests/server/sws.c @@ -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; diff --git a/tests/server/tftpd.c b/tests/server/tftpd.c index 6902b39e7d..37761fa4c7 100644 --- a/tests/server/tftpd.c +++ b/tests/server/tftpd.c @@ -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 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 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; }