pkgsrc-Changes archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

CVS commit: pkgsrc/www/thttpd



Module Name:    pkgsrc
Committed By:   wiz
Date:           Fri Sep 25 14:20:40 UTC 2026

Modified Files:
        pkgsrc/www/thttpd: Makefile PLIST distinfo
Added Files:
        pkgsrc/www/thttpd/files: README.pkgsrc
        pkgsrc/www/thttpd/patches: patch-CVE-2005-3124 patch-CVE-2007-0158
            patch-CVE-2009-4491 patch-CVE-2012-5640 patch-Makefile.in
            patch-cgi-src_Makefile.in patch-configure patch-extras_Makefile.in
            patch-extras_htpasswd.c patch-index.html patch-libhttpd.c
            patch-mmc.c patch-thttpd.c
Removed Files:
        pkgsrc/www/thttpd: MESSAGE
        pkgsrc/www/thttpd/patches: patch-aa patch-ab patch-ac patch-ad patch-af
            patch-ag

Log Message:
thttpd: fix a couple CVEs

Rename patches, add some patches from Debian/FreeBSD,
move MESSAGE to README.pkgsrc.

Bump PKGREVISION.

>From Showta Ishizaki in PR 60761.


To generate a diff of this commit:
cvs rdiff -u -r1.2 -r0 pkgsrc/www/thttpd/MESSAGE
cvs rdiff -u -r1.54 -r1.55 pkgsrc/www/thttpd/Makefile
cvs rdiff -u -r1.6 -r1.7 pkgsrc/www/thttpd/PLIST
cvs rdiff -u -r1.21 -r1.22 pkgsrc/www/thttpd/distinfo
cvs rdiff -u -r0 -r1.1 pkgsrc/www/thttpd/files/README.pkgsrc
cvs rdiff -u -r0 -r1.1 pkgsrc/www/thttpd/patches/patch-CVE-2005-3124 \
    pkgsrc/www/thttpd/patches/patch-CVE-2007-0158 \
    pkgsrc/www/thttpd/patches/patch-CVE-2009-4491 \
    pkgsrc/www/thttpd/patches/patch-CVE-2012-5640 \
    pkgsrc/www/thttpd/patches/patch-Makefile.in \
    pkgsrc/www/thttpd/patches/patch-cgi-src_Makefile.in \
    pkgsrc/www/thttpd/patches/patch-configure \
    pkgsrc/www/thttpd/patches/patch-extras_Makefile.in \
    pkgsrc/www/thttpd/patches/patch-extras_htpasswd.c \
    pkgsrc/www/thttpd/patches/patch-index.html \
    pkgsrc/www/thttpd/patches/patch-libhttpd.c \
    pkgsrc/www/thttpd/patches/patch-mmc.c \
    pkgsrc/www/thttpd/patches/patch-thttpd.c
cvs rdiff -u -r1.6 -r0 pkgsrc/www/thttpd/patches/patch-aa
cvs rdiff -u -r1.4 -r0 pkgsrc/www/thttpd/patches/patch-ab \
    pkgsrc/www/thttpd/patches/patch-ad pkgsrc/www/thttpd/patches/patch-af
cvs rdiff -u -r1.7 -r0 pkgsrc/www/thttpd/patches/patch-ac
cvs rdiff -u -r1.3 -r0 pkgsrc/www/thttpd/patches/patch-ag

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: pkgsrc/www/thttpd/Makefile
diff -u pkgsrc/www/thttpd/Makefile:1.54 pkgsrc/www/thttpd/Makefile:1.55
--- pkgsrc/www/thttpd/Makefile:1.54     Sun Mar 30 10:02:53 2025
+++ pkgsrc/www/thttpd/Makefile  Fri Sep 25 14:20:39 2026
@@ -1,63 +1,92 @@
-# $NetBSD: Makefile,v 1.54 2025/03/30 10:02:53 nia Exp $
+# $NetBSD: Makefile,v 1.55 2026/09/25 14:20:39 wiz Exp $
 
-DISTNAME=      thttpd-2.29
-CATEGORIES=    www
-MASTER_SITES=  https://www.acme.com/software/thttpd/
-DISTFILES=     ${DISTNAME}${EXTRACT_SUFX} \
-               ${NETBSD_LOGO}
-DIST_SUBDIR=   ${PKGNAME_NOREV}
+DISTNAME=              thttpd-2.29
+PKGREVISION=           1
+CATEGORIES=            www
+MASTER_SITES=          https://www.acme.com/software/thttpd/
+DIST_SUBDIR=           ${PKGNAME_NOREV}
+DISTFILES=             ${DISTNAME}${EXTRACT_SUFX} ${NETBSD_LOGO}
+EXTRACT_ONLY=          ${DISTNAME}${EXTRACT_SUFX}
+NETBSD_LOGO=           sitedrivenby.gif
+SITES.${NETBSD_LOGO}=  http://www.NetBSD.org/images/logos/
 
 MAINTAINER=    pkgsrc-users%NetBSD.org@localhost
 HOMEPAGE=      https://www.acme.com/software/thttpd/
 COMMENT=       Tiny/turbo/throttling HTTP server
+LICENSE=       2-clause-bsd
 
-EXTRACT_ONLY=  ${DISTNAME}${EXTRACT_SUFX}
+GNU_CONFIGURE= yes
 
-NETBSD_LOGO=           sitedrivenby.gif
-SITES.${NETBSD_LOGO}=  http://www.NetBSD.org/images/logos/
+# cgi-src/Makefile.in builds strerror.o by running make in the parent
+# directory ("cd .. ; $(MAKE) strerror.o"), and the top Makefile runs
+# "all: this subdirs", so with -j the parent's own build and the
+# subdirectory's can compile that object into the same file at once.
+MAKE_JOBS_SAFE=        no
 
-GNU_CONFIGURE= yes
+.include "../../mk/bsd.prefs.mk"
 
-# Note: this pkg auto-detects IPv6.
-BUILD_DEFS+=           IPV6_READY
-# thttpd syslogs at level LOG_DAEMON by default
-# change this to your preferred syslog level
+BUILD_DEFS+=   IPV6_READY
+
+# thttpd syslogs at level LOG_DAEMON by default; change this to your
+# preferred syslog level.
 THTTPD_LOG_FACILITY?=  LOG_DAEMON
 BUILD_DEFS+=           THTTPD_LOG_FACILITY
 
+# glibc declares sigset(3) only under _XOPEN_SOURCE.  configure finds the
+# symbol anyway (it links), so HAVE_SIGSET is defined and thttpd.c calls it
+# with no prototype in scope.
+CFLAGS.Linux+= -D_GNU_SOURCE
+
+# illumos declares crypt(3c) in <unistd.h> only under __EXTENSIONS__, and
+# thttpd includes no <crypt.h>.
+CFLAGS.SunOS+= -D__EXTENSIONS__
+
+CFLAGS.SCO_SV+=        -DMAXPATHLEN=255 -DS_IFSOCK=0
+
 EGDIR=         ${PREFIX}/share/examples
+DOCDIR=                ${PREFIX}/share/doc/thttpd
+
 CONF_FILES=    ${EGDIR}/thttpd.conf ${PKG_SYSCONFDIR}/thttpd.conf
 RCD_SCRIPTS=   thttpd
 
-INSTALLATION_DIRS=     ${PKGMANDIR}/man1 share/thttpd ${EGDIR}
+INSTALLATION_DIRS=     ${PKGMANDIR}/man1 share/thttpd ${EGDIR} ${DOCDIR}
 
 SUBST_CLASSES+=                paths
-SUBST_FILES.paths=     thttpd.conf
-SUBST_VARS.paths=      PREFIX
 SUBST_STAGE.paths=     pre-configure
+SUBST_FILES.paths=     thttpd.conf README.pkgsrc
+SUBST_VARS.paths=      PREFIX
 
-MAKE_JOBS_SAFE=        no
-
-CFLAGS.SCO_SV+=        -DMAXPATHLEN=255 -DS_IFSOCK=0
+# htpasswd(1) is a common name, so the package installs it as thtpasswd.
+# post-patch does the renaming: after the patches, which still name
+# extras/htpasswd.c, and before this substitution runs.
+SUBST_CLASSES+=                        thtpasswd
+SUBST_STAGE.thtpasswd=         pre-configure
+SUBST_MESSAGE.thtpasswd=       Renaming htpasswd to thtpasswd.
+SUBST_FILES.thtpasswd=         thttpd.8 extras/Makefile.in extras/thtpasswd.c
+SUBST_SED.thtpasswd=           -e 's,\.htpasswd,@DOTHTPASSWD@,g'
+SUBST_SED.thtpasswd+=          -e 's,htpasswd,thtpasswd,g'
+SUBST_SED.thtpasswd+=          -e 's,@DOTHTPASSWD@,.htpasswd,g'
+
+SUBST_CLASSES+=                logfac
+SUBST_STAGE.logfac=    pre-configure
+SUBST_MESSAGE.logfac=  Setting the syslog facility.
+SUBST_FILES.logfac=    config.h
+SUBST_SED.logfac=      -e 's,LOG_DAEMON,${THTTPD_LOG_FACILITY},'
 
 post-extract:
        ${CP} ${FILESDIR}/thttpd.conf ${WRKSRC}
+       ${CP} ${FILESDIR}/README.pkgsrc ${WRKSRC}
 
-pre-configure:
+post-patch:
        ${MV} ${WRKSRC}/extras/htpasswd.1 ${WRKSRC}/extras/thtpasswd.1
        ${MV} ${WRKSRC}/extras/htpasswd.c ${WRKSRC}/extras/thtpasswd.c
-       for FILE in thttpd.8 extras/Makefile.in extras/thtpasswd.c; do  \
-               ${MV} -f ${WRKSRC}/$${FILE} ${WRKSRC}/$${FILE}.bak;     \
-               ${SED}  -e "s,\.htpasswd,XXX,g;s,htpasswd,thtpasswd,g;s,XXX,.htpasswd,g" \
-                       ${WRKSRC}/$${FILE}.bak > ${WRKSRC}/$${FILE};    \
-       done
-       ${MV} -f ${WRKSRC}/config.h ${WRKSRC}/config.h.bak
-       ${SED} -e "s/LOG_DAEMON/${THTTPD_LOG_FACILITY}/" ${WRKSRC}/config.h.bak \
-                       > ${WRKSRC}/config.h
 
 post-install:
-       ${INSTALL_DATA} ${WRKSRC}/thttpd.conf ${DESTDIR}${PREFIX}/share/examples
-       ${INSTALL_DATA} ${WRKSRC}/index.html ${DESTDIR}${PREFIX}/share/thttpd
-       ${INSTALL_DATA} ${DISTDIR}/${DIST_SUBDIR}/sitedrivenby.gif ${DESTDIR}${PREFIX}/share/thttpd
+       ${INSTALL_DATA} ${WRKSRC}/README.pkgsrc ${DESTDIR}${DOCDIR}
+       ${INSTALL_DATA} ${WRKSRC}/thttpd.conf ${DESTDIR}${EGDIR}
+       ${INSTALL_DATA} ${WRKSRC}/index.html \
+               ${DESTDIR}${PREFIX}/share/thttpd
+       ${INSTALL_DATA} ${DISTDIR}/${DIST_SUBDIR}/${NETBSD_LOGO} \
+               ${DESTDIR}${PREFIX}/share/thttpd
 
 .include "../../mk/bsd.pkg.mk"

Index: pkgsrc/www/thttpd/PLIST
diff -u pkgsrc/www/thttpd/PLIST:1.6 pkgsrc/www/thttpd/PLIST:1.7
--- pkgsrc/www/thttpd/PLIST:1.6 Tue Mar 11 14:05:18 2014
+++ pkgsrc/www/thttpd/PLIST     Fri Sep 25 14:20:39 2026
@@ -1,4 +1,4 @@
-@comment $NetBSD: PLIST,v 1.6 2014/03/11 14:05:18 jperkin Exp $
+@comment $NetBSD: PLIST,v 1.7 2026/09/25 14:20:39 wiz Exp $
 libexec/cgi-bin/phf
 libexec/cgi-bin/redirect
 libexec/cgi-bin/ssi
@@ -12,6 +12,7 @@ sbin/makeweb
 sbin/syslogtocern
 sbin/thtpasswd
 sbin/thttpd
+share/doc/thttpd/README.pkgsrc
 share/examples/thttpd.conf
 share/thttpd/index.html
 share/thttpd/sitedrivenby.gif

Index: pkgsrc/www/thttpd/distinfo
diff -u pkgsrc/www/thttpd/distinfo:1.21 pkgsrc/www/thttpd/distinfo:1.22
--- pkgsrc/www/thttpd/distinfo:1.21     Tue Oct 26 11:31:10 2021
+++ pkgsrc/www/thttpd/distinfo  Fri Sep 25 14:20:39 2026
@@ -1,4 +1,4 @@
-$NetBSD: distinfo,v 1.21 2021/10/26 11:31:10 nia Exp $
+$NetBSD: distinfo,v 1.22 2026/09/25 14:20:39 wiz Exp $
 
 BLAKE2s (thttpd-2.29/sitedrivenby.gif) = 7eef9313c8279aadd94c91a4bbe0a682cff0b5647f76acf30cd414ea72b0df60
 SHA512 (thttpd-2.29/sitedrivenby.gif) = fc80ec0f63a9a28cd8002ba4c1f922807faf8661c677f7935f8768b2739facf411e6a1e72c65038d72c68c1f7bf64d2bc583c1880dc85f77eca48f7baec38ae2
@@ -6,9 +6,16 @@ Size (thttpd-2.29/sitedrivenby.gif) = 85
 BLAKE2s (thttpd-2.29/thttpd-2.29.tar.gz) = 023ee26357ec155995627c937e282ce5077dd1413f1f69f2f5927c883b2dca98
 SHA512 (thttpd-2.29/thttpd-2.29.tar.gz) = e02ed5b889eb3c875d56503093777c542316165a8df2d83e539337fb0759b6d9728fd484123dd903af57b06c97fe339055b816cca64778b7369b94020bd61ab6
 Size (thttpd-2.29/thttpd-2.29.tar.gz) = 133967 bytes
-SHA1 (patch-aa) = 0f739bebf1ade45a9b9819fc92d48eeb1004eb66
-SHA1 (patch-ab) = c6d6a20a00b4ceaf409b849982b8e963debf9530
-SHA1 (patch-ac) = f7fe1fed88b4cb33c9456d6fdc13e2bbc175ced6
-SHA1 (patch-ad) = 234127aaf4a3b5e6536bd08cc80f823800240fac
-SHA1 (patch-af) = 5160b635cdf9b3c997e93e039ef2764f77857018
-SHA1 (patch-ag) = 01410f8d293e2d4033f2a1119b21c05e21dfefbe
+SHA1 (patch-CVE-2005-3124) = af34d681b00af143ea86b696ae52b8b19fb619f7
+SHA1 (patch-CVE-2007-0158) = 49428634a39e4bda2f1d0a0a15cc62450c1259d1
+SHA1 (patch-CVE-2009-4491) = 37fa7858d14c7afff9d22ed1aa423b10cfe80516
+SHA1 (patch-CVE-2012-5640) = dda5e5a3e3b40e9d2997952f729e44d2d0ef58d1
+SHA1 (patch-Makefile.in) = 4c5b92df9e28d14ff83d00a4d951efa4595532ac
+SHA1 (patch-cgi-src_Makefile.in) = b80257756fcbb769b6540440b476f7548a5f117d
+SHA1 (patch-configure) = c3a09ec45abbfed2dced7e861333cf4376af2fbb
+SHA1 (patch-extras_Makefile.in) = c0c0df11f5ca945ef588d47a17906ad2d1c6c86c
+SHA1 (patch-extras_htpasswd.c) = 5cd23587d0a6d483462866c96fce5f2d31754556
+SHA1 (patch-index.html) = 2a7b22d91f8ef8cad7eecabdd8cb76fb44076b3e
+SHA1 (patch-libhttpd.c) = 89660ecceb2c4e3242c38a78f734db854d028f31
+SHA1 (patch-mmc.c) = 4052750ad1318f1d6776087f743a15ff609aed8c
+SHA1 (patch-thttpd.c) = 77e8de4f058ef27dc9463614e0c14295f0460fcb

Added files:

Index: pkgsrc/www/thttpd/files/README.pkgsrc
diff -u /dev/null pkgsrc/www/thttpd/files/README.pkgsrc:1.1
--- /dev/null   Fri Sep 25 14:20:40 2026
+++ pkgsrc/www/thttpd/files/README.pkgsrc       Fri Sep 25 14:20:39 2026
@@ -0,0 +1,16 @@
+makeweb and the web group
+=========================
+
+makeweb lets a user create a public_html directory under the web root.
+To do that it has to be set-group-id to the group that owns the web
+root; pkgsrc installs it without that bit, because making a program
+set-group-id is the administrator's decision, not the package's.
+
+If you want to use it:
+
+       chgrp <group> @PREFIX@/sbin/makeweb
+       chmod 2755 @PREFIX@/sbin/makeweb
+
+The group thttpd's own Makefile suggests is "www".
+
+If you do not use makeweb, leave it as it is.

Index: pkgsrc/www/thttpd/patches/patch-CVE-2005-3124
diff -u /dev/null pkgsrc/www/thttpd/patches/patch-CVE-2005-3124:1.1
--- /dev/null   Fri Sep 25 14:20:40 2026
+++ pkgsrc/www/thttpd/patches/patch-CVE-2005-3124       Fri Sep 25 14:20:39 2026
@@ -0,0 +1,34 @@
+$NetBSD: patch-CVE-2005-3124,v 1.1 2026/09/25 14:20:39 wiz Exp $
+
+CVE-2005-3124 -- the script wrote to /tmp/stc1.$$, a name that can be
+worked out in advance, so anyone with write access to /tmp could put a
+symlink there and have the script write through it.  Use mktemp(1) and
+remove the file on the way out.  NVD says this was fixed before 2.23,
+but 2.29 still has the /tmp name, which is why the patch is still here.
+
+The line as written carried a stray second backtick, so the script it
+produced did not parse -- "sh -n" reports an unmatched backtick, and
+running it gives
+
+  syslogtocern: 34: Syntax error: "||" unexpected
+
+before it does anything.  That has been so since the patch was added in
+2005, so the installed syslogtocern has never run.  Nobody was exposed
+to the symlink attack by it, because a script that does not parse does
+not write anywhere; the tool was simply dead.  With the backtick removed
+it parses, keeps the mktemp(1) protection, and converts a syslog file
+into CERN-format access_log and error_log as the manual page says.
+
+--- extras/syslogtocern.orig   2005-05-20 19:10:25.000000000 +0000
++++ extras/syslogtocern
+@@ -31,8 +31,8 @@
+     exit 1
+ fi
+ 
+-tmp1=/tmp/stc1.$$
+-rm -f $tmp1
++tmp1=`mktemp -t stc1.XXXXXX` || { echo "$0: Cannot create temporary file" >&2; exit 1;  }
++trap " [ -f \"$tmp1\" ] && /bin/rm -f -- \"$tmp1\"" 0 1 2 3 13 15
+ 
+ # Gather up all the thttpd entries.
+ egrep -h ' thttpd\[' "$@" > $tmp1
Index: pkgsrc/www/thttpd/patches/patch-CVE-2007-0158
diff -u /dev/null pkgsrc/www/thttpd/patches/patch-CVE-2007-0158:1.1
--- /dev/null   Fri Sep 25 14:20:40 2026
+++ pkgsrc/www/thttpd/patches/patch-CVE-2007-0158       Fri Sep 25 14:20:39 2026
@@ -0,0 +1,112 @@
+$NetBSD: patch-CVE-2007-0158,v 1.1 2026/09/25 14:20:39 wiz Exp $
+
+CVE-2007-0158 -- buffer underflow.  Five places index with length-1
+without checking the length.  Three are in expand_symlinks():
+
+  - with no_symlink_check (chroot mode) and a path of only slashes, the
+    trailing-slash trim drives checkedlen to 0 and then reads
+    checked[-1]; stat("/") always succeeds, so "/" reaches it;
+  - with an empty path, rest[restlen-1] reads before the buffer;
+  - with a zero-length symlink target in the served tree, readlink()
+    returns 0 and lnk[linklen-1] reads before the stack buffer.
+
+The fourth is in auth_check2(): fgets() returns non-NULL for a line that
+begins with a NUL byte, strlen() is then 0, and line[l-1] reads before
+the 500-byte stack buffer.  The .htpasswd is user-written, so this is
+reached the same way CVE-2012-5640 is, by requesting a protected
+directory.
+
+The fifth is in thttpd.c: max_connects is fdwatch_get_nfiles() minus
+SPARE_FDS, with no lower bound, so a file descriptor limit of 10 leaves
+it at 0.  connects[max_connects-1].next_free_connect is then written
+before the array; malloc(0) returns a pointer, so the allocation does
+not fail first.  With that limit thttpd starts and then refuses every
+connection (measured: HTTP 200 normally, no answer at all under
+"ulimit -n 10"), which is what max_connects <= 0 looks like from
+outside.  It now exits with a message instead.
+
+The four in libhttpd.c fire under AddressSanitizer on the routines
+extracted unchanged from 2.29, and are clean once guarded.
+
+What other packaging carries, for comparison:
+
+  site                    checkedlen  restlen  readlink  auth_check2
+  FreeBSD ports               yes       yes       no         no
+  Debian (2.25b-11)            no       yes*      no         no
+  ACME 2.30 (unreleased)        -         -      yes          -
+
+  * Debian drops the trailing-slash trim entirely rather than guarding it.
+
+(FreeBSD and Debian also change the PATH_INFO trimming near
+origfilename[i-1].  That one is not a length-1 underflow -- the stock
+"i > 0" test already prevents it -- it is a behaviour change, and it is
+kept separate in patch-libhttpd.c.)
+
+ACME's 2.30 changelog lists "off-by-one illegal memory access in
+expand_symlinks()", which is the readlink() case.
+
+--- libhttpd.c.orig
++++ libhttpd.c
+@@ -1117,9 +1117,11 @@
+     /* Read it. */
+     while ( fgets( line, sizeof(line), fp ) != (char*) 0 )
+       {
+-      /* Nuke newline. */
++      /* Nuke newline.  A NUL byte in the file leaves strlen() at 0, and
++      ** line[l-1] would then read before the buffer.
++      */
+       l = strlen( line );
+-      if ( line[l - 1] == '\n' )
++      if ( l > 0 && line[l - 1] == '\n' )
+           line[l - 1] = '\0';
+       /* Split into user and encrypted password. */
+       cryp = strchr( line, ':' );
+@@ -1486,7 +1488,7 @@
+           httpd_realloc_str( &checked, &maxchecked, checkedlen );
+           (void) strcpy( checked, path );
+           /* Trim trailing slashes. */
+-          while ( checked[checkedlen - 1] == '/' )
++          while ( checkedlen > 0 && checked[checkedlen - 1] == '/' )
+               {
+               checked[checkedlen - 1] = '\0';
+               --checkedlen;
+@@ -1505,7 +1507,7 @@
+     restlen = strlen( path );
+     httpd_realloc_str( &rest, &maxrest, restlen );
+     (void) strcpy( rest, path );
+-    if ( rest[restlen - 1] == '/' )
++    if ( restlen > 0 && rest[restlen - 1] == '/' )
+       rest[--restlen] = '\0';         /* trim trailing slash */
+     if ( ! tildemapped )
+       /* Remove any leading slashes. */
+@@ -1623,7 +1625,9 @@
+           return (char*) 0;
+           }
+       lnk[linklen] = '\0';
+-      if ( lnk[linklen - 1] == '/' )
++      /* An empty symlink target makes readlink() return 0, and
++      ** lnk[linklen-1] then reads lnk[-1], underflowing the buffer. */
++      if ( linklen > 0 && lnk[linklen - 1] == '/' )
+           lnk[--linklen] = '\0';     /* trim trailing slash */
+ 
+       /* Insert the link contents in front of the rest of the filename. */
+--- thttpd.c.orig
++++ thttpd.c
+@@ -554,6 +554,17 @@
+       exit( 1 );
+       }
+     max_connects -= SPARE_FDS;
++    if ( max_connects <= 0 )
++      {
++      /* With a file descriptor limit this low there is no room for even one
++      ** connection, and connects[max_connects-1] below would write before
++      ** the array.  malloc(0) returns a pointer, so this is not caught by
++      ** the allocation failing.
++      */
++      syslog( LOG_CRIT, "file descriptor limit is too low for any connection" );
++      (void) fprintf( stderr, "%s: file descriptor limit is too low for any connection\n", argv0 );
++      exit( 1 );
++      }
+ 
+     /* Chroot if requested. */
+     if ( do_chroot )
Index: pkgsrc/www/thttpd/patches/patch-CVE-2009-4491
diff -u /dev/null pkgsrc/www/thttpd/patches/patch-CVE-2009-4491:1.1
--- /dev/null   Fri Sep 25 14:20:40 2026
+++ pkgsrc/www/thttpd/patches/patch-CVE-2009-4491       Fri Sep 25 14:20:39 2026
@@ -0,0 +1,332 @@
+$NetBSD: patch-CVE-2009-4491,v 1.1 2026/09/25 14:20:39 wiz Exp $
+
+CVE-2009-4491 -- log injection.  thttpd writes the request line, the
+headers and values derived from them to the log exactly as the client
+sent them, so a request can place terminal escape sequences into the
+log.  Control characters are written as \xHH instead.
+
+This is not only the access log.  Every one of these logs a value the
+client controls, and none of them sanitised it:
+
+  make_log_entry()  the access log and its syslog form: URL, Referer,
+                    User-Agent, remote user
+  the "unparsable time" pair  the raw If-Modified-Since and If-Range
+                    header values
+  check_referrer()  the Referer host, the URL and the Referer
+  thirteen more     the URL or the expanded filename, in the "goes
+                    outside the web tree", "tried to index a
+                    directory", "tried to retrieve an auth file",
+                    "isn't CGI", opendir, execve and spawn messages
+  thttpd.c          the URL in the write-error message
+
+httpd_log_escape() is exported because thttpd.c needs it too.  It hands
+back a pointer into a small ring of static buffers so that several
+values can be escaped in one syslog() call; thttpd is single-threaded,
+so that is safe.
+
+Not in ACME's 2.30 changelog, and not carried by FreeBSD ports, Debian,
+Fedora, Alpine, MacPorts or Void.
+
+--- libhttpd.c.orig
++++ libhttpd.c
+@@ -172,6 +172,7 @@
+ static int cgi( httpd_conn* hc );
+ static int really_start_request( httpd_conn* hc, struct timeval* nowP );
+ static void make_log_entry( httpd_conn* hc, struct timeval* nowP );
++static char* log_escape( char* dst, size_t dstsize, const char* src );
+ static int check_referrer( httpd_conn* hc );
+ static int really_check_referrer( httpd_conn* hc );
+ static int sockaddr_check( httpd_sockaddr* saP );
+@@ -1619,7 +1620,7 @@
+       ++nlinks;
+       if ( nlinks > MAX_LINKS )
+           {
+-          syslog( LOG_ERR, "too many symlinks in %.80s", path );
++          syslog( LOG_ERR, "too many symlinks in %.80s", httpd_log_escape( path ) );
+           return (char*) 0;
+           }
+       lnk[linklen] = '\0';
+@@ -2173,7 +2174,8 @@
+               cp = &buf[18];
+               hc->if_modified_since = tdate_parse( cp );
+               if ( hc->if_modified_since == (time_t) -1 )
+-                  syslog( LOG_DEBUG, "unparsable time: %.80s", cp );
++                  syslog( LOG_DEBUG, "unparsable time: %.80s",
++                      httpd_log_escape( cp ) );
+               }
+           else if ( strncasecmp( buf, "Cookie:", 7 ) == 0 )
+               {
+@@ -2214,7 +2216,8 @@
+               cp = &buf[9];
+               hc->range_if = tdate_parse( cp );
+               if ( hc->range_if == (time_t) -1 )
+-                  syslog( LOG_DEBUG, "unparsable time: %.80s", cp );
++                  syslog( LOG_DEBUG, "unparsable time: %.80s",
++                      httpd_log_escape( cp ) );
+               }
+           else if ( strncasecmp( buf, "Content-Type:", 13 ) == 0 )
+               {
+@@ -2380,7 +2383,7 @@
+           {
+           syslog(
+               LOG_NOTICE, "%.80s URL \"%.80s\" goes outside the web tree",
+-              httpd_ntoa( &hc->client_addr ), hc->encodedurl );
++              httpd_ntoa( &hc->client_addr ), httpd_log_escape( hc->encodedurl ) );
+           httpd_send_err(
+               hc, 403, err403title, "",
+               ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a file outside the permitted web server directory tree.\n" ),
+@@ -2734,7 +2737,7 @@
+     dirp = opendir( hc->expnfilename );
+     if ( dirp == (DIR*) 0 )
+       {
+-      syslog( LOG_ERR, "opendir %.80s - %m", hc->expnfilename );
++      syslog( LOG_ERR, "opendir %.80s - %m", httpd_log_escape( hc->expnfilename ) );
+       httpd_send_err( hc, 404, err404title, "", err404form, hc->encodedurl );
+       return -1;
+       }
+@@ -2974,7 +2977,8 @@
+ 
+       /* Parent process. */
+       closedir( dirp );
+-      syslog( LOG_DEBUG, "spawned indexing process %d for directory '%.200s'", r, hc->expnfilename );
++      syslog( LOG_DEBUG, "spawned indexing process %d for directory '%.200s'",
++          r, httpd_log_escape( hc->expnfilename ) );
+ #ifdef CGI_TIMELIMIT
+       /* Schedule a kill for the child process, in case it runs too long */
+       client_data.i = r;
+@@ -3563,7 +3567,7 @@
+     (void) execve( binary, argp, envp );
+ 
+     /* Something went wrong. */
+-    syslog( LOG_ERR, "execve %.80s - %m", hc->expnfilename );
++    syslog( LOG_ERR, "execve %.80s - %m", httpd_log_escape( hc->expnfilename ) );
+     httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
+     httpd_write_response( hc );
+     _exit( 1 );
+@@ -3602,7 +3606,8 @@
+       }
+ 
+     /* Parent process. */
+-    syslog( LOG_DEBUG, "spawned CGI process %d for file '%.200s'", r, hc->expnfilename );
++    syslog( LOG_DEBUG, "spawned CGI process %d for file '%.200s'",
++          r, httpd_log_escape( hc->expnfilename ) );
+ #ifdef CGI_TIMELIMIT
+     /* Schedule a kill for the child process, in case it runs too long */
+     client_data.i = r;
+@@ -3654,7 +3659,7 @@
+       syslog(
+           LOG_INFO,
+           "%.80s URL \"%.80s\" resolves to a non world-readable file",
+-          httpd_ntoa( &hc->client_addr ), hc->encodedurl );
++          httpd_ntoa( &hc->client_addr ), httpd_log_escape( hc->encodedurl ) );
+       httpd_send_err(
+           hc, 403, err403title, "",
+           ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a file that is not world-readable.\n" ),
+@@ -3709,7 +3714,7 @@
+           syslog(
+               LOG_INFO,
+               "%.80s URL \"%.80s\" tried to index a directory with indexing disabled",
+-              httpd_ntoa( &hc->client_addr ), hc->encodedurl );
++              httpd_ntoa( &hc->client_addr ), httpd_log_escape( hc->encodedurl ) );
+           httpd_send_err(
+               hc, 403, err403title, "",
+               ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a directory that has indexing disabled.\n" ),
+@@ -3729,7 +3734,7 @@
+ #else /* GENERATE_INDEXES */
+       syslog(
+           LOG_INFO, "%.80s URL \"%.80s\" tried to index a directory",
+-          httpd_ntoa( &hc->client_addr ), hc->encodedurl );
++          httpd_ntoa( &hc->client_addr ), httpd_log_escape( hc->encodedurl ) );
+       httpd_send_err(
+           hc, 403, err403title, "",
+           ERROR_FORM( err403form, "The requested URL '%.80s' is a directory, and directory indexing is disabled on this server.\n" ),
+@@ -3757,7 +3762,7 @@
+           syslog(
+               LOG_INFO,
+               "%.80s URL \"%.80s\" resolves to a non-world-readable index file",
+-              httpd_ntoa( &hc->client_addr ), hc->encodedurl );
++              httpd_ntoa( &hc->client_addr ), httpd_log_escape( hc->encodedurl ) );
+           httpd_send_err(
+               hc, 403, err403title, "",
+               ERROR_FORM( err403form, "The requested URL '%.80s' resolves to an index file that is not world-readable.\n" ),
+@@ -3786,7 +3791,7 @@
+           syslog(
+               LOG_NOTICE,
+               "%.80s URL \"%.80s\" tried to retrieve an auth file",
+-              httpd_ntoa( &hc->client_addr ), hc->encodedurl );
++              httpd_ntoa( &hc->client_addr ), httpd_log_escape( hc->encodedurl ) );
+           httpd_send_err(
+               hc, 403, err403title, "",
+               ERROR_FORM( err403form, "The requested URL '%.80s' is an authorization file, retrieving it is not permitted.\n" ),
+@@ -3801,7 +3806,7 @@
+       syslog(
+           LOG_NOTICE,
+           "%.80s URL \"%.80s\" tried to retrieve an auth file",
+-          httpd_ntoa( &hc->client_addr ), hc->encodedurl );
++          httpd_ntoa( &hc->client_addr ), httpd_log_escape( hc->encodedurl ) );
+       httpd_send_err(
+           hc, 403, err403title, "",
+           ERROR_FORM( err403form, "The requested URL '%.80s' is an authorization file, retrieving it is not permitted.\n" ),
+@@ -3828,7 +3833,7 @@
+       {
+       syslog(
+           LOG_NOTICE, "%.80s URL \"%.80s\" is executable but isn't CGI",
+-          httpd_ntoa( &hc->client_addr ), hc->encodedurl );
++          httpd_ntoa( &hc->client_addr ), httpd_log_escape( hc->encodedurl ) );
+       httpd_send_err(
+           hc, 403, err403title, "",
+           ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a file which is marked executable but is not a CGI file; retrieving it is forbidden.\n" ),
+@@ -3839,7 +3844,7 @@
+       {
+       syslog(
+           LOG_INFO, "%.80s URL \"%.80s\" has pathinfo but isn't CGI",
+-          httpd_ntoa( &hc->client_addr ), hc->encodedurl );
++          httpd_ntoa( &hc->client_addr ), httpd_log_escape( hc->encodedurl ) );
+       httpd_send_err(
+           hc, 403, err403title, "",
+           ERROR_FORM( err403form, "The requested URL '%.80s' resolves to a file plus CGI-style pathinfo, but the file is not a valid CGI file.\n" ),
+@@ -3904,12 +3909,65 @@
+     }
+ 
+ 
++/* Copy src into dst, replacing control characters with \xHH.  The request
++** line, the headers and anything derived from them reach the log exactly as
++** the client sent them, so without this a request can write terminal escape
++** sequences into the log.
++*/
++static char*
++log_escape( char* dst, size_t dstsize, const char* src )
++    {
++    static const char hex[] = "0123456789abcdef";
++    size_t i = 0;
++    unsigned char c;
++
++    if ( src == (const char*) 0 )
++      src = "";
++    for ( ; *src != '\0' && i + 4 < dstsize; ++src )
++      {
++      c = (unsigned char) *src;
++      if ( c < 0x20 || c == 0x7f )
++          {
++          dst[i++] = '\\';
++          dst[i++] = 'x';
++          dst[i++] = hex[c >> 4];
++          dst[i++] = hex[c & 0xf];
++          }
++      else
++          dst[i++] = c;
++      }
++    dst[i] = '\0';
++    return dst;
++    }
++
++
++/* The same, for use straight in a syslog() argument list.  Returns a pointer
++** into a small ring of buffers so that more than one value can be escaped in
++** one call.  thttpd is single-threaded, so this is safe.
++*/
++char*
++httpd_log_escape( const char* src )
++    {
++    static char bufs[4][1000];
++    static int next = 0;
++    char* dst;
++
++    dst = bufs[next];
++    next = ( next + 1 ) % 4;
++    return log_escape( dst, sizeof(bufs[0]), src );
++    }
++
++
+ static void
+ make_log_entry( httpd_conn* hc, struct timeval* nowP )
+     {
+     char* ru;
+     char url[305];
+     char bytes[40];
++    char eurl[305 * 4];
++    char eref[200 * 4 + 1];
++    char eua[200 * 4 + 1];
++    char eru[80 * 4 + 1];
+ 
+     if ( hc->hs->no_log )
+       return;
+@@ -3922,7 +3980,7 @@
+ 
+     /* Format remote user. */
+     if ( hc->remoteuser[0] != '\0' )
+-      ru = hc->remoteuser;
++      ru = log_escape( eru, sizeof(eru), hc->remoteuser );
+     else
+       ru = "-";
+     /* If we're vhosting, prepend the hostname to the url.  This is
+@@ -3937,6 +3995,9 @@
+     else
+       (void) my_snprintf( url, sizeof(url),
+           "%.200s", hc->encodedurl );
++    (void) log_escape( eurl, sizeof(eurl), url );
++    (void) log_escape( eref, sizeof(eref), hc->referrer );
++    (void) log_escape( eua, sizeof(eua), hc->useragent );
+     /* Format the bytes. */
+     if ( hc->bytes_sent >= 0 )
+       (void) my_snprintf(
+@@ -3985,8 +4046,8 @@
+       (void) fprintf( hc->hs->logfp,
+           "%.80s - %.80s [%s] \"%.80s %.300s %.80s\" %d %s \"%.200s\" \"%.200s\"\n",
+           httpd_ntoa( &hc->client_addr ), ru, date,
+-          httpd_method_str( hc->method ), url, hc->protocol,
+-          hc->status, bytes, hc->referrer, hc->useragent );
++          httpd_method_str( hc->method ), eurl, hc->protocol,
++          hc->status, bytes, eref, eua );
+ #ifdef FLUSH_LOG_EVERY_TIME
+       (void) fflush( hc->hs->logfp );
+ #endif
+@@ -3995,8 +4056,8 @@
+       syslog( LOG_INFO,
+           "%.80s - %.80s \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.200s\"",
+           httpd_ntoa( &hc->client_addr ), ru,
+-          httpd_method_str( hc->method ), url, hc->protocol,
+-          hc->status, bytes, hc->referrer, hc->useragent );
++          httpd_method_str( hc->method ), eurl, hc->protocol,
++          hc->status, bytes, eref, eua );
+     }
+ 
+ 
+@@ -4023,7 +4084,9 @@
+           cp = "";
+       syslog(
+           LOG_INFO, "%.80s non-local referrer \"%.80s%.80s\" \"%.80s\"",
+-          httpd_ntoa( &hc->client_addr ), cp, hc->encodedurl, hc->referrer );
++          httpd_ntoa( &hc->client_addr ), httpd_log_escape( cp ),
++          httpd_log_escape( hc->encodedurl ),
++          httpd_log_escape( hc->referrer ) );
+       httpd_send_err(
+           hc, 403, err403title, "",
+           ERROR_FORM( err403form, "You must supply a local referrer to get URL '%.80s' from this server.\n" ),
+--- libhttpd.h.orig
++++ libhttpd.h
+@@ -263,6 +263,12 @@
+ extern char* httpd_err408form;
+ extern char* httpd_err503title;
+ extern char* httpd_err503form;
++
++/* Escape control characters in a string for the log, so that a request
++** cannot write terminal escape sequences into it.  Returns a pointer into
++** a small ring of static buffers.
++*/
++char* httpd_log_escape( const char* src );
+ 
+ /* Generate a string representation of a method number. */
+ char* httpd_method_str( int method );
+--- thttpd.c.orig
++++ thttpd.c
+@@ -1778,7 +1778,8 @@
+       ** And ECONNRESET isn't interesting either.
+       */
+       if ( errno != EPIPE && errno != EINVAL && errno != ECONNRESET )
+-          syslog( LOG_ERR, "write - %m sending %.80s", hc->encodedurl );
++          syslog( LOG_ERR, "write - %m sending %.80s",
++              httpd_log_escape( hc->encodedurl ) );
+       clear_connection( c, tvP );
+       return;
+       }
Index: pkgsrc/www/thttpd/patches/patch-CVE-2012-5640
diff -u /dev/null pkgsrc/www/thttpd/patches/patch-CVE-2012-5640:1.1
--- /dev/null   Fri Sep 25 14:20:40 2026
+++ pkgsrc/www/thttpd/patches/patch-CVE-2012-5640       Fri Sep 25 14:20:39 2026
@@ -0,0 +1,82 @@
+$NetBSD: patch-CVE-2012-5640,v 1.1 2026/09/25 14:20:39 wiz Exp $
+
+CVE-2012-5640 -- denial of service.  crypt() returns NULL for a salt it
+does not recognise (glibc and illumos do; NetBSD and current Linux
+return "*0" instead).  thttpd passes the result straight on in all three
+places it calls crypt(), and the salt comes from a user-written
+.htpasswd, so one malformed line is enough.
+
+In auth_check2() the result is handed to strcmp(), so a request for the
+protected directory crashes the server.  Note that the fix must keep
+going to the send_authenticate() call below rather than returning
+straight away: this function documents -1 as "unauthorized" and every
+existing -1 is preceded by send_authenticate(), and the callers take -1
+to mean a response has already been written.  Returning -1 without it
+would answer the request with nothing at all.  Fedora and Alpine carry
+this fix in the shorter "return -1" form, which has that effect.  A
+syslog line is added because an unusable salt is an administrator's
+mistake, not a wrong password.
+
+In extras/htpasswd.c the result is passed to fprintf("%s"), so writing a
+password with a salt crypt() dislikes dereferences NULL.  Fedora and
+Alpine guard this one too.
+
+ACME lists a crypt() NULL check for the unreleased 2.30.
+
+--- libhttpd.c.orig
++++ libhttpd.c
+@@ -1030,6 +1030,7 @@
+     FILE* fp;
+     char line[500];
+     char* cryp;
++    char* cryp2;
+     static char* prevauthpath;
+     static size_t maxprevauthpath = 0;
+     static time_t prevmtime;
+@@ -1082,8 +1083,16 @@
+        sb.st_mtime == prevmtime &&
+        strcmp( authinfo, prevuser ) == 0 )
+       {
+-      /* Yes.  Check against the cached encrypted password. */
+-      if ( strcmp( crypt( authpass, prevcryp ), prevcryp ) == 0 )
++      /* Yes.  Check against the cached encrypted password.  crypt() returns
++      ** NULL for a salt it does not understand, and the password file is
++      ** user-supplied, so a bad line must not be dereferenced.  Treat it as
++      ** a mismatch so that the send_authenticate() below still runs: the
++      ** callers take -1 to mean a response has already been sent.
++      */
++      cryp = crypt( authpass, prevcryp );
++      if ( cryp == (char*) 0 )
++          syslog( LOG_ERR, "unusable password for %.80s in %.80s", authinfo, authpath );
++      if ( cryp != (char*) 0 && strcmp( cryp, prevcryp ) == 0 )
+           {
+           /* Ok! */
+           httpd_realloc_str(
+@@ -1131,8 +1140,11 @@
+           {
+           /* Yes. */
+           (void) fclose( fp );
+-          /* So is the password right? */
+-          if ( strcmp( crypt( authpass, cryp ), cryp ) == 0 )
++          /* So is the password right?  As above, crypt() may return NULL. */
++          cryp2 = crypt( authpass, cryp );
++          if ( cryp2 == (char*) 0 )
++              syslog( LOG_ERR, "unusable password for %.80s in %.80s", line, authpath );
++          if ( cryp2 != (char*) 0 && strcmp( cryp2, cryp ) == 0 )
+               {
+               /* Ok! */
+               httpd_realloc_str(
+--- extras/htpasswd.c.orig
++++ extras/htpasswd.c
+@@ -131,6 +131,11 @@
+     (void) srandom( (int) time( (time_t*) 0 ) );
+     to64( &salt[0], random(), 2 );
+     cpw = crypt( pw, salt );
++    if ( cpw == (char*) 0 )
++      {
++      (void) fprintf( stderr, "htpasswd: crypt() could not hash the password\n" );
++      exit( 1 );
++      }
+     (void) fprintf( f, "%s:%s\n", user, cpw );
+     }
+ 
Index: pkgsrc/www/thttpd/patches/patch-Makefile.in
diff -u /dev/null pkgsrc/www/thttpd/patches/patch-Makefile.in:1.1
--- /dev/null   Fri Sep 25 14:20:40 2026
+++ pkgsrc/www/thttpd/patches/patch-Makefile.in Fri Sep 25 14:20:39 2026
@@ -0,0 +1,59 @@
+$NetBSD: patch-Makefile.in,v 1.1 2026/09/25 14:20:39 wiz Exp $
+
+Install into the pkgsrc layout instead of the upstream defaults: the
+CGI programs go to libexec/cgi-bin and the sample web root to
+share/thttpd, rather than under $prefix/www.
+
+Use the BSD_INSTALL_* tools rather than "mkdir -p" and an install(1)
+run with a hardcoded owner of bin:bin, so that DESTDIR and the
+unprivileged build both work.
+
+CFLAGS is appended to rather than assigned, so that the flags pkgsrc
+passes in survive.
+
+--- Makefile.in.orig   2005-05-20 19:10:24.000000000 +0000
++++ Makefile.in
+@@ -34,7 +34,7 @@
+ # Pathname of directory to install the man page.
+ MANDIR = @mandir@
+ # Pathname of directory to install the CGI programs.
+-WEBDIR = $(prefix)/www
++WEBDIR = $(prefix)/share/thttpd
+ 
+ # CONFIGURE: The group that the web directory belongs to.  This is so that
+ # the makeweb program can be installed set-group-id to that group, and make
+@@ -42,7 +42,7 @@
+ WEBGROUP =    www
+ 
+ # CONFIGURE: Directory for CGI executables.
+-CGIBINDIR =   $(WEBDIR)/cgi-bin
++CGIBINDIR =   $(prefix)/libexec/cgi-bin
+ 
+ # You shouldn't need to edit anything below here.
+ 
+@@ -50,7 +50,7 @@
+ CCOPT =               @V_CCOPT@
+ DEFS =                @DEFS@
+ INCLS =               -I.
+-CFLAGS =      $(CCOPT) $(DEFS) $(INCLS)
++CFLAGS +=     $(CCOPT) $(DEFS) $(INCLS)
+ LDFLAGS =     @LDFLAGS@
+ LIBS =                @LIBS@
+ NETLIBS =     @V_NETLIBS@
+@@ -109,12 +109,12 @@
+ install:      installthis install-man installsubdirs
+ 
+ installthis:
+-      -mkdir -p $(DESTDIR)$(BINDIR)
+-      $(INSTALL) -m 555 -o bin -g bin thttpd $(DESTDIR)$(BINDIR)
++      $(BSD_INSTALL_PROGRAM_DIR) $(DESTDIR)$(BINDIR)
++      $(BSD_INSTALL_PROGRAM) thttpd $(DESTDIR)$(BINDIR)
+ 
+ install-man:
+-      -mkdir -p $(DESTDIR)$(MANDIR)/man8
+-      $(INSTALL) -m 444 -o bin -g bin thttpd.8 $(DESTDIR)$(MANDIR)/man8
++      $(BSD_INSTALL_DATA_DIR) $(DESTDIR)$(MANDIR)/man8
++      $(BSD_INSTALL_MAN) thttpd.8 $(DESTDIR)$(MANDIR)/man8
+ 
+ installsubdirs:
+       for i in $(SUBDIRS) ; do ( \
Index: pkgsrc/www/thttpd/patches/patch-cgi-src_Makefile.in
diff -u /dev/null pkgsrc/www/thttpd/patches/patch-cgi-src_Makefile.in:1.1
--- /dev/null   Fri Sep 25 14:20:40 2026
+++ pkgsrc/www/thttpd/patches/patch-cgi-src_Makefile.in Fri Sep 25 14:20:40 2026
@@ -0,0 +1,46 @@
+$NetBSD: patch-cgi-src_Makefile.in,v 1.1 2026/09/25 14:20:40 wiz Exp $
+
+The same as patch-ab for the CGI programs: honour DESTDIR, install with
+the BSD_INSTALL_* tools instead of cp, and append to CFLAGS rather than
+overwrite it.
+
+--- cgi-src/Makefile.in.orig   2005-05-20 19:10:25.000000000 +0000
++++ cgi-src/Makefile.in
+@@ -34,7 +34,7 @@
+ CCOPT =               @V_CCOPT@
+ DEFS =                @DEFS@
+ INCLS =               -I..
+-CFLAGS =      $(CCOPT) $(DEFS) $(INCLS)
++CFLAGS +=     $(CCOPT) $(DEFS) $(INCLS)
+ LDFLAGS =     @LDFLAGS@ @V_STATICFLAG@
+ LIBS =                @LIBS@
+ NETLIBS =     @V_NETLIBS@
+@@ -67,17 +67,17 @@
+       cd .. ; $(MAKE) $(MFLAGS) strerror.o
+ 
+ install:      all
+-      -mkdir -p $(CGIBINDIR)
+-      rm -f $(CGIBINDIR)/redirect
+-      cp redirect $(CGIBINDIR)/redirect
+-      rm -f $(MANDIR)/man8/redirect.8
+-      cp redirect.8 $(MANDIR)/man8/redirect.8
+-      rm -f $(CGIBINDIR)/ssi
+-      cp ssi $(CGIBINDIR)/ssi
+-      rm -f $(MANDIR)/man8/ssi.8
+-      cp ssi.8 $(MANDIR)/man8/ssi.8
+-      rm -f $(CGIBINDIR)/phf
+-      cp phf $(CGIBINDIR)/phf
++      -mkdir -p $(DESTDIR)$(CGIBINDIR)
++      rm -f $(DESTDIR)$(CGIBINDIR)/redirect
++      $(BSD_INSTALL_PROGRAM) redirect $(DESTDIR)$(CGIBINDIR)/redirect
++      rm -f $(DESTDIR)$(MANDIR)/man8/redirect.8
++      $(BSD_INSTALL_MAN) redirect.8 $(DESTDIR)$(MANDIR)/man8/redirect.8
++      rm -f $(DESTDIR)$(CGIBINDIR)/ssi
++      $(BSD_INSTALL_PROGRAM) ssi $(DESTDIR)$(CGIBINDIR)/ssi
++      rm -f $(DESTDIR)$(MANDIR)/man8/ssi.8
++      $(BSD_INSTALL_MAN) ssi.8 $(DESTDIR)$(MANDIR)/man8/ssi.8
++      rm -f $(DESTDIR)$(CGIBINDIR)/phf
++      $(BSD_INSTALL_PROGRAM) phf $(DESTDIR)$(CGIBINDIR)/phf
+ 
+ clean:
+       rm -f $(CLEANFILES)
Index: pkgsrc/www/thttpd/patches/patch-configure
diff -u /dev/null pkgsrc/www/thttpd/patches/patch-configure:1.1
--- /dev/null   Fri Sep 25 14:20:40 2026
+++ pkgsrc/www/thttpd/patches/patch-configure   Fri Sep 25 14:20:40 2026
@@ -0,0 +1,49 @@
+$NetBSD: patch-configure,v 1.1 2026/09/25 14:20:40 wiz Exp $
+
+Not a CVE: the configure script's test programs declare main() without a
+return type.  C99 removed implicit int and current compilers reject it,
+so the very first test -- "checking whether the C compiler works" --
+fails and configure stops with "C compiler cannot create executables".
+gcc 14 and clang 16 and later are in that state; this was measured with
+clang 21 and it is what breaks the build on Debian 13.
+
+Fedora and MacPorts both carry this fix.
+
+--- configure.orig
++++ configure
+@@ -761,7 +761,7 @@
+ #line 762 "configure"
+ #include "confdefs.h"
+ 
+-main(){return(0);}
++int main(){return(0);}
+ EOF
+ if { (eval echo configure:767: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+   ac_cv_prog_cc_works=yes
+@@ -880,7 +880,7 @@
+   echo $ac_n "(cached) $ac_c" 1>&6
+ else
+   ac_cv_lbl_static_flag=unknown
+-    echo 'main() {}' > conftest.c
++    echo 'int main() {}' > conftest.c
+     if test "$GCC" != yes ; then
+           trial_flag="-Bstatic"
+           test=`$CC $trial_flag -o conftest conftest.c 2>&1`
+@@ -1588,7 +1588,7 @@
+ #include "confdefs.h"
+ 
+ int main() {
+-main()
++int main()
+ ; return 0; }
+ EOF
+ if { (eval echo configure:1595: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+@@ -2095,7 +2095,7 @@
+ #endif
+ 
+ int
+-main()
++int main()
+ {
+       char *data, *data2, *data3;
+       int i, pagesize;
Index: pkgsrc/www/thttpd/patches/patch-extras_Makefile.in
diff -u /dev/null pkgsrc/www/thttpd/patches/patch-extras_Makefile.in:1.1
--- /dev/null   Fri Sep 25 14:20:40 2026
+++ pkgsrc/www/thttpd/patches/patch-extras_Makefile.in  Fri Sep 25 14:20:40 2026
@@ -0,0 +1,51 @@
+$NetBSD: patch-extras_Makefile.in,v 1.1 2026/09/25 14:20:40 wiz Exp $
+
+The same two things as patch-aa, for the helper programs: honour
+DESTDIR, install with the BSD_INSTALL_* tools instead of cp, and append
+to CFLAGS rather than overwrite it.
+
+syslogtocern is a shell script, so it goes in with BSD_INSTALL_SCRIPT.
+
+The upstream rule also made makeweb set-group-id here; that is left to
+the administrator instead, which is what README.pkgsrc explains.
+
+--- extras/Makefile.in.orig    2005-05-20 19:10:25.000000000 +0000
++++ extras/Makefile.in
+@@ -35,7 +35,7 @@
+ CCOPT =               @V_CCOPT@
+ DEFS =                @DEFS@
+ INCLS =               -I..
+-CFLAGS =      $(CCOPT) $(DEFS) $(INCLS)
++CFLAGS +=     $(CCOPT) $(DEFS) $(INCLS)
+ STATICFLAG =  @V_STATICFLAG@
+ LDFLAGS =     @LDFLAGS@
+ LIBS =                @LIBS@
+@@ -66,18 +66,16 @@
+ 
+ 
+ install:      all
+-      rm -f $(BINDIR)/makeweb $(BINDIR)/htpasswd $(BINDIR)/syslogtocern
+-      cp makeweb $(BINDIR)/makeweb
+-      chgrp $(WEBGROUP) $(BINDIR)/makeweb
+-      chmod 2755 $(BINDIR)/makeweb
+-      cp htpasswd $(BINDIR)/htpasswd
+-      cp syslogtocern $(BINDIR)/syslogtocern
+-      rm -f $(MANDIR)/man1/makeweb.1
+-      cp makeweb.1 $(MANDIR)/man1/makeweb.1
+-      rm -f $(MANDIR)/man1/htpasswd.1
+-      cp htpasswd.1 $(MANDIR)/man1/htpasswd.1
+-      rm -f $(MANDIR)/man8/syslogtocern.8
+-      cp syslogtocern.8 $(MANDIR)/man8/syslogtocern.8
++      rm -f $(DESTDIR)$(BINDIR)/makeweb $(DESTDIR)$(BINDIR)/htpasswd $(DESTDIR)$(BINDIR)/syslogtocern
++      $(BSD_INSTALL_PROGRAM) makeweb $(DESTDIR)$(BINDIR)/makeweb
++      $(BSD_INSTALL_PROGRAM) htpasswd $(DESTDIR)$(BINDIR)/htpasswd
++      $(BSD_INSTALL_SCRIPT) syslogtocern $(DESTDIR)$(BINDIR)/syslogtocern
++      rm -f $(DESTDIR)$(MANDIR)/man1/makeweb.1
++      $(BSD_INSTALL_MAN) makeweb.1 $(DESTDIR)$(MANDIR)/man1/makeweb.1
++      rm -f $(DESTDIR)$(MANDIR)/man1/htpasswd.1
++      $(BSD_INSTALL_MAN) htpasswd.1 $(DESTDIR)$(MANDIR)/man1/htpasswd.1
++      rm -f $(DESTDIR)$(MANDIR)/man8/syslogtocern.8
++      $(BSD_INSTALL_MAN) syslogtocern.8 $(DESTDIR)$(MANDIR)/man8/syslogtocern.8
+ 
+ clean:
+       rm -f $(CLEANFILES)
Index: pkgsrc/www/thttpd/patches/patch-extras_htpasswd.c
diff -u /dev/null pkgsrc/www/thttpd/patches/patch-extras_htpasswd.c:1.1
--- /dev/null   Fri Sep 25 14:20:40 2026
+++ pkgsrc/www/thttpd/patches/patch-extras_htpasswd.c   Fri Sep 25 14:20:40 2026
@@ -0,0 +1,70 @@
+$NetBSD: patch-extras_htpasswd.c,v 1.1 2026/09/25 14:20:40 wiz Exp $
+
+Not a CVE, two things in the htpasswd utility.
+
+The line buffers are MAX_STRING_LEN (256) while a .htpasswd line is
+"user:hash".  The hash of a modern crypt(3) runs to about a hundred
+characters, so a line written by any other tool does not fit, and
+my_getline() truncates at the limit without saying so; the remainder is
+then read as the next line.  Reading an existing file in order to add a
+user to it therefore corrupted it.  FreeBSD ports, Debian and Fedora all
+enlarge these buffers.
+
+fgets() is not checked when the password comes in on stdin, so pass is
+left uninitialised if it fails, and a leading NUL byte leaves strlen()
+at 0; either way pass[strlen(pass)-1] reads before the buffer.  Both
+paths fire under AddressSanitizer on the routine extracted unchanged
+from 2.29 (empty stdin, and a line starting with a NUL byte).  No other
+packaging fixes this one.
+
+--- extras/htpasswd.c.orig
++++ extras/htpasswd.c
+@@ -20,6 +20,12 @@
+ #define CR 13
+ 
+ #define MAX_STRING_LEN 256
++/* A .htpasswd line is "user:hash".  The hash of a modern crypt(3) is around a
++** hundred characters, so a line does not fit in MAX_STRING_LEN.  my_getline()
++** truncates silently at its limit and the remainder is then read as the next
++** line, so reading an existing file to update it corrupted it.
++*/
++#define MAX_LINE_LEN (MAX_STRING_LEN + 1 + 256)
+ 
+ int tfd;
+ char temp_template[] = "/tmp/htp.XXXXXX";
+@@ -112,8 +118,13 @@
+ 
+     if ( ! isatty( fileno( stdin ) ) )
+       {
+-      (void) fgets( pass, sizeof(pass), stdin );
+-      if ( pass[strlen(pass) - 1] == '\n' )
++      /* fgets() can fail, leaving pass uninitialised, and a NUL byte on
++      ** stdin leaves strlen() at 0; either way pass[strlen(pass)-1] would
++      ** read before the buffer.
++      */
++      if ( fgets( pass, sizeof(pass), stdin ) == (char*) 0 )
++          pass[0] = '\0';
++      if ( pass[0] != '\0' && pass[strlen(pass) - 1] == '\n' )
+           pass[strlen(pass) - 1] = '\0';
+       pw = pass;
+       }
+@@ -149,8 +160,8 @@
+ int main(int argc, char *argv[]) {
+     FILE *tfp,*f;
+     char user[MAX_STRING_LEN];
+-    char line[MAX_STRING_LEN];
+-    char l[MAX_STRING_LEN];
++    char line[MAX_LINE_LEN];
++    char l[MAX_LINE_LEN];
+     char w[MAX_STRING_LEN];
+     char command[MAX_STRING_LEN];
+     int found;
+@@ -188,7 +199,7 @@
+     user[sizeof(user)-1] = '\0';
+ 
+     found = 0;
+-    while(!(my_getline(line,MAX_STRING_LEN,f))) {
++    while(!(my_getline(line,MAX_LINE_LEN,f))) {
+         if(found || (line[0] == '#') || (!line[0])) {
+             putline(tfp,line);
+             continue;
Index: pkgsrc/www/thttpd/patches/patch-index.html
diff -u /dev/null pkgsrc/www/thttpd/patches/patch-index.html:1.1
--- /dev/null   Fri Sep 25 14:20:40 2026
+++ pkgsrc/www/thttpd/patches/patch-index.html  Fri Sep 25 14:20:40 2026
@@ -0,0 +1,18 @@
+$NetBSD: patch-index.html,v 1.1 2026/09/25 14:20:40 wiz Exp $
+
+Add the NetBSD logo to the sample index page.  The image it points at
+is sitedrivenby.gif, which the package fetches as a second distfile and
+installs beside this page in share/thttpd.
+
+--- index.html.orig    2005-05-13 20:20:35.000000000 +0000
++++ index.html
+@@ -9,6 +9,9 @@
+ 
+ <P>
+ Here's a link to the <A HREF="http://www.acme.com/software/thttpd/";>thttpd web pages</A>.
++<A HREF="http://www.netbsd.org/";>
++<IMG ALIGN=RIGHT SRC="sitedrivenby.gif" border=0 ALT="Site driven by NetBSD">
++</A>
+ 
+ </BODY>
+ </HTML>
Index: pkgsrc/www/thttpd/patches/patch-libhttpd.c
diff -u /dev/null pkgsrc/www/thttpd/patches/patch-libhttpd.c:1.1
--- /dev/null   Fri Sep 25 14:20:40 2026
+++ pkgsrc/www/thttpd/patches/patch-libhttpd.c  Fri Sep 25 14:20:40 2026
@@ -0,0 +1,44 @@
+$NetBSD: patch-libhttpd.c,v 1.1 2026/09/25 14:20:40 wiz Exp $
+
+Not a security fix, and not an underflow.  The stock code
+
+       i = strlen( hc->origfilename ) - strlen( hc->pathinfo );
+       if ( i > 0 && strcmp( &hc->origfilename[i], hc->pathinfo ) == 0 )
+           hc->origfilename[i - 1] = '\0';
+
+already prevents the index from going negative: the "i > 0" test means
+origfilename[i-1] is only reached with i >= 1.  I checked this after
+mistaking it for the same shape as the underflows in
+patch-CVE-2007-0158; it is not one.
+
+What it does change is behaviour.  When PATH_INFO is the whole of
+origfilename (i == 0) the stock code leaves origfilename alone, so the
+pathinfo part is not removed from it as the comment above it says it
+should be.  This makes that case set origfilename to the empty string.
+FreeBSD ports and Debian both carry the same change; Debian files it
+under "path-info" rather than as a security patch, which is the right
+reading.
+
+Included because the neighbouring CVE patches touch the same file and a
+reader will otherwise wonder why this one spot is left as it is.  Drop
+this patch if the behaviour change is not wanted -- the CVE patches do
+not depend on it.
+
+--- libhttpd.c.orig
++++ libhttpd.c
+@@ -2351,8 +2351,13 @@
+       {
+       int i;
+       i = strlen( hc->origfilename ) - strlen( hc->pathinfo );
+-      if ( i > 0 && strcmp( &hc->origfilename[i], hc->pathinfo ) == 0 )
+-          hc->origfilename[i - 1] = '\0';
++      if ( i >= 0 && strcmp( &hc->origfilename[i], hc->pathinfo ) == 0 )
++          {
++          if ( i == 0 )
++              hc->origfilename[0] = '\0';
++          else
++              hc->origfilename[i - 1] = '\0';
++          }
+       }
+ 
+     /* If the expanded filename is an absolute path, check that it's still
Index: pkgsrc/www/thttpd/patches/patch-mmc.c
diff -u /dev/null pkgsrc/www/thttpd/patches/patch-mmc.c:1.1
--- /dev/null   Fri Sep 25 14:20:40 2026
+++ pkgsrc/www/thttpd/patches/patch-mmc.c       Fri Sep 25 14:20:40 2026
@@ -0,0 +1,27 @@
+$NetBSD: patch-mmc.c,v 1.1 2026/09/25 14:20:40 wiz Exp $
+
+Turn off mmap on NetBSD up to 1.5L (__NetBSD_Version__ 105120000) and
+include <sys/param.h> so that version is defined.
+
+Before UBC arrived in 1.5L, mmap and read were not kept in sync, so
+thttpd went on serving the old contents of a page after it had been
+edited until it was restarted.  From PR pkg/25487, where Hauke Fath
+reported it on 1.5.4 and Michael Santos wrote this hunk.
+
+--- mmc.c.orig 2015-11-05 18:13:49.000000000 +0000
++++ mmc.c
+@@ -37,6 +37,14 @@
+ #include <fcntl.h>
+ #include <syslog.h>
+ #include <errno.h>
++
++#if defined(__NetBSD__)
++#include <sys/param.h>
++#endif
++
++#if (defined(__NetBSD__) && __NetBSD_Version__ <= 105120000)
++#undef HAVE_MMAP
++#endif
+ 
+ #ifdef HAVE_MMAP
+ #include <sys/mman.h>
Index: pkgsrc/www/thttpd/patches/patch-thttpd.c
diff -u /dev/null pkgsrc/www/thttpd/patches/patch-thttpd.c:1.1
--- /dev/null   Fri Sep 25 14:20:40 2026
+++ pkgsrc/www/thttpd/patches/patch-thttpd.c    Fri Sep 25 14:20:40 2026
@@ -0,0 +1,67 @@
+$NetBSD: patch-thttpd.c,v 1.1 2026/09/25 14:20:40 wiz Exp $
+
+Not a CVE: the access log is created world-readable.  Both places that
+open it use fopen(logfile, "a"), so a log that does not exist yet is
+created 0666 & ~umask -- 0644 under the usual umask.  Every line holds
+the request line, the Referer and the User-Agent, and on an
+authenticated directory the remote user name, so it should not be
+readable by every local user.  This matters most after log rotation,
+where the file is gone and thttpd recreates it on SIGHUP.
+
+Created with 0640 instead.  open() rather than a chmod() after fopen()
+so that there is no window in which the file exists with the wider
+mode, and so that a log the administrator already created keeps the
+mode they chose -- a chmod() would overwrite it on every start.
+
+FreeBSD ports and Fedora both fix this, but with a chmod() after the
+fopen(): FreeBSD to 0640, Fedora to 0600.  Fedora additionally disables
+the fchown() below, which stops thttpd handing the log to the user it
+drops privileges to, so it cannot re-open the log afterwards.  The
+fchown() is left alone here.
+
+--- thttpd.c.orig
++++ thttpd.c
+@@ -327,6 +327,25 @@
+     }
+ 
+ 
++/* Open the log file for appending.  A log that has to be created is created
++** without the world-readable bit: every line holds the request, the Referer
++** and the User-Agent, and on an authenticated directory the remote user name.
++** A file that already exists keeps the mode the administrator gave it.  This
++** uses open() rather than fopen() so that there is no window in which the
++** file exists with the wider mode.
++*/
++static FILE*
++open_logfile( char* lf )
++    {
++    int fd;
++
++    fd = open( lf, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP );
++    if ( fd < 0 )
++      return (FILE*) 0;
++    return fdopen( fd, "a" );
++    }
++
++
+ static void
+ re_open_logfile( void )
+     {
+@@ -339,7 +358,7 @@
+     if ( logfile != (char*) 0 && strcmp( logfile, "-" ) != 0 )
+       {
+       syslog( LOG_NOTICE, "re-opening logfile" );
+-      logfp = fopen( logfile, "a" );
++      logfp = open_logfile( logfile );
+       if ( logfp == (FILE*) 0 )
+           {
+           syslog( LOG_CRIT, "re-opening %.80s - %m", logfile );
+@@ -428,7 +447,7 @@
+           logfp = stdout;
+       else
+           {
+-          logfp = fopen( logfile, "a" );
++          logfp = open_logfile( logfile );
+           if ( logfp == (FILE*) 0 )
+               {
+               syslog( LOG_CRIT, "%.80s - %m", logfile );



Home | Main Index | Thread Index | Old Index