<https://schmonz.com/qmail/destdir>

diff --git Makefile Makefile
index 0f0e31a..01b0d4e 100644
--- Makefile
+++ Makefile
@@ -4,6 +4,14 @@ SHELL=/bin/sh
 
 default: it
 
+auto_destdir.c: \
+auto-str conf-destdir
+	./auto-str auto_qmail `head -1 conf-destdir` > auto_destdir.c
+
+auto_destdir.o: \
+compile auto_destdir.c
+	./compile auto_destdir.c
+
 addresses.0: \
 addresses.5
 	nroff -man addresses.5 > addresses.0
@@ -110,7 +118,7 @@ auto_split.o: \
 compile auto_split.c
 	./compile auto_split.c
 
-auto_uids.c: \
+auto_uids_orig.c: \
 auto-uid auto-gid conf-users conf-groups
 	( ./auto-uid auto_uida `head -1 conf-users` \
 	&&./auto-uid auto_uidd `head -2 conf-users | tail -1` \
@@ -122,10 +130,10 @@ auto-uid auto-gid conf-users conf-groups
 	&&./auto-uid auto_uids `head -8 conf-users | tail -1` \
 	&&./auto-gid auto_gidq `head -1 conf-groups` \
 	&&./auto-gid auto_gidn `head -2 conf-groups | tail -1` \
-	) > auto_uids.c.tmp && mv auto_uids.c.tmp auto_uids.c
+	) > auto_uids_orig.c.tmp && mv auto_uids_orig.c.tmp auto_uids_orig.c
 
 auto_uids.o: \
-compile auto_uids.c
+compile auto_uids.c auto_usergroupnames.h
 	./compile auto_uids.c
 
 auto_usera.c: \
@@ -136,6 +144,20 @@ auto_usera.o: \
 compile auto_usera.c
 	./compile auto_usera.c
 
+auto_usergroupnames.h: \
+auto-str conf-users conf-groups
+	( ./auto-str auto_usera `head -1 conf-users` \
+	&&./auto-str auto_userd `head -2 conf-users | tail -1` \
+	&&./auto-str auto_userl `head -3 conf-users | tail -1` \
+	&&./auto-str auto_usero `head -4 conf-users | tail -1` \
+	&&./auto-str auto_userp `head -5 conf-users | tail -1` \
+	&&./auto-str auto_userq `head -6 conf-users | tail -1` \
+	&&./auto-str auto_userr `head -7 conf-users | tail -1` \
+	&&./auto-str auto_users `head -8 conf-users | tail -1` \
+	&&./auto-str auto_groupq `head -1 conf-groups` \
+	&&./auto-str auto_groupn `head -2 conf-groups | tail -1` \
+	) > auto_usergroupnames.h.tmp && mv auto_usergroupnames.h.tmp auto_usergroupnames.h
+
 binm1: \
 binm1.sh conf-qmail
 	cat binm1.sh \
@@ -701,8 +723,18 @@ hfield.o: \
 compile hfield.c hfield.h
 	./compile hfield.c
 
+hier_destdir.c: \
+hier.c
+	cat hier.c \
+	| sed s}fake_uids\.h}auto_uids.h}g \
+	> hier_destdir.c
+
+hier_destdir.o: \
+compile hier_destdir.c auto_qmail.h auto_split.h auto_uids.h fmt.h fifo.h
+	./compile hier_destdir.c
+
 hier.o: \
-compile hier.c auto_qmail.h auto_split.h auto_uids.h fmt.h fifo.h
+compile hier.c auto_qmail.h auto_split.h fake_uids.h fmt.h fifo.h
 	./compile hier.c
 
 home: \
@@ -740,15 +772,15 @@ seek.h fork.h
 	./compile idedit.c
 
 install: \
-load install.o fifo.o hier.o auto_qmail.o auto_split.o auto_uids.o \
+load install.o fifo.o hier.o auto_destdir.o auto_split.o auto_uids.o \
 strerr.a substdio.a open.a error.a str.a fs.a
-	./load install fifo.o hier.o auto_qmail.o auto_split.o \
+	./load install fifo.o hier.o auto_destdir.o auto_split.o \
 	auto_uids.o strerr.a substdio.a open.a error.a str.a fs.a 
 
 install-big: \
-load install-big.o fifo.o install.o auto_qmail.o auto_split.o \
+load install-big.o fifo.o install.o auto_destdir.o auto_split.o \
 auto_uids.o strerr.a substdio.a open.a error.a str.a fs.a
-	./load install-big fifo.o install.o auto_qmail.o \
+	./load install-big fifo.o install.o auto_destdir.o \
 	auto_split.o auto_uids.o strerr.a substdio.a open.a error.a \
 	str.a fs.a 
 
@@ -757,15 +789,26 @@ compile install-big.c auto_qmail.h auto_split.h auto_uids.h fmt.h \
 fifo.h
 	./compile install-big.c
 
+install-destdir: \
+load install-destdir.o fifo.o hier_destdir.o auto_qmail.o auto_split.o auto_uids.o \
+strerr.a substdio.a open.a error.a str.a fs.a env.a
+	./load install-destdir fifo.o hier_destdir.o auto_qmail.o auto_split.o \
+	auto_uids.o strerr.a substdio.a open.a error.a str.a fs.a env.a
+
+install-destdir.o: \
+compile install-destdir.c substdio.h strerr.h error.h open.h readwrite.h \
+exit.h str.h env.h
+	./compile install-destdir.c
+
 install.o: \
 compile install.c substdio.h strerr.h error.h open.h readwrite.h \
 exit.h
 	./compile install.c
 
 instcheck: \
-load instcheck.o fifo.o hier.o auto_qmail.o auto_split.o auto_uids.o \
+load instcheck.o fifo.o hier_destdir.o auto_qmail.o auto_split.o auto_uids.o \
 strerr.a substdio.a error.a str.a fs.a
-	./load instcheck fifo.o hier.o auto_qmail.o auto_split.o \
+	./load instcheck fifo.o hier_destdir.o auto_qmail.o auto_split.o \
 	auto_uids.o strerr.a substdio.a error.a str.a fs.a 
 
 instcheck.o: \
diff --git TARGETS TARGETS
index facdad7..59aec59 100644
--- TARGETS
+++ TARGETS
@@ -154,7 +154,6 @@ auto-uid.o
 auto-uid
 auto-gid.o
 auto-gid
-auto_uids.c
 auto_uids.o
 qmail-lspawn
 qmail-getpw.o
@@ -385,3 +384,10 @@ forgeries.0
 man
 setup
 check
+auto_destdir.c
+auto_destdir.o
+auto_usergroupnames.h
+hier_destdir.c
+hier_destdir.o
+install-destdir.o
+install-destdir
diff --git auto_uids.c auto_uids.c
new file mode 100644
index 0000000..a55597b
--- /dev/null
+++ auto_uids.c
@@ -0,0 +1,59 @@
+#include <pwd.h>
+#include <grp.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include "auto_uids.h"
+#include "auto_usergroupnames.h"
+#include "error.h"
+#include "qlx.h"
+
+struct group *getgrnam();
+struct passwd *getpwnam();
+
+static int ids[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
+
+static int
+name2uid(name)
+char *name;
+{
+  struct passwd *pw;
+  errno = 0;
+  pw = getpwnam(name);
+  if (errno) _exit(QLX_SYS);
+  if (!pw) _exit(QLX_NOALIAS);
+  return (int)(pw->pw_uid);
+}
+
+static int
+name2gid(name)
+char *name;
+{
+  struct group *gr;
+  errno = 0;
+  gr = getgrnam(name);
+  if (errno) _exit(QLX_SYS);
+  if (!gr) _exit(QLX_NOALIAS);
+  return (int)(gr->gr_gid);
+}
+
+int
+qmail_id_lookup(id)
+int id;
+{
+  if (ids[id] >= 0) return ids[id];
+
+  switch(id) {
+  case ID_OWNER:   ids[id] = name2uid(auto_usero); break;
+  case ID_ALIAS:   ids[id] = name2uid(auto_usera); break;
+  case ID_DAEMON:  ids[id] = name2uid(auto_userd); break;
+  case ID_LOG:     ids[id] = name2uid(auto_userl); break;
+  case ID_PASSWD:  ids[id] = name2uid(auto_userp); break;
+  case ID_QUEUE:   ids[id] = name2uid(auto_userq); break;
+  case ID_REMOTE:  ids[id] = name2uid(auto_userr); break;
+  case ID_SEND:    ids[id] = name2uid(auto_users); break;
+  case ID_QMAIL:   ids[id] = name2gid(auto_groupq); break;
+  case ID_NOFILES: ids[id] = name2gid(auto_groupn); break;
+  default: _exit(QLX_NOALIAS);
+  }
+  return ids[id];
+}
diff --git auto_uids.h auto_uids.h
index 1252ecb..882b597 100644
--- auto_uids.h
+++ auto_uids.h
@@ -1,16 +1,29 @@
 #ifndef AUTO_UIDS_H
 #define AUTO_UIDS_H
 
-extern int auto_uida;
-extern int auto_uidd;
-extern int auto_uidl;
-extern int auto_uido;
-extern int auto_uidp;
-extern int auto_uidq;
-extern int auto_uidr;
-extern int auto_uids;
+#define ID_OWNER   0
+#define ID_ALIAS   1
+#define ID_DAEMON  2
+#define ID_LOG     3
+#define ID_PASSWD  4
+#define ID_QUEUE   5
+#define ID_REMOTE  6
+#define ID_SEND    7
+#define ID_QMAIL   8
+#define ID_NOFILES 9
 
-extern int auto_gidn;
-extern int auto_gidq;
+#define auto_uido qmail_id_lookup(ID_OWNER)
+#define auto_uida qmail_id_lookup(ID_ALIAS)
+#define auto_uidd qmail_id_lookup(ID_DAEMON)
+#define auto_uidl qmail_id_lookup(ID_LOG)
+#define auto_uidp qmail_id_lookup(ID_PASSWD)
+#define auto_uidq qmail_id_lookup(ID_QUEUE)
+#define auto_uidr qmail_id_lookup(ID_REMOTE)
+#define auto_uids qmail_id_lookup(ID_SEND)
+
+#define auto_gidq qmail_id_lookup(ID_QMAIL)
+#define auto_gidn qmail_id_lookup(ID_NOFILES)
+
+extern int qmail_id_lookup();
 
 #endif
diff --git conf-destdir conf-destdir
new file mode 100644
index 0000000..5720f2a
--- /dev/null
+++ conf-destdir
@@ -0,0 +1,5 @@
+/var/tmp/qmail-destdir
+
+This is the qmail installation staging directory. It can be the same as
+the qmail home directory (conf-qmail), if you want. If it's somewhere
+else writable by non-root, you can build as non-root.
diff --git fake_chown.h fake_chown.h
new file mode 100644
index 0000000..b471d65
--- /dev/null
+++ fake_chown.h
@@ -0,0 +1,11 @@
+#ifndef FAKE_CHOWN_H
+#define FAKE_CHOWN_H
+
+/*
+ * Included only by install.c, which we don't want setting permissions
+ * Permissions will be set by ./install-destdir
+ */
+
+int chown(const char *p, unsigned int o, unsigned int g) { return 0; }
+
+#endif
diff --git fake_uids.h fake_uids.h
new file mode 100644
index 0000000..b76103e
--- /dev/null
+++ fake_uids.h
@@ -0,0 +1,22 @@
+#ifndef FAKE_UIDS_H
+#define FAKE_UIDS_H
+
+/*
+ * Included only by hier.c, which is linked only into ./install
+ * These values don't matter there, as it doesn't try to set permissions
+ * Permissions are set by ./install-destdir (linked with hier_destdir.c)
+ */
+
+#define auto_uido -79
+#define auto_uida -78
+#define auto_uidd -77
+#define auto_uidl -76
+#define auto_uidp -75
+#define auto_uidq -74
+#define auto_uidr -73
+#define auto_uids -72
+
+#define auto_gidq -71
+#define auto_gidn -70
+
+#endif
diff --git hier.c hier.c
index 28e568d..df45c6e 100644
--- hier.c
+++ hier.c
@@ -1,6 +1,6 @@
 #include "auto_qmail.h"
 #include "auto_split.h"
-#include "auto_uids.h"
+#include "fake_uids.h"
 #include "fmt.h"
 #include "fifo.h"
 
diff --git install-destdir.c install-destdir.c
new file mode 100644
index 0000000..ddafcd9
--- /dev/null
+++ install-destdir.c
@@ -0,0 +1,126 @@
+#include "substdio.h"
+#include "strerr.h"
+#include "error.h"
+#include "open.h"
+#include "readwrite.h"
+#include "exit.h"
+#include "str.h"
+#include "env.h"
+
+extern void hier();
+
+#define FATAL "install-destdir: fatal: "
+
+void h(home,uid,gid,mode)
+char *home;
+int uid;
+int gid;
+int mode;
+{
+  if (mkdir(home,0700) == -1)
+    if (errno != error_exist)
+      strerr_die4sys(111,FATAL,"unable to mkdir ",home,": ");
+  if (chown(home,uid,gid) == -1)
+    strerr_die4sys(111,FATAL,"unable to chown ",home,": ");
+  if (chmod(home,mode) == -1)
+    strerr_die4sys(111,FATAL,"unable to chmod ",home,": ");
+}
+
+void d(home,subdir,uid,gid,mode)
+char *home;
+char *subdir;
+int uid;
+int gid;
+int mode;
+{
+  if (chdir(home) == -1)
+    strerr_die4sys(111,FATAL,"unable to switch to ",home,": ");
+  if (mkdir(subdir,0700) == -1)
+    if (errno != error_exist)
+      strerr_die6sys(111,FATAL,"unable to mkdir ",home,"/",subdir,": ");
+  if (chown(subdir,uid,gid) == -1)
+    strerr_die6sys(111,FATAL,"unable to chown ",home,"/",subdir,": ");
+  if (chmod(subdir,mode) == -1)
+    strerr_die6sys(111,FATAL,"unable to chmod ",home,"/",subdir,": ");
+}
+
+void p(home,fifo,uid,gid,mode)
+char *home;
+char *fifo;
+int uid;
+int gid;
+int mode;
+{
+  if (chdir(home) == -1)
+    strerr_die4sys(111,FATAL,"unable to switch to ",home,": ");
+  if (fifo_make(fifo,0700) == -1)
+    if (errno != error_exist)
+      strerr_die6sys(111,FATAL,"unable to mkfifo ",home,"/",fifo,": ");
+  if (chown(fifo,uid,gid) == -1)
+    strerr_die6sys(111,FATAL,"unable to chown ",home,"/",fifo,": ");
+  if (chmod(fifo,mode) == -1)
+    strerr_die6sys(111,FATAL,"unable to chmod ",home,"/",fifo,": ");
+}
+
+char outbuf[SUBSTDIO_OUTSIZE];
+substdio ssout;
+
+void c(home,subdir,file,uid,gid,mode)
+char *home;
+char *subdir;
+char *file;
+int uid;
+int gid;
+int mode;
+{
+  if (chdir(home) == -1)
+    strerr_die4sys(111,FATAL,"unable to switch to ",home,": ");
+  if (chdir(subdir) == -1)
+    strerr_die6sys(111,FATAL,"unable to switch to ",home,"/",subdir,": ");
+  if (chown(file,uid,gid) == -1)
+    strerr_die6sys(111,FATAL,"unable to chown .../",subdir,"/",file,": ");
+  if (chmod(file,mode) == -1)
+    strerr_die6sys(111,FATAL,"unable to chmod .../",subdir,"/",file,": ");
+}
+
+void z(home,file,len,uid,gid,mode)
+char *home;
+char *file;
+int len;
+int uid;
+int gid;
+int mode;
+{
+  int fdout;
+
+  if (chdir(home) == -1)
+    strerr_die4sys(111,FATAL,"unable to switch to ",home,": ");
+
+  fdout = open_trunc(file);
+  if (fdout == -1)
+    strerr_die6sys(111,FATAL,"unable to write ",home,"/",file,": ");
+  substdio_fdbuf(&ssout,write,fdout,outbuf,sizeof outbuf);
+
+  while (len-- > 0)
+    if (substdio_put(&ssout,"",1) == -1)
+      strerr_die6sys(111,FATAL,"unable to write ",home,"/",file,": ");
+
+  if (substdio_flush(&ssout) == -1)
+    strerr_die6sys(111,FATAL,"unable to write ",home,"/",file,": ");
+  if (fsync(fdout) == -1)
+    strerr_die6sys(111,FATAL,"unable to write ",home,"/",file,": ");
+  if (close(fdout) == -1) /* NFS silliness */
+    strerr_die6sys(111,FATAL,"unable to write ",home,"/",file,": ");
+
+  if (chown(file,uid,gid) == -1)
+    strerr_die6sys(111,FATAL,"unable to chown ",home,"/",file,": ");
+  if (chmod(file,mode) == -1)
+    strerr_die6sys(111,FATAL,"unable to chmod ",home,"/",file,": ");
+}
+
+void main()
+{
+  umask(077);
+  hier();
+  _exit(0);
+}
diff --git install.c install.c
index 95034f2..1c72225 100644
--- install.c
+++ install.c
@@ -4,6 +4,7 @@
 #include "open.h"
 #include "readwrite.h"
 #include "exit.h"
+#include "fake_chown.h"
 
 extern void hier();
 
