Index | Thread | Search

From:
Marc Espie <marc.espie.openbsd@gmail.com>
Subject:
cookiejar support in ftp(1)
To:
tech@openbsd.org
Date:
Tue, 28 Oct 2025 19:53:16 +0100

Download raw body.

Thread
  • Marc Espie:

    cookiejar support in ftp(1)

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);