From: Marc Espie Subject: cookiejar support in ftp(1) To: tech@openbsd.org Date: Tue, 28 Oct 2025 19:53:16 +0100 That stuff is remarkably ill-documented I just tried an extension for firefox (https://github.com/rotemdan/ExportCookies) that says it will export to a netscape cookie jar, and of course ftp(1) failed to load it. Turns out modern cookies have sub-second expiry date... which we don't really care about, but would more or less like to support Here's a small patch that does two things: - exit with something more of an indication of what went wrong if the cookie jar didn't load (lineno + automaton state indication) - "support" floating expiry date, by simply cutting the timestamp at the first dot. This (at least) allows ftp to load that cookiejar for me. Index: cookie.c =================================================================== RCS file: /build/data/openbsd/cvs/src/usr.bin/ftp/cookie.c,v diff -u -p -r1.10 cookie.c --- cookie.c 16 Feb 2021 16:27:34 -0000 1.10 +++ cookie.c 28 Oct 2025 17:51:26 -0000 @@ -54,6 +54,12 @@ typedef enum { static struct cookiejar jar; +static void +bad_cookie(const char *automaton, size_t lineno) +{ + errx(1, "invalid cookie file (%s) at line %zu\n", automaton, lineno); +} + void cookie_load(void) { @@ -62,8 +68,10 @@ cookie_load(void) char *line; char *lbuf = NULL; size_t lbufsize = 0; + size_t lineno = 0; char *param; const char *estr; + char *dot; FILE *fp; struct cookie *ck; @@ -76,6 +84,7 @@ cookie_load(void) err(1, "cannot open cookie file %s", cookiefile); date = time(NULL); while (getline(&lbuf, &lbufsize, fp) != -1) { + lineno++; line = lbuf; line[strcspn(line, "\r\n")] = '\0'; @@ -104,7 +113,7 @@ cookie_load(void) if (strcasecmp(param, "TRUE") == 0) { ck->flags |= F_TAILMATCH; } else if (strcasecmp(param, "FALSE") != 0) { - errx(1, "invalid cookie file"); + bad_cookie("TAILMATCH", lineno); } break; case PATH: @@ -119,10 +128,14 @@ cookie_load(void) if (strcasecmp(param, "TRUE") == 0) { ck->flags |= F_SECURE; } else if (strcasecmp(param, "FALSE") != 0) { - errx(1, "invalid cookie file"); + bad_cookie("SECURE", lineno); } break; case EXPIRES: + /* XXX "supports" floating expiry dates */ + dot = strchr(param, '.'); + if (dot) + *dot = 0; /* * rely on sizeof(time_t) being 4 */ @@ -131,8 +144,10 @@ cookie_load(void) if (estr) { if (errno == ERANGE) ck->flags |= F_NOEXPIRY; - else - errx(1, "invalid cookie file"); + else { + perror("strnum"); + bad_cookie("EXPIRE", lineno); + } } break; case NAME: @@ -146,13 +161,13 @@ cookie_load(void) err(1, NULL); break; case DONE: - errx(1, "invalid cookie file"); + bad_cookie("EARLY DONE", lineno); break; } field++; } if (field != DONE) - errx(1, "invalid cookie file"); + bad_cookie("NOT DONE", lineno); if (ck->expires < date && !(ck->flags & F_NOEXPIRY)) { free(ck->val); free(ck->key);