From d7efee9bd7e8933eb6a204a4ac6451e0abc9ce14 Mon Sep 17 00:00:00 2001 From: Christoph Huber Date: Tue, 29 Aug 2023 16:50:45 +0200 Subject: [PATCH 1/5] retest: fix format string in test_listcases for size_t argument (#922) --- test/test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.c b/test/test.c index 2e25c310b..55585a12e 100644 --- a/test/test.c +++ b/test/test.c @@ -861,7 +861,7 @@ void test_listcases(void) n = RE_ARRAY_SIZE(tests); nh = (n+1)/2; - (void)re_printf("\n%u test cases:\n", n); + (void)re_printf("\n%zu test cases:\n", n); for (i=0; i Date: Wed, 30 Aug 2023 15:57:02 +0200 Subject: [PATCH 2/5] httpauth: http digest challenge request using RFC 7616 (#919) * httpauth: http digest challenge request using RFC 7616 + definition of a request struct allowing to handle necessary data used for the digest challenge + adding data fields to challenge struct to support the new data fields * httpauth: test cases for http digest challenge requests * httpauth: fix mingw by casting time_t when printing timestamp * httpauth: rename digest challenge request functions * httpauth: fix testcase with new function name * httpauth: fixed size integer cast of time_t. hashlen variable to size_t * httpauth: use a stack array for the hash instead of heap --- include/re_httpauth.h | 29 +++++++ src/httpauth/digest.c | 185 +++++++++++++++++++++++++++++++++++++++++- test/httpauth.c | 177 +++++++++++++++++++++++++++++++++++++++- test/test.c | 1 + test/test.h | 1 + 5 files changed, 391 insertions(+), 2 deletions(-) diff --git a/include/re_httpauth.h b/include/re_httpauth.h index 83f6d3b21..0fce26dbd 100644 --- a/include/re_httpauth.h +++ b/include/re_httpauth.h @@ -5,6 +5,21 @@ */ +/** HTTP digest request challenge */ +struct httpauth_digest_chall_req { + char *realm; + char *domain; + char *nonce; + char *opaque; + bool stale; + char *algorithm; + char *qop; + + /* optional */ + char *charset; + bool userhash; +}; + /** HTTP Digest Challenge */ struct httpauth_digest_chall { struct pl realm; @@ -15,6 +30,9 @@ struct httpauth_digest_chall { struct pl stale; struct pl algorithm; struct pl qop; + struct pl domain; + struct pl charset; + struct pl userhash; }; /** HTTP Digest response */ @@ -62,6 +80,17 @@ int httpauth_digest_make_response(struct httpauth_digest_resp **resp, int httpauth_digest_response_encode(const struct httpauth_digest_resp *resp, struct mbuf *mb); + +int httpauth_digest_chall_req_print(struct re_printf *pf, + const struct httpauth_digest_chall_req *req); +int httpauth_digest_chall_request(struct httpauth_digest_chall_req **preq, + const char *realm, const char *etag, const char *qop); +int httpauth_digest_chall_request_full(struct httpauth_digest_chall_req **preq, + const char *real, const char *domain, const char *etag, + const char *opaque, const bool stale, const char *algo, + const char *qop, const char *charset, const bool userhash); + + struct httpauth_basic *httpauth_basic_alloc(void); int httpauth_basic_decode(struct httpauth_basic *basic, const struct pl *hval); diff --git a/src/httpauth/digest.c b/src/httpauth/digest.c index d10019535..c0788b153 100644 --- a/src/httpauth/digest.c +++ b/src/httpauth/digest.c @@ -1,14 +1,17 @@ /** - * @file digest.c HTTP Digest authentication (RFC 2617) + * @file digest.c HTTP Digest authentication (RFC 2617) - obsolete + * HTTP Digest authentication (RFC 7616) - wip * * Copyright (C) 2010 Creytiv.com */ #include +#include #include #include #include #include #include +#include #include #include @@ -410,3 +413,183 @@ int httpauth_digest_response_encode(const struct httpauth_digest_resp *resp, mbuf_set_pos(mb, 0); return err; } + + +static void httpauth_digest_chall_req_destructor(void *arg) +{ + struct httpauth_digest_chall_req *req = arg; + + mem_deref(req->realm); + mem_deref(req->domain); + mem_deref(req->nonce); + mem_deref(req->opaque); + mem_deref(req->algorithm); + mem_deref(req->qop); + mem_deref(req->charset); +} + + +static int generate_nonce(char **pnonce, const time_t ts, + const char *etag, const char *secret) +{ + struct mbuf *mb = NULL; + char *nonce = NULL; + uint8_t hash [SHA256_DIGEST_LENGTH]; + int err = 0; + + mb = mbuf_alloc(32); + if (!mb) + return ENOMEM; + + if (str_isset(secret)) + err = mbuf_printf(mb, "%"PRIu64":%s:%s", + (uint64_t)ts, etag, secret); + else + err = mbuf_printf(mb, "%"PRIu64":%s", (uint64_t)ts, etag); + + if (err) + goto out; + + sha256(mb->buf, mb->end, hash); + mbuf_rewind(mb); + + err = mbuf_printf(mb, "%w%016"PRIx64"", hash, sizeof(hash), + (uint64_t)ts); + if (err) + goto out; + + mbuf_set_pos(mb, 0); + err = mbuf_strdup(mb, &nonce, mbuf_get_left(mb)); + +out: + if (err) + mem_deref(nonce); + else + *pnonce = nonce; + + mem_deref(mb); + + return err; +} + + +/** + * Prints / encodes an HTTP digest request challenge + * + * @param pf Re_printf object + * @param req Request to print + * + * @return 0 if success, otherwise errorcode + */ +int httpauth_digest_chall_req_print(struct re_printf *pf, + const struct httpauth_digest_chall_req *req) +{ + int err = 0; + + if (!req) + return EINVAL; + + /* historical reason quoted strings: */ + /* realm, domain, nonce, opaque, qop */ + /* historical reason unquoted strings: */ + /* stale, algorithm */ + err = re_hprintf(pf, "Digest realm=\"%s\", " + "qop=\"%s\", nonce=\"%s\", algorithm=%s", + req->realm, req->qop, req->nonce, req->algorithm); + + if (str_isset(req->opaque)) + err |= re_hprintf(pf, ", opaque=\"%s\"", req->opaque); + if (str_isset(req->domain)) + err |= re_hprintf(pf, ", domain=\"%s\"", req->domain); + if (req->stale) + err |= re_hprintf(pf, ", stale=true"); + if (str_isset(req->charset)) + err |= re_hprintf(pf, ", charset=\"%s\"", req->charset); + if (req->userhash) + err |= re_hprintf(pf, ", userhash=true"); + + return err; +} + + +/** + * Create a digest authentication request + * + * @param preq Httpauth_digest_chall_req object ptr + * @param realm Realm + * @param etag Changing data for nonce creation + * (HTTP ETag header / SIP msg src address) + * @param qop Quality of protection + * + * @return 0 if success, otherwise errorcode + */ +int httpauth_digest_chall_request(struct httpauth_digest_chall_req **preq, + const char *realm, const char *etag, const char *qop) +{ + return httpauth_digest_chall_request_full(preq, realm, NULL, etag, + NULL, false, NULL, qop, NULL, false); +} + + +/** + * Create a full configurable digest authentication request + * + * @param preq Httpauth_digest_chall_req object ptr + * @param realm Realm + * @param domain Domain (not used in SIP) + * @param etag Changing data for nonce creation + * (HTTP ETag header / SIP msg src address) + * @param opaque Opaque + * @param stale Stale + * @param algo Supported algorithm (MD5, SHA1, SHA256 and sess versions) + * @param qop Quality of protection + * @param charset Character set used (not used in SIP) + * @param userhash Userhash support (not used in SIP) + * + * @return 0 if success, otherwise errorcode + */ +int httpauth_digest_chall_request_full(struct httpauth_digest_chall_req **preq, + const char *realm, const char *domain, const char *etag, + const char *opaque, const bool stale, const char *algo, + const char *qop, const char *charset, const bool userhash) +{ + struct httpauth_digest_chall_req *req = NULL; + int err = 0; + + if (!preq || !realm || !etag || !qop) + return EINVAL; + + req = mem_zalloc(sizeof(*req), httpauth_digest_chall_req_destructor); + if (!req) + return ENOMEM; + + req->stale = stale; + req->userhash = userhash; + err = str_dup(&req->realm, realm); + err |= str_dup(&req->qop, qop); + + if (str_isset(algo)) + err |= str_dup(&req->algorithm, algo); + else + err |= str_dup(&req->algorithm, "MD5"); + + if (str_isset(domain)) + err |= str_dup(&req->domain, domain); + if (str_isset(opaque)) + err |= str_dup(&req->opaque, opaque); + if (str_isset(charset) && str_casecmp(charset, "UTF-8") == 0) + err |= str_dup(&req->charset, charset); + + if (err) + goto out; + + err = generate_nonce(&req->nonce, time(NULL), etag, NULL); + +out: + if (err) + mem_deref(req); + else + *preq = req; + + return err; +} diff --git a/test/httpauth.c b/test/httpauth.c index 18bed195c..4c01a981a 100644 --- a/test/httpauth.c +++ b/test/httpauth.c @@ -86,6 +86,9 @@ int test_httpauth_chall(void) PL("123"), PL("true"), PL("MD5"), + PL_INIT, + PL_INIT, + PL_INIT, PL_INIT}, 0 }, @@ -98,13 +101,15 @@ int test_httpauth_chall(void) PL("9c916919cbc6ad7f54a4f64e5b5115074ee109fa"), PL_INIT, PL_INIT, PL_INIT, PL("auth"), + PL_INIT, PL_INIT, PL_INIT }, 0 }, { "Basic bogus", - {PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT}, + {PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT, PL_INIT, + PL_INIT, PL_INIT, PL_INIT}, EBADMSG }, }; @@ -329,3 +334,173 @@ int test_httpauth_basic_request(void) { return err; } + + +int test_httpauth_digest_request(void) +{ + static const struct { + const char *hval_fmt; + const char *realm; + const char *domain; + const char *etag; + const char *opaque; + const bool stale; + const char *algorithm; + const char *qop; + const char *charset; + const bool userhash; + int err; + } testv [] = { + { + "", + NULL, NULL, "", NULL, false, NULL, "auth", NULL, false, + EINVAL + }, + { + "Digest realm=\"/my/home\", qop=\"\"," + " nonce=\"%s\", algorithm=MD5", + "/my/home", NULL, "localhost:5060", NULL, false, + NULL, "", NULL, false, + 0 + }, + { + "Digest realm=\"/my/home\", qop=\"\"," + " nonce=\"%s\", algorithm=MD5", + "/my/home", NULL, "localhost:5060", NULL, false, + NULL, "", NULL, false, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth\"," + " nonce=\"%s\", algorithm=SHA256", + "/my/home", NULL, "localhost:5060", NULL, false, + "SHA256", "auth", NULL, false, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth\"," + " nonce=\"%s\", algorithm=SHA256-sess, stale=true", + "/my/home", NULL, "localhost:5060", NULL, true, + "SHA256-sess", "auth", NULL, false, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth\"," + " nonce=\"%s\", algorithm=SHA1," + " stale=true, userhash=true", + "/my/home", NULL, "localhost:5060", NULL, true, + "SHA1", "auth", NULL, true, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth\"," + " nonce=\"%s\", algorithm=SHA1-sess," + " domain=\"example.com\", stale=true," + " charset=\"UTF-8\", userhash=true", + "/my/home", "example.com", "localhost:5060", NULL, + true, "SHA1-sess", "auth", "UTF-8", true, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth\"," + " nonce=\"%s\", algorithm=SHA256," + " domain=\"example.com\", stale=true," + " charset=\"UTF-8\", userhash=true", + "/my/home", "example.com", "localhost:5060", NULL, + true, "SHA256", "auth", "UTF-8", true, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth-int\"," + " nonce=\"%s\", algorithm=MD5-sess," + " domain=\"example.com\", stale=true," + " charset=\"UTF-8\", userhash=true", + "/my/home", "example.com", "localhost:5060", NULL, + true, "MD5-sess", "auth-int", "UTF-8", true, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth-int\"," + " nonce=\"%s\", algorithm=SHA1-sess," + " domain=\"example.com\", stale=true," + " charset=\"UTF-8\", userhash=true", + "/my/home", "example.com", "213579023", NULL, + true, "SHA1-sess", "auth-int", "UTF-8", true, 0 + }, + { + "Digest realm=\"/my/home\", qop=\"auth-int\"," + " nonce=\"%s\", algorithm=MD5," + " domain=\"example.com\", stale=true," + " charset=\"UTF-8\", userhash=true", + "/my/home", "example.com", "129842", NULL, + true, NULL, "auth-int", "UTF-8", true, 0 + }, + }; + + int err = 0; + for (unsigned int i = 0; i < RE_ARRAY_SIZE(testv); i++) { + struct httpauth_digest_chall_req *req = NULL; + struct mbuf *mb_refval = NULL; + struct mbuf *mb_printed = NULL; + + mb_refval = mbuf_alloc(512); + mb_printed = mbuf_alloc(512); + if (!mb_refval || !mb_printed) { + err = ENOMEM; + goto for_out; + } + + err = httpauth_digest_chall_request_full(&req, testv[i].realm, + testv[i].domain, testv[i].etag, testv[i].opaque, + testv[i].stale, testv[i].algorithm, testv[i].qop, + testv[i].charset, testv[i].userhash); + if (err == ENOMEM) { + goto for_out; + } + else if (err != testv[i].err) { + DEBUG_WARNING("[%d]" + " Expected return value %m, got %m\n", + i, testv[i].err, err); + } + else if (err) { + goto for_continue; + } + + err = mbuf_printf(mb_refval, testv[i].hval_fmt, req->nonce); + if (err) { + DEBUG_WARNING("[%d]" + " No reference created %m\n", i, err); + goto for_out; + } + + err = mbuf_printf(mb_printed, "%H", + httpauth_digest_chall_req_print, req); + if (err) { + DEBUG_WARNING("[%d]" + " Digest request print error %m\n", i, err); + goto for_out; + } + + if (mb_refval->end != mb_printed->end) { + DEBUG_WARNING("[%d] Expected header len %d, got %d\n", + i, mb_refval->end, mb_printed->end); + err = EINVAL; + goto for_out; + } + + if (memcmp(mb_refval->buf, mb_printed->buf, mb_refval->end)) { + DEBUG_WARNING("[%d] Expected header %b, got %b\n", i, + mb_refval->buf, mb_refval->end, + mb_printed->buf, mb_printed->end); + err = EINVAL; + goto for_out; + } + +for_continue: + mem_deref(req); + mem_deref(mb_refval); + mem_deref(mb_printed); + continue; + +for_out: + mem_deref(req); + mem_deref(mb_refval); + mem_deref(mb_printed); + break; + } + + return err; +} diff --git a/test/test.c b/test/test.c index 55585a12e..a9c23278b 100644 --- a/test/test.c +++ b/test/test.c @@ -119,6 +119,7 @@ static const struct test tests[] = { TEST(test_httpauth_chall), TEST(test_httpauth_resp), TEST(test_httpauth_basic_request), + TEST(test_httpauth_digest_request), TEST(test_ice_cand), TEST(test_ice_loop), TEST(test_jbuf), diff --git a/test/test.h b/test/test.h index d03909ed4..dc614f9b5 100644 --- a/test/test.h +++ b/test/test.h @@ -223,6 +223,7 @@ int test_https_conn_post_handshake(void); int test_httpauth_chall(void); int test_httpauth_resp(void); int test_httpauth_basic_request(void); +int test_httpauth_digest_request(void); int test_ice_loop(void); int test_ice_cand(void); int test_jbuf(void); From 2905f4fd9808d5fe01da8963234c64bd37d93cce Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Wed, 30 Aug 2023 16:48:25 +0200 Subject: [PATCH 3/5] types: fix RE_ARG_SIZE default argument promotions (#924) Variable argument lists are promoted to different types in C (default argument promotions). char, signed char, short -> int unsigned char, unsigned short -> int float -> double bool (char) -> int This PR fixes the sizes and improves size check --- include/re_types.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/re_types.h b/include/re_types.h index 049d0ab03..0f6ee9aab 100644 --- a/include/re_types.h +++ b/include/re_types.h @@ -317,18 +317,18 @@ typedef int re_sock_t; #define RE_ARG_SIZE(type) \ _Generic((type), \ - bool: sizeof(bool), \ - char: sizeof(char), \ - unsigned char: sizeof(unsigned char), \ - short: sizeof(short), \ - unsigned short: sizeof(unsigned short), \ + bool: sizeof(int), \ + char: sizeof(int), \ + unsigned char: sizeof(unsigned int), \ + short: sizeof(int), \ + unsigned short: sizeof(unsigned int), \ int: sizeof(int), \ unsigned int: sizeof(unsigned int), \ long: sizeof(long), \ unsigned long: sizeof(unsigned long), \ long long: sizeof(long long), \ unsigned long long: sizeof(unsigned long long), \ - float: sizeof(float), \ + float: sizeof(double), \ double: sizeof(double), \ char const*: sizeof(char const *), \ char*: sizeof(char *), \ @@ -375,7 +375,7 @@ typedef int re_sock_t; err = EOVERFLOW; \ goto out; \ } \ - if (unlikely(sz > sizeof(type))) { \ + if (unlikely(sz != sizeof(type))) { \ re_assert(0 && "RE_VA_ARG: arg is not compatible"); \ err = EOVERFLOW; \ goto out; \ From 9ac5d494d4faa048ef08c129a009d88a4c3aba1e Mon Sep 17 00:00:00 2001 From: Christian Spielberger Date: Wed, 30 Aug 2023 17:11:11 +0200 Subject: [PATCH 4/5] sip: fix TCP source port (#921) * sip: fix TCP source port The TCP source has at the `struct sip` has to be set before the TCP connection is opened. This commit fixes an issue that was introduced with 5c709fce59bc7cace51769cf45e627193f235021 which postponed the call to the `send_handler()` after the TCP connection is established. * sipreg: remove unneeded check --- include/re_sip.h | 2 ++ src/sip/dialog.c | 32 ++++++++++++++++++++++++++++++++ src/sip/request.c | 7 +++++++ src/sipreg/reg.c | 13 ++++--------- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/include/re_sip.h b/include/re_sip.h index a78f0660b..e374f215c 100644 --- a/include/re_sip.h +++ b/include/re_sip.h @@ -395,6 +395,8 @@ int sip_dialog_update(struct sip_dialog *dlg, const struct sip_msg *msg); bool sip_dialog_rseq_valid(struct sip_dialog *dlg, const struct sip_msg *msg); const char *sip_dialog_callid(const struct sip_dialog *dlg); int sip_dialog_set_callid(struct sip_dialog *dlg, const char *callid); +void sip_dialog_set_srcport(struct sip_dialog *dlg, uint16_t srcport); +uint16_t sip_dialog_srcport(struct sip_dialog *dlg); const char *sip_dialog_uri(const struct sip_dialog *dlg); uint32_t sip_dialog_lseq(const struct sip_dialog *dlg); enum sip_transp sip_dialog_tp(const struct sip_dialog *dlg); diff --git a/src/sip/dialog.c b/src/sip/dialog.c index e605e17c3..4585e22b6 100644 --- a/src/sip/dialog.c +++ b/src/sip/dialog.c @@ -36,6 +36,7 @@ struct sip_dialog { uint32_t rseq; size_t cpos; enum sip_transp tp; + uint32_t srcport; }; @@ -578,6 +579,37 @@ int sip_dialog_set_callid(struct sip_dialog *dlg, const char *callid) } +/** + * Set TCP source port for a SIP Dialog + * + * @param dlg SIP Dialog + * @param srcport The TCP source port to be used + */ +void sip_dialog_set_srcport(struct sip_dialog *dlg, uint16_t srcport) +{ + if (!dlg) + return; + + dlg->srcport = srcport; +} + + +/** + * Get TCP source port for a SIP Dialog + * + * @param dlg SIP Dialog + * + * @return TCP source port + */ +uint16_t sip_dialog_srcport(struct sip_dialog *dlg) +{ + if (!dlg) + return 0; + + return dlg->srcport; +} + + /** * Get the local sequence number from a SIP Dialog * diff --git a/src/sip/request.c b/src/sip/request.c index 6cfe4985b..4c3748d8d 100644 --- a/src/sip/request.c +++ b/src/sip/request.c @@ -43,6 +43,7 @@ struct sip_request { bool canceled; bool provrecv; uint16_t port; + uint16_t srcport; }; @@ -224,6 +225,11 @@ static int request(struct sip_request *req, enum sip_transp tp, if (!req->branch || !mb) goto out; + if (req->srcport) { + struct sip_conncfg cfg = {.srcport = req->srcport}; + err = sip_conncfg_set(req->sip, dst, &cfg); + } + if (!req->stateful) { err = sip_send_conn(req->sip, NULL, tp, dst, mb, connect_handler, req); @@ -940,6 +946,7 @@ int sip_drequestf(struct sip_request **reqp, struct sip *sip, bool stateful, goto out; req->reqp = reqp; + req->srcport = sip_dialog_srcport(dlg); err = sip_request_send(req, sip, sip_dialog_route(dlg)); out: diff --git a/src/sipreg/reg.c b/src/sipreg/reg.c index 09347017c..76e558c75 100644 --- a/src/sipreg/reg.c +++ b/src/sipreg/reg.c @@ -295,6 +295,7 @@ static int send_handler(enum sip_transp tp, struct sa *src, int err; (void)contp; + (void)dst; reg->tp = tp; if (reg->srcport && tp != SIP_TRANSP_UDP) @@ -313,14 +314,6 @@ static int send_handler(enum sip_transp tp, struct sa *src, err |= mbuf_printf(mb, ";reg-id=%d", reg->regid); err |= mbuf_printf(mb, "\r\n"); - - if (reg->srcport) { - struct sip_conncfg cfg; - memset(&cfg, 0, sizeof(cfg)); - cfg.srcport = reg->srcport; - err = sip_conncfg_set(reg->sip, dst, &cfg); - } - return err; } @@ -618,10 +611,12 @@ int sipreg_set_fbregint(struct sipreg *reg, uint32_t fbregint) */ void sipreg_set_srcport(struct sipreg *reg, uint16_t srcport) { - if (!reg) + if (!reg || !reg->dlg) return; reg->srcport = srcport; + + sip_dialog_set_srcport(reg->dlg, srcport); } From 36b80da8873670709c8122ef6c94fe4dffaf3a7b Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Wed, 30 Aug 2023 17:24:37 +0200 Subject: [PATCH 5/5] fmt/print: add 64-bit length modifier %Li, %Ld, %Lx and %Lu (#905) --- src/fmt/print.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/fmt/print.c b/src/fmt/print.c index 6a1ec36d2..7098ad34f 100644 --- a/src/fmt/print.c +++ b/src/fmt/print.c @@ -31,6 +31,7 @@ enum length_modifier { LENMOD_NONE = 0, LENMOD_LONG = 1, LENMOD_LONG_LONG = 2, + LENMOD_INT64 = 3, LENMOD_SIZE = 42, }; @@ -219,6 +220,10 @@ static int vhprintf(const char *fmt, va_list ap, re_vprintf_h *vph, void *arg, case 'i': switch (lenmod) { + case LENMOD_INT64: + RE_VA_ARG(ap, sn, int64_t, safe); + break; + case LENMOD_SIZE: RE_VA_ARG(ap, sn, ssize_t, safe); break; @@ -335,6 +340,10 @@ static int vhprintf(const char *fmt, va_list ap, re_vprintf_h *vph, void *arg, case 'u': switch (lenmod) { + case LENMOD_INT64: + RE_VA_ARG(ap, n, uint64_t, safe); + break; + case LENMOD_SIZE: RE_VA_ARG(ap, n, size_t, safe); break; @@ -400,6 +409,11 @@ static int vhprintf(const char *fmt, va_list ap, re_vprintf_h *vph, void *arg, fm = true; break; + case 'L': + lenmod = LENMOD_INT64; + fm = true; + break; + case 'j': RE_VA_ARG(ap, sa, struct sa *, safe); if (!sa) @@ -500,6 +514,7 @@ static int vhprintf(const char *fmt, va_list ap, re_vprintf_h *vph, void *arg, * %H (re_printf_h *, void *) Print handler with argument * %v (char *fmt, va_list *) Variable argument list * %m (int) Describe an error code + * %L (uint64_t/int64_t) 64-bit length modifier for %i, %d, %x and %u * * * Reserved for the future: