]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
build: omit forward declarations
authorViktor Szakats <commit@vsz.me>
Tue, 13 Jan 2026 14:40:09 +0000 (15:40 +0100)
committerViktor Szakats <commit@vsz.me>
Tue, 13 Jan 2026 20:15:36 +0000 (21:15 +0100)
- drop redundant forward declarations.
- reorder local functions to not need forward declarations.
- tftpd: merge two `ifdef` blocks.

Closes #20297

lib/curlx/inet_pton.c
lib/hostip.c
lib/http_aws_sigv4.c
lib/parsedate.c
lib/vtls/openssl.c
src/tool_cb_hdr.c
tests/http/testenv/mod_curltest/mod_curltest.c
tests/server/sws.c
tests/server/tftpd.c

index 703a7b96cbff9e5089314adaafe6afddba58eb96..448f1357c6e9a6c0a136e45a344c68022100b72d 100644 (file)
  * 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 */
index a0e1d40c81fdbfb227b736773c74c08fe86844f0..ced2ba2547c39ffdf8d4dda380839485e11e9306 100644 (file)
  * 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
index 598d8cbe5fcbfc51191e6525a741d8c322b51806..38e81fca434abc91de2e0c299277f62a933dea21 100644 (file)
@@ -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 */
index 9b4c483858af82bdb1cbc002bdd6e3e419edb7c6..9a3060bd50be97332fd44af19bc34df264bd0fc3 100644 (file)
 
 */
 
-/*
- * 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
index 84e6eaadd5e11feb1e7f1cabfa1c17f6e6de0c0e..7390af8c64c994ef3662860c398cf188b2598f0f 100644 (file)
 #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;
index 3b84f65d1fd10073920a651d434ee65de6e1d32d..e65e617a15bb0b1c77399a635eafd6fa3ae738b5 100644 (file)
@@ -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
index 28fa5ff57d2786b2b0c3b9a6cdaa27944dc086cc..c27596a37ef9fd2fcd4db127d7ea2bbea1314ee2 100644 (file)
 #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
+};
index de0f096d467f194c28444990768176e0ac294c2e..59f41c8e0302ee1aaa0d48c8f904de7ac17edf63 100644 (file)
@@ -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;
index 6902b39e7d6f73a00f8aed55e74bc2dde5192990..37761fa4c70000ac12257e4450ee1f938e2f307e 100644 (file)
@@ -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;
 }