nightmaremail

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit af9625ddf9464e85fd781e8ad889201ce340d9fc
Author: D. J. Bernstein <djb@cr.yp.to>
Date:   Tue, 28 Oct 1997 00:00:00 +0100

qmail 1.01

Diffstat:
ABLURB | 43+++++++++++++++++++++++++++++++++++++++++++
ABLURB2 | 26++++++++++++++++++++++++++
ABLURB3 | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ABLURB4 | 44++++++++++++++++++++++++++++++++++++++++++++
ACHANGES | 1204+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AFAQ | 574+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AFILES | 401+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AINSTALL | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AINSTALL.alias | 40++++++++++++++++++++++++++++++++++++++++
AINSTALL.boot | 16++++++++++++++++
AINSTALL.ctl | 29+++++++++++++++++++++++++++++
AINSTALL.ids | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AINSTALL.mbox | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AINSTALL.qsmhook | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
AINTERNALS | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AMakefile | 2181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AREADME | 199+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ARFCHCSC | 37+++++++++++++++++++++++++++++++++++++
ARFCLOOPS | 338+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ARFCMXPS | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ARFCNETSTR | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ARFCNRUDT | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ARFCQMTP | 229+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ARFCQSBMF | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ARFCVERP | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ASECURITY | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ASYSDEPS | 17+++++++++++++++++
ATARGETS | 353+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ATHANKS | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ATHOUGHTS | 437+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ATODO | 9+++++++++
AUPGRADE | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AVERSION | 1+
Aaddresses.5 | 260+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aalloc.3 | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aalloc.c | 32++++++++++++++++++++++++++++++++
Aalloc.h | 8++++++++
Aalloc_re.c | 17+++++++++++++++++
Aauto-gid.c | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Aauto-int.c | 40++++++++++++++++++++++++++++++++++++++++
Aauto-int8.c | 40++++++++++++++++++++++++++++++++++++++++
Aauto-str.c | 44++++++++++++++++++++++++++++++++++++++++++++
Aauto-uid.c | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Aauto_break.h | 6++++++
Aauto_patrn.h | 6++++++
Aauto_qmail.h | 6++++++
Aauto_spawn.h | 6++++++
Aauto_split.h | 6++++++
Aauto_uids.h | 16++++++++++++++++
Aauto_usera.h | 6++++++
Abyte.h | 13+++++++++++++
Abyte_chr.c | 20++++++++++++++++++++
Abyte_copy.c | 14++++++++++++++
Abyte_cr.c | 16++++++++++++++++
Abyte_diff.c | 16++++++++++++++++
Abyte_rchr.c | 23+++++++++++++++++++++++
Abyte_zero.c | 13+++++++++++++
Acase.3 | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acase.h | 13+++++++++++++
Acase_diffb.c | 21+++++++++++++++++++++
Acase_diffs.c | 19+++++++++++++++++++
Acase_lowerb.c | 14++++++++++++++
Acase_lowers.c | 12++++++++++++
Acase_starts.c | 18++++++++++++++++++
Acdb.3 | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acdb.h | 12++++++++++++
Acdb_hash.c | 16++++++++++++++++
Acdb_seek.c | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acdb_unpack.c | 12++++++++++++
Acdbmake.h | 35+++++++++++++++++++++++++++++++++++
Acdbmake_add.c | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acdbmake_hash.c | 10++++++++++
Acdbmake_pack.c | 11+++++++++++
Acdbmss.c | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acdbmss.h | 16++++++++++++++++
Achkshsgr.c | 9+++++++++
Achkspawn.c | 48++++++++++++++++++++++++++++++++++++++++++++++++
Acoe.3 | 25+++++++++++++++++++++++++
Acoe.c | 8++++++++
Acoe.h | 6++++++
Acondredirect.1 | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acondredirect.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconf-break | 9+++++++++
Aconf-cc | 3+++
Aconf-groups | 6++++++
Aconf-ld | 3+++
Aconf-patrn | 5+++++
Aconf-qmail | 4++++
Aconf-spawn | 5+++++
Aconf-split | 3+++
Aconf-users | 15+++++++++++++++
Aconstmap.c | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconstmap.h | 22++++++++++++++++++++++
Acontrol.c | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontrol.h | 10++++++++++
Adate822fmt.c | 29+++++++++++++++++++++++++++++
Adate822fmt.h | 7+++++++
Adatemail.sh | 1+
Adatetime.3 | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adatetime.c | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adatetime.h | 20++++++++++++++++++++
Adatetime_un.c | 35+++++++++++++++++++++++++++++++++++
Adirentry.3 | 36++++++++++++++++++++++++++++++++++++
Adirentry.h1 | 8++++++++
Adirentry.h2 | 8++++++++
Adns.c | 402+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adns.h | 14++++++++++++++
Adnscname.c | 25+++++++++++++++++++++++++
Adnsdoe.c | 16++++++++++++++++
Adnsdoe.h | 6++++++
Adnsfq.c | 32++++++++++++++++++++++++++++++++
Adnsip.c | 34++++++++++++++++++++++++++++++++++
Adnsmxip.c | 40++++++++++++++++++++++++++++++++++++++++
Adnsptr.c | 27+++++++++++++++++++++++++++
Adot-qmail.9 | 394+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelq.sh | 1+
Aenv.3 | 31+++++++++++++++++++++++++++++++
Aenv.c | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aenv.h | 17+++++++++++++++++
Aenvelopes.5 | 231+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aenvread.c | 30++++++++++++++++++++++++++++++
Aerror.3 | 45+++++++++++++++++++++++++++++++++++++++++++++
Aerror.c | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aerror.h | 23+++++++++++++++++++++++
Aerror_str.3 | 19+++++++++++++++++++
Aerror_str.c | 276+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aerror_temp.3 | 27+++++++++++++++++++++++++++
Aerror_temp.c | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aexit.h | 6++++++
Aextra.h | 7+++++++
Afd.h | 7+++++++
Afd_copy.3 | 44++++++++++++++++++++++++++++++++++++++++++++
Afd_copy.c | 13+++++++++++++
Afd_move.3 | 41+++++++++++++++++++++++++++++++++++++++++
Afd_move.c | 11+++++++++++
Afifo.c | 10++++++++++
Afifo.h | 6++++++
Afifo_make.3 | 24++++++++++++++++++++++++
Afind-systype.sh | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afmt.h | 25+++++++++++++++++++++++++
Afmt_str.c | 12++++++++++++
Afmt_strn.c | 12++++++++++++
Afmt_uint.c | 6++++++
Afmt_uint0.c | 10++++++++++
Afmt_ulong.c | 13+++++++++++++
Afmtqfn.c | 24++++++++++++++++++++++++
Afmtqfn.h | 8++++++++
Aforgeries.7 | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afork.h1 | 7+++++++
Afork.h2 | 7+++++++
Aforward.1 | 24++++++++++++++++++++++++
Aforward.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agen_alloc.h | 7+++++++
Agen_allocdefs.h | 34++++++++++++++++++++++++++++++++++
Agetln.3 | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Agetln.c | 20++++++++++++++++++++
Agetln.h | 7+++++++
Agetln2.3 | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agetln2.c | 31+++++++++++++++++++++++++++++++
Agfrom.c | 10++++++++++
Agfrom.h | 6++++++
Aheaderbody.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aheaderbody.h | 6++++++
Ahfield.c | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahfield.h | 37+++++++++++++++++++++++++++++++++++++
Ahostname.c | 17+++++++++++++++++
Ainstall.c | 167+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainstcheck.c | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aip.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Aip.h | 11+++++++++++
Aipalloc.c | 7+++++++
Aipalloc.h | 14++++++++++++++
Aipme.c | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aipme.h | 12++++++++++++
Aipmeprint.c | 24++++++++++++++++++++++++
Alock.h | 8++++++++
Alock_ex.c | 11+++++++++++
Alock_exnb.c | 11+++++++++++
Alock_un.c | 11+++++++++++
Amaildir.5 | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amaildir.c | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amaildir.h | 12++++++++++++
Amaildir2mbox.1 | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Amaildir2mbox.c | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amaildirmake.1 | 15+++++++++++++++
Amaildirmake.c | 22++++++++++++++++++++++
Amaildirwatch.1 | 23+++++++++++++++++++++++
Amaildirwatch.c | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amailsubj.1 | 38++++++++++++++++++++++++++++++++++++++
Amailsubj.sh | 7+++++++
Amake-compile.sh | 1+
Amake-load.sh | 2++
Amake-makelib.sh | 16++++++++++++++++
Ambox.5 | 235+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amyctime.c | 37+++++++++++++++++++++++++++++++++++++
Amyctime.h | 6++++++
Andelay.c | 9+++++++++
Andelay.h | 7+++++++
Andelay_off.c | 9+++++++++
Anewfield.c | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anewfield.h | 12++++++++++++
Anow.3 | 14++++++++++++++
Anow.c | 8++++++++
Anow.h | 8++++++++
Aopen.h | 10++++++++++
Aopen_append.c | 6++++++
Aopen_excl.c | 6++++++
Aopen_read.c | 6++++++
Aopen_trunc.c | 6++++++
Aopen_write.c | 6++++++
Apinq.sh | 1+
Apredate.c | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apreline.1 | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apreline.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aprioq.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aprioq.h | 15+++++++++++++++
Aprot.c | 21+++++++++++++++++++++
Aprot.h | 7+++++++
Aqail.sh | 1+
Aqbiff.1 | 31+++++++++++++++++++++++++++++++
Aqbiff.c | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqlist.1 | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqlist.c | 333+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqlist2.sh | 1+
Aqlx.h | 18++++++++++++++++++
Aqmail-clean.8 | 13+++++++++++++
Aqmail-clean.c | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-command.8 | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-config.sh | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-control.9 | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-getpw.9 | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-getpw.c | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-header.5 | 331+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-hier.c | 248+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-inject.8 | 294+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-inject.c | 735+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-limits.9 | 30++++++++++++++++++++++++++++++
Aqmail-local.8 | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-local.c | 672+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-log.5 | 270+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-lspawn.8 | 46++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-lspawn.c | 234+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-newu.9 | 43+++++++++++++++++++++++++++++++++++++++++++
Aqmail-newu.c | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-pop3d.8 | 43+++++++++++++++++++++++++++++++++++++++++++
Aqmail-pop3d.c | 397+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-popup.8 | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-popup.c | 219+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-pw2u.9 | 235+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-pw2u.c | 308+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-qmtpd.8 | 29+++++++++++++++++++++++++++++
Aqmail-qmtpd.c | 281+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-qread.8 | 24++++++++++++++++++++++++
Aqmail-qread.c | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-qstat.8 | 18++++++++++++++++++
Aqmail-qstat.sh | 3+++
Aqmail-queue.8 | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-queue.c | 254+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-remote.8 | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-remote.c | 480+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-rspawn.8 | 21+++++++++++++++++++++
Aqmail-rspawn.c | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-send.9 | 264+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-send.c | 1652+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-showctl.8 | 12++++++++++++
Aqmail-showctl.c | 233+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-smtpd.8 | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-smtpd.c | 449+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-start.9 | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-start.c | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-tcpto.8 | 29+++++++++++++++++++++++++++++
Aqmail-tcpto.c | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-upgrade.9 | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail-upq.sh | 14++++++++++++++
Aqmail-users.9 | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail.7 | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail.c | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqmail.h | 36++++++++++++++++++++++++++++++++++++
Aqreceipt.1 | 33+++++++++++++++++++++++++++++++++
Aqreceipt.c | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqsmhook.c | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aqsutil.c | 46++++++++++++++++++++++++++++++++++++++++++++++
Aqsutil.h | 12++++++++++++
Aquote.c | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aquote.h | 8++++++++
Areadsubdir.c | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Areadsubdir.h | 20++++++++++++++++++++
Areadwrite.h | 7+++++++
Areceived.c | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Areceived.h | 6++++++
Aremoteinfo.c | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aremoteinfo.h | 6++++++
Ascan.h | 27+++++++++++++++++++++++++++
Ascan_8long.c | 11+++++++++++
Ascan_nbblong.c | 33+++++++++++++++++++++++++++++++++
Ascan_ulong.c | 11+++++++++++
Aseek.h | 15+++++++++++++++
Aseek_cur.c | 7+++++++
Aseek_end.c | 7+++++++
Aseek_set.c | 7+++++++
Aseek_trunc.c | 5+++++
Aselect.h1 | 8++++++++
Aselect.h2 | 9+++++++++
Asendmail.c | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asgetopt.3 | 28++++++++++++++++++++++++++++
Asgetopt.c | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asgetopt.h | 21+++++++++++++++++++++
Asig.h | 43+++++++++++++++++++++++++++++++++++++++++++
Asig_alarm.c | 7+++++++
Asig_block.c | 40++++++++++++++++++++++++++++++++++++++++
Asig_bug.c | 17+++++++++++++++++
Asig_catch.c | 18++++++++++++++++++
Asig_child.c | 7+++++++
Asig_hup.c | 7+++++++
Asig_misc.c | 17+++++++++++++++++
Asig_pause.c | 14++++++++++++++
Asig_pipe.c | 5+++++
Asig_term.c | 7+++++++
Aslurpclose.c | 17+++++++++++++++++
Aslurpclose.h | 6++++++
Aspawn.c | 259+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asplogger.8 | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asplogger.c | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astr.h | 14++++++++++++++
Astr_chr.c | 19+++++++++++++++++++
Astr_cpy.c | 16++++++++++++++++
Astr_diff.c | 17+++++++++++++++++
Astr_diffn.c | 18++++++++++++++++++
Astr_len.c | 15+++++++++++++++
Astr_rchr.c | 22++++++++++++++++++++++
Astr_start.c | 15+++++++++++++++
Astralloc.3 | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astralloc.h | 21+++++++++++++++++++++
Astralloc_arts.c | 12++++++++++++
Astralloc_cat.c | 9+++++++++
Astralloc_catb.c | 15+++++++++++++++
Astralloc_cats.c | 10++++++++++
Astralloc_copy.c | 9+++++++++
Astralloc_eady.c | 6++++++
Astralloc_opyb.c | 14++++++++++++++
Astralloc_opys.c | 10++++++++++
Astralloc_pend.c | 5+++++
Astrerr.h | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astrerr_die.c | 37+++++++++++++++++++++++++++++++++++++
Astrerr_sys.c | 12++++++++++++
Asubfd.h | 15+++++++++++++++
Asubfderr.c | 7+++++++
Asubfdin.c | 13+++++++++++++
Asubfdins.c | 13+++++++++++++
Asubfdout.c | 7+++++++
Asubfdouts.c | 7+++++++
Asubgetopt.3 | 357+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asubgetopt.c | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asubgetopt.h | 24++++++++++++++++++++++++
Asubstdi.c | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asubstdio.c | 15+++++++++++++++
Asubstdio.h | 41+++++++++++++++++++++++++++++++++++++++++
Asubstdio_copy.c | 18++++++++++++++++++
Asubstdo.c | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atcp-env.1 | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atcp-env.c | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atcp-environ.5 | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atcpto.c | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atcpto.h | 8++++++++
Atcpto_clean.c | 20++++++++++++++++++++
Atimeoutconn.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atimeoutconn.h | 6++++++
Atimeoutread.c | 25+++++++++++++++++++++++++
Atimeoutread.h | 8++++++++
Atimeoutwrite.c | 25+++++++++++++++++++++++++
Atimeoutwrite.h | 8++++++++
Atoken822.c | 511+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atoken822.h | 37+++++++++++++++++++++++++++++++++++++
Atrigger.c | 41+++++++++++++++++++++++++++++++++++++++++
Atrigger.h | 8++++++++
Atriggerpull.c | 16++++++++++++++++
Atriggerpull.h | 6++++++
Atrycpp.c | 7+++++++
Atrydrent.c | 8++++++++
Atryflock.c | 8++++++++
Atrylsock.c | 4++++
Atrymkffo.c | 7+++++++
Atrynpbg1.c | 26++++++++++++++++++++++++++
Atryrsolv.c | 4++++
Atrysalen.c | 11+++++++++++
Atrysgact.c | 10++++++++++
Atrysgprm.c | 10++++++++++
Atryshsgr.c | 14++++++++++++++
Atrysysel.c | 8++++++++
Atrysyslog.c | 9+++++++++
Atryulong32.c | 11+++++++++++
Atryvfork.c | 4++++
Atrywaitp.c | 7+++++++
Auint32.h1 | 6++++++
Auint32.h2 | 6++++++
Await.3 | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Await.h | 14++++++++++++++
Await_nohang.c | 12++++++++++++
Await_pid.c | 13+++++++++++++
Awarn-auto.sh | 2++
Awarn-shsgr | 3+++
401 files changed, 30689 insertions(+), 0 deletions(-)

diff --git a/BLURB b/BLURB @@ -0,0 +1,43 @@ +qmail is a secure, reliable, efficient, simple message transfer agent. +It is meant as a replacement for the entire sendmail-binmail system on +typical Internet-connected UNIX hosts. + +Secure: Security isn't just a goal, but an absolute requirement. Mail +delivery is critical for users; it cannot be turned off, so it must be +completely secure. (This is why I started writing qmail: I was sick of +the security holes in sendmail and other MTAs.) + +Reliable: qmail's straight-paper-path philosophy guarantees that a +message, once accepted into the system, will never be lost. qmail also +supports maildir, a new, super-reliable user mailbox format. Maildirs, +unlike mbox files and mh folders, won't be corrupted if the system +crashes during delivery. Even better, not only can a user safely read +his mail over NFS, but any number of NFS clients can deliver mail to him +at the same time. + +Efficient: On a Pentium under BSD/OS, qmail can easily sustain 200000 +local messages per day---that's separate messages injected and delivered +to mailboxes in a real test! Although remote deliveries are inherently +limited by the slowness of DNS and SMTP, qmail overlaps 20 simultaneous +deliveries by default, so it zooms quickly through mailing lists. (This +is why I finished qmail: I had to get a big mailing list set up.) + +Simple: qmail is vastly smaller than any other Internet MTA. Some +reasons why: (1) Other MTAs have separate forwarding, aliasing, and +mailing list mechanisms. qmail has one simple forwarding mechanism that +lets users handle their own mailing lists. (2) Other MTAs offer a +spectrum of delivery modes, from fast+unsafe to slow+queued. qmail-send +is instantly triggered by new items in the queue, so the qmail system +has just one delivery mode: fast+queued. (3) Other MTAs include, in +effect, a specialized version of inetd that watches the load average. +qmail's design inherently limits the machine load, so qmail-smtpd can +safely run from your system's inetd. + +Replacement for sendmail: qmail supports host and user masquerading, +full host hiding, virtual domains, null clients, list-owner rewriting, +relay control, double-bounce recording, arbitrary RFC 822 address lists, +cross-host mailing list loop detection, per-recipient checkpointing, +downed host backoffs, independent message retry schedules, etc. In +short, it's up to speed on modern MTA features. qmail also includes a +drop-in ``sendmail'' wrapper so that it will be used transparently by +your current UAs. diff --git a/BLURB2 b/BLURB2 @@ -0,0 +1,26 @@ +Mailing list management is one of qmail's strengths. Notable features: + +* qmail lets each user handle his own mailing lists. The delivery +instructions for user-whatever go into ~user/.qmail-whatever. + +* qmail makes it really easy to set up mailing list owners. If the user +touches ~user/.qmail-whatever-owner, all bounces will come back to him. + +* qmail supports VERPs, which permit completely reliable automated +bounce handling for mailing lists of any size. + +* SPEED---qmail blasts through mailing lists an order of magnitude +faster than sendmail. For example, one message was successfully +delivered to 150 hosts around the world in just 70 seconds, with qmail's +out-of-the-box configuration. + +* qmail automatically prevents mailing list loops, even across hosts. + +* qlist, included in the qmail package, deals with subscription requests +safely and automatically. + +* qmail allows inconceivably gigantic mailing lists. No random limits. + +* qmail handles aliasing and forwarding with the same simple mechanism. +For example, Postmaster is controlled by ~alias/.qmail-postmaster. This +means that cross-host loop detection also applies to aliases. diff --git a/BLURB3 b/BLURB3 @@ -0,0 +1,90 @@ +Here are some of qmail's features. + +Setup: +* automatic adaptation to your UNIX variant---no configuration needed +* AIX, BSD/OS, FreeBSD, HP/UX, Irix, Linux, OSF/1, SunOS, Solaris, and more +* automatic per-host configuration (qmail-config) +* quick installation---no big list of decisions to make + +Security: +* clear separation between addresses, files, and programs +* minimization of setuid code (qmail-queue) +* minimization of root code (qmail-start, qmail-lspawn) +* five-way trust partitioning---security in depth +* optional logging of one-way hashes, entire contents, etc. (QUEUE_EXTRA) + +Message construction (qmail-inject): +* RFC 822, RFC 1123 +* full support for address groups +* automatic conversion of old-style headers to RFC 822 format +* header line length limited only by memory +* host masquerading (control/defaulthost) +* user masquerading (MAILUSER, MAILHOST) +* sendmail hook for compatibility with current user agents + +SMTP service (qmail-smtpd): +* RFC 821, RFC 1123, RFC 1651, RFC 1652, RFC 1854 +* 8-bit clean +* 931/1413/ident/TAP callback (tcp-env) +* relay control---stop unauthorized relaying by outsiders (control/rcpthosts) +* no interference between relay control and forwarding +* tcpd hook---reject SMTP connections from known abusers +* automatic recognition of local IP addresses +* per-buffer timeouts +* hop counting + +Queue management (qmail-send): +* instant handling of messages added to queue +* parallelism limit (control/concurrencyremote, control/concurrencylocal) +* split queue directory---no slowdown when queue gets big +* quadratic retry schedule---old messages tried less often +* independent message retry schedules +* automatic safe queueing---no loss of mail if system crashes +* automatic per-recipient checkpointing +* automatic queue cleanups (qmail-clean) +* queue viewing (qmail-qread) +* detailed delivery statistics (qmailanalog, available separately) + +Bounces (qmail-send): +* QSBMF bounce messages---both machine-readable and human-readable +* HCMSSC support---language-independent RFC 1893 error codes +* double bounces sent to postmaster + +Routing by domain (qmail-send): +* any number of names for local host (control/locals) +* any number of virtual domains (control/virtualdomains) +* domain wildcards (control/virtualdomains) +* configurable percent hack support (control/percenthack) +* UUCP hook + +SMTP delivery (qmail-remote): +* RFC 821, RFC 974, RFC 1123 +* 8-bit clean +* automatic downed host backoffs +* artificial routing---smarthost, localnet, mailertable (control/smtproutes) +* per-buffer timeouts +* passive SMTP queue---perfect for SLIP/PPP (serialmail, available separately) + +Forwarding and mailing lists (qmail-local): +* address wildcards (.qmail-default, .qmail-foo-default, etc.) +* sendmail/smail /etc/aliases compatibility (qmsmac, available separately) +* mailing list owners---automatically divert bounces and vacation messages +* VERPs---automatic recipient identification for mailing list bounces +* Delivered-To---automatic loop prevention, even across hosts +* automatic subscription management (qlist) + +Local delivery (qmail-local): +* user-controlled address hierarchy---fred controls fred-anything +* mbox delivery +* reliable NFS delivery (maildir) +* user-controlled program delivery: procmail etc. (qmail-command) +* optional new-mail notification (qbiff) +* optional NRUDT return receipts (qreceipt) +* conditional filtering (condredirect) + +POP3 service (qmail-popup, qmail-pop3d): +* RFC 1939 +* UIDL support +* TOP support +* APOP hook +* modular password checking (checkpassword, available separately) diff --git a/BLURB4 b/BLURB4 @@ -0,0 +1,44 @@ +qmail's modular, lightweight design and sensible queue management make +it the fastest available message transfer agent. Here's how it stacks up +against the competition in five different speed measurements. + +* Scheduling: I sent a message to 8192 ``trash'' recipients on my home +machine. All the deliveries were done in a mere 78 seconds---a rate of +over 9 MILLION deliveries a day! Compare this to the speed advertised +for Zmailer's scheduling: 1.1 million deliveries a day on a +SparcStation-10/50. (My home machine is a 16MB Pentium-100 under BSD/OS, +with the default qmail configuration. qmail's logs were piped through +accustamp and written to disk as usual.) + +* Local mailing lists: When qmail is delivering a message to a mailbox, +it physically writes the message to disk before it announces success--- +that way, mail doesn't get lost if the power goes out. I tried sending a +message to 1024 local mailboxes on the same disk on my home machine; all +the deliveries were done in 25.5 seconds. That's more than 3.4 MILLION +deliveries a day! Sending 1024 copies to a _single_ mailbox was just as +fast. Compare these figures to Zmailer's advertised rate for throwing +recipients away without even delivering the message---only 0.48 million +per day on the SparcStation. + +* Mailing lists with remote recipients: qmail uses the same delivery +strategy that makes LSOFT's LSMTP so fast for outgoing mailing lists--- +you choose how many parallel SMTP connections you want to run, and qmail +runs exactly that many. Of course, performance varies depending on how +far away your recipients are. The advantage of qmail over other packages +is its smallness: for example, one Linux user is running 60 simultaneous +connections, without swapping, on a machine with just 16MB of memory! + +* Separate local messages: What LSOFT doesn't tell you about LSMTP is +how many _separate_ messages it can handle in a day. Does it get bogged +down as the queue fills up? On my home machine, I disabled qmail's +deliveries and then sent 5000 separate messages to one recipient. The +messages were all safely written to the queue disk in 23 minutes, with +no slowdown as the queue filled up. After I reenabled deliveries, all +the messages were delivered to the recipient's mailbox in under 12 +minutes. End-to-end rate: more than 200000 individual messages a day! + +* Overall performance: What really matters is how well qmail performs +with your mail load. Red Hat Software found one day that their mail hub, +a 48MB Pentium running sendmail 8.7, was running out of steam at 70000 +messages a day. They shifted the load to qmail---on a _smaller_ machine, +a 16MB 486/66---and now they're doing fine. diff --git a/CHANGES b/CHANGES @@ -0,0 +1,1204 @@ +19970415 version: qmail 1.01. +19970414 doc: tightened up qmail-upgrade.7. +19970414 code: rewrote rewrite(). +19970414 code: implemented recipientmap. suggested by RDM. +19970414 doc: auto-configured qmail home directory in qmail-control.5, + qmail-newu.8, qmail-pw2u.8, qmail-start.8, qmail-users.5. +19970414 port: Solaris needs socket libs for gethostname. impact: can't + compile under Solaris. fix: use socket.lib for qmail-local. +19970412 code: introduced stralloc_starts. +19970412 code: introduced str_equal. +19970412 code: introduced str_start. +19970412 code: introduced byte_equal. +19970412 code: made an optional aliasempty arg for qmail-start. +19970412 code: made an aliasempty arg for qmail-lspawn. +19970412 code: changed ALIAS_EMPTY to an arg for qmail-local. +19970412 port: UnixWare returns >0 for SIOCGIFCONF. impact: ipme fails + under UnixWare. fix: check for >=0, not =0. tnx JD. +19970412 port: DGUX does not have ranlib. impact: can't compile under + DGUX. fix: added dgux line to make-makelib. tnx HWM. +19970412 code: changed maildir library to skip any filename beginning + with dot. tnx SP. +19970412 doc: added FAQ entry about aliases with dots. +19970412 doc: clarified in qmail-inject.8 that default envelope sender + is the same as _default_ From address. +19970411 code: renamed qmail-makectl as qmail-config. +19970411 code: renamed qmail-alias as qmail-local. +19970411 code: switched from signal library to sig library. +19970411 code: switched from qqtalk library to qmail library. +19970411 code: switched from getline library to getln library. +19970411 code: massive library cleanups. +19970411 code: revamped autoconfiguration system. +19970411 code: revamped configuration interface. +19970411 code: eliminated qmail-home. +19970411 code: eliminated tokenize. +19970220 qmail 1.00. +19970219 change: various documentation tweaks. +19970218 change: updated THOUGHTS. +19970218 change: talked about SPAWN_NUMD in FAQ. tnx EC. +19970210 change: noted in maildir.5 that readers should skip any name + starting with a dot. tnx SP. +19970209 change: added note to splogger.8 about reliability. tnx BT. +19970209 change: added section to FAQ on slow sendmail switch. tnx BT. +19970206 change: added section to FAQ about dtcm. tnx PJG. +19970206 change: tweaked maildir.5. +19970201 change: added MH spost note to FAQ. tnx TU. +19970131 change: reorganized FAQ. +19970131 change: added web references to FAQ. +19970124 change: tweaked qmail-upgrade man page. +19970120 qmail 0.96, gamma. +19970120 change: removed various try* in auto-configuration. +19970120 bug: qmail-inject fails to quote argument addresses. impact: + addresses containing special characters could be misinterpreted + or rejected. tnx C2F. fix: use quote2(). +19970120 portability problem: ESIX puts syslog() and openlog() into + -lgen. impact: can't compile under ESIX. fix: put -lgen into + LIBS for unix_sv. tnx RN. +19961221 qmail 0.95, gamma. +19961218 change: added various try* to TARGETS. tnx SA. +19961216 change: clarified in qmail-send.8 that virtualdomains does not + apply to domains listed in locals. +19961216 change: slurpclose() now closes fd on out-of-memory. makes it + more widely applicable. +19961215 change: replaced elm instructions in INSTALL.mbox with an + explanation of what source change to make. tnx AB. +19961212 portability problem: under NEWS-OS, time_t needs sys/types.h. + impact: couldn't compile under NEWS-OS. fix: include + sys/types.h in predate.c. tnx TU. +19961211 change: used timeoutread, timeoutwrite in remoteinfo(). tnx + REB. +19961210 portability problem: apparently some SGIs produce a systype of + irix64. impact: couldn't compile on those systems. fix: handle + irix64 in make-cmds. tnx M3S. +19961208 change: added note to maildir2mbox.1 about mbox locking. +19961208 qmail 0.94, gamma. +19961207 change: added QMAILDEFAULTDOMAIN, QMAILDEFAULTHOST, + QMAILIDHOST, QMAILPLUSDOMAIN. tnx BTW. +19961206 cleanup: readsubdir() protects itself against name overflow, + rather than depending on caller. +19961204 change: changed FAQ 7.3 to prohibit fixup relaying. +19961203 change: added note to FAQ about possibly having to put a space + before "$SENDER" for uux. tnx FW. +19961202 change: added FAQ entry on QUEUE_EXTRA. +19961202 change: added FAQ entry on backups. tnx DP. +19961202 change: added note to INSTALL.mbox about qpopper. tnx VV. +19961201 change: replaced logger with splogger in FAQ 5.1. tnx FPL. +19961201 change: used netmask example for tcpcontrol in FAQ. tnx FPL. +19961201 change: added note to README about the mailing list. tnx FPL. +19961201 change: documented rcpthosts wildcards. tnx RN. +19961201 change: added note to FAQ about making mailx use datemail. +19961201 change: added datemail. function requested by several people; + approach suggested by TG. +19961201 change: added predate. +19961129 change: added QUEUE_EXTRA, QUEUE_EXTRALEN. +19961129 change: qmail-remote bounces messages with partial final lines. +19961129 change: added atomcheck() to quote crappy atoms. +19961129 change: revised atomok() to let atoms deal with more crap. +19961127 change: qmail-send adds note to deferral if flagdying. tnx TG. +19961127 change: split off maildirbounce, maildir2qmtp, and maildir2smtp + into a separate serialmail package. +19961126 change: eliminated beta success reports from README. +19961124 change: forced res_query() return value to fit inside incoming + buffer size. allegedly there are buggy versions of res_query() + that don't guarantee this. +19961122 qmail 0.93, gamma. +19961122 change: allowed empty arg list in forward. +19961121 change: qmail-smtpd now uses unknown (like qmail-qmtpd) rather + than dying if environment variables are not set. +19961121 cleanup: reorganized helo handling in qmail-smtpd.c. +19961121 cleanup: eliminated newfield_rec. +19961121 cleanup: introduced DATE822FMT. used it in received.c. +19961121 cleanup: introduced received.c. used it in qmail-qmtpd.c, + qmail-smtpd.c. +19961121 change: qmail-smtpd now generates a new timestamp for each + message. tnx PJG. +19961121 cleanup: used stralloc in newfield. +19961121 cleanup: eliminated newfield_cc. +19961121 change: eliminated hfield_mort(). +19961119 change: added 2-minute damper on tcpto. +19961118 change: wrote defaults for readfile controls in showctl. +19961117 change: control_readfile() now allows comments and blank lines. + tnx LW. +19961117 change: qmail-start sets logger gid to GID_NOFILES. +19961117 bug: ipme_init() uses a fixed-length buffer for SIOCGIFCONF. + impact: qmail-smtpd and qmail-remote will die if there are too + many local IP addresses. tnx MD. fix: ipme_init() now + dynamically allocates space, up to 200000 bytes, as long as + SIOCGIFCONF keeps failing. note that this is a very widespread + bug; it's in amd, exim, mrouted, named, nntpd, rarpd, sendmail, + tcpdump, timed, xntpd, and probably dozens more programs. +19961117 portability problem: on BSD 4.4 and various other systems, + SIOCGIFCONF will truncate long lists and return success. + impact: on those systems, qmail-smtpd and qmail-remote will + miss some local IP addresses if there are too many. fix: + ipme_init() now checks whether there is enough space left in + the buffer for another ifreq, plus 64 bytes JIC. yuck. +19961117 change: ipmeprint now flushes only at end. +19961117 cleanup: introduced subfdinsmall. used it in qmail-clean.c, + qmail-qmtpd.c. +19961117 cleanup: introduced subfdoutsmall. used it in hostname.c, + printbreak.c, printnumd.c, printsplit.c, qmail-alias.c, + qmail-clean.c, qmail-getpw.c, qmail-qmtpd.c, maildir2smtp.c, + maildir2qmtp.c. +19961117 change: moved subfderr buf up to 256 characters. +19961117 change: added maildirbounce. tnx MD, TG. +19961116 change: maildir2smtp and maildir2qmtp now print filenames for + permanent bounces. tnx MD. +19961116 change: in SECURITY, ``eleven most recent sendmail security + holes, five'' -> ``twelve most recent sendmail security holes, + six.'' +19961116 change: rewrote qmail-showctl more professionally. +19961115 change: added several tests to find-systype.sh. this will + affect many systypes. +19961115 change: qmail-alias now treats most exit codes as soft errors. +19961115 change: revamped exit codes everywhere for 0, 100, 111. +19961114 change: added splogger. +19961114 portability problem: Sun's cc recognizes sqrt() as a builtin, + even if math.h is not included and sqrt is defined statically. + yuck. impact: when qmail is compiled with Sun's cc, next-retry + times are all screwed up. tnx PJG. fix: my sqrt() is now called + squareroot(). +19961114 change: dns_ip() now recognizes [1.2.3.4]. tnx DS. +19961112 change: enabled x option in sendmail. tnx DS. +19961111 change: added SIGHUP handling to qmail-send. suggested by many + people. +19961111 bug: control routines returned incorrect codes for some + out-of-memory conditions. impact: none; conditions cannot + happen with sane control files. fix: return -1. +19961111 change: added SIGALRM handling to qmail-send. suggested by many + people. +19961111 change: eliminated flagnobreak (-b/-B) from qmail-pw2u. +19961111 change: qmail-getpw now allows hyphens inside usernames. +19961111 change: added users/append to qmail-pw2u. tnx G2A. +19961111 change: added badmailfrom to qmail-smtpd. requested by several + people. +19961110 change: replaced elm instructions in INSTALL.mbox with a simple + note to set incomingfolders in elm.rc. tnx AB. +19961110 change: replaced ``owner hack'' with ``variable envelope + return paths'' throughout the documentation. tnx DS. +19961110 change: qmail-setup installs man pages as well as cat pages. +19961110 change: renamed qmail-newuser as qmail-newu. tnx G2A. +19961110 change: renamed qmail-pw2user as qmail-pw2u. tnx G2A. +19961105 change: set path in INSTALL.boot. tnx TJH. +19961105 change: noted in qmail-smtpd.8 that addresses without @ are + always allowed through. +19961105 change: indicated at various spots in FAQ that rcpthosts has to + be updated. suggested by various people. +19961105 change: indicated at various spots in FAQ that qmail has to be + restarted. suggested by various people. +19961029 change: fixed typo in maildir2qmtp.1. tnx BG. +19961026 qmail 0.92, gamma. +19961026 bug: qmail-getpw did not 0-terminate usernames. tnx CF. impact: + qmail-getpw would crash on some systems, deferring local + deliveries. fix: 0-terminate. +19961025 cleanup: renamed auto-hassgprm.h to hassgprm.h. +19961025 cleanup: renamed auto-hassgact.h to hassgact.h. +19961024 change: replaced qmail-alias.0 with dot-qmail.0 in + INSTALL.alias. tnx MW. +19961022 change: switched uids as early as possible in qmail-start.c. +19961022 change: in SECURITY, ``ten most recent sendmail security + holes, five'' -> ``eleven most recent sendmail security holes, + five.'' +19961022 change: quote_need() now treats non-ASCII characters the same + way as control characters. +19961022 change: added version and home page to qmail.7. +19961022 cleanup: introduced slurpclose.c. used it in qmail-alias.c, + qmail-lspawn.c. +19961021 portability problem: AT&T NCR boxes need stdio.h before + arpa/nameser.h. impact: dns.c would not compile. fix: include + stdio.h. tnx HS. +19961021 change: added AIX section to INSTALL.ids. tnx SSB. +19961021 change: added qmail-pw2user. +19961020 change: added qmail-pw2user.8. +19961020 change: qmail-alias now dies soft on EACCES/EPERM for .qmail. +19961020 change: eliminated root comments from INSTALL.qsmhook. +19961020 change: various improvements in FAQ. +19961017 change: added QLX_ROOT. +19961017 change: renamed hosts in FAQ. tnx SLB. +19961017 change: in dot-qmail.5, documented envnoathost effects. tnx RN. +19961017 change: revamped addresses.5. +19961017 change: added stripvdomprepend() for better bounces. tnx PT. +19961012 portability problem: under HP-UX 9, can't setgroups() to 65537. + impact: couldn't compile under HP-UX 9. fix: use 0 instead of + 65537 in chkshsgr.c. tnx HHO. +19961008 change: added several qlx codes. +19961008 cleanup: eliminated qlx from qmail-alias. +19961008 change: qmail-lspawn runs qmail-getpw as UID_PW. +19961008 change: added qmail-newuser. +19961008 change: added cdb support to qmail-lspawn. +19961008 change: integrated cdb. +19961007 change: added qmail-users.5. +19961007 change: eliminated usermap. +19961007 cleanup: switched execvp to execv in sendmail, qmail-lspawn. +19961007 change: used qmail-getpw in qmail-lspawn. +19961007 change: renamed LSPAWN_USERLEN as GETPW_USERLEN. +19961007 change: added qmail-getpw. +19961007 change: created users subdirectory of CONF_HOME. +19961007 change: fixed typo in FAQ. tnx J1B. +19961006 change: replaced subfdout with a small ss in qmail-alias. +19961006 change: reduced qmail-alias buffer sizes to 1024. +19961003 change: added note to maildir2smtp.0 about maildirmake. tnx SM. +19961003 bug: if ipme_init() returned -1, qmail-remote would continue, + blindly assuming that all addresses are local. impact: on + systems with too many aliases, all remote deliveries fail. tnx + MD. fix: qmail-remote now dies with temp_oserr() on any result + other than 1. +19961003 portability problem: all pre-4.9.4 versions of bind barf, + badly, on CNAME queries to lame servers. what a crappy system. + even if the resolver doesn't barf, the next name server down + the line may barf. impact: qmail can't get mail through to + domains that are (1) lame and (2) running old versions of bind. + fix: never, ever, do a CNAME query. dns_cname() now does an ANY + query instead. this, like sendmail's analogous procedure, is + unreliable when a CNAME is mixed with other records. +19961001 cleanup: switched to libfd in qmail-start.c. +19960929 cleanup: renamed auto-hasmkffo.h to hasmkffo.h. +19960928 cleanup: reorganized qmail-start.c. +19960928 cleanup: used libfd in preline.c, qmail-lspawn.c, + qmail-popup.c, qmail-rspawn.c, qmail-start.c, qqtalk.c, + qsmhook.c. +19960928 cleanup: added libfd. +19960927 change: in SECURITY, ``nine most recent sendmail security + holes, four'' -> ``ten most recent sendmail security holes, + five.'' +19960926 change: added tcpcontrol notes to FAQ. +19960926 change: qmail-smtpd now immediately closes connection, with a + warning message dedicated to Solaris, if stray newlines show up + in the incoming data. +19960926 change: added INSTALL.boot. +19960926 portability problem: on systems that can handle IP interface + aliases (i.e., on sa_len systems), SIOCGIFADDR returns the + primary address for an alias. impact: ipme_init() did not + include alias addresses. fix: ipme_init() avoids SIOCGIFADDR on + sa_len systems; on these systems, the address we want is + already in ifr. tnx DM. +19960926 change: qmail-alias kills itself if locking takes longer than + 30 seconds. +19960926 change: qmail-pop3d no longer moves messages. tnx RS. +19960924 change: added note to FAQ about descriptors limit. tnx RD. +19960922 change: open_trunc() now uses 644. +19960922 change: qmail-setup now does umask(077). +19960922 change: maildir2mbox now does umask(077). +19960922 change: moved subfderr buf up to 64 characters. +19960920 change: in SECURITY, ``eight most recent sendmail security + holes, three'' -> ``nine most recent sendmail security holes, + four.'' +19960920 portability problem: init run commands are subject to job + control signals under more systems than HP-UX. impact: on some + systems (e.g., Solaris), qmail daemons would be killed. fix: + INSTALL now tells everybody to use csh -cf. +19960920 change: added queue-run section to FAQ. +19960920 change: in pine-crashing question in FAQ, added -oem and -oi, + so that change will work with the real sendmail too. +19960919 change: added CNAME section to FAQ. tnx to various people. +19960919 change: eliminated QQX_EXECHARD and QQT_EXECHARD. this means + that all qmail-queue invocation failures are now soft, even + things like EPERM. +19960919 change: replaced ``No such address'' with ``Sorry, no mailbox + here by that name.'' tnx G2A. +19960919 change: qmail-remote now includes host name in no-such-host + messages. tnx G2A. +19960919 change: replaced ``Temporarily unable to canonicalize address'' + with ``CNAME lookup failed temporarily.'' +19960918 change: improved an error message in qmail-alias.c. tnx TG. +19960918 change: added SHELL=/bin/sh to Makefile. tnx JL. +19960916 change: reorganized INSTALL.ids a bit. +19960916 change: ``from smtpd'' is now ``from network''. +19960916 change: SMTPD is now DAEMON. +19960916 change: qmail-start sets logger uid to UID_LOG. tnx JLH. +19960916 change: added CONF_USERL. +19960916 change: iaafmt() now puts a dot on in-addr.arpa. +19960915 change: added UPGRADE. suggested by several people. +19960915 change: added qsutil error messages to qmail-log.5. +19960915 change: qsutil error messages are now alerts. +19960915 portability problem: on some systems, logger appears to use + syslog(pri,buf) instead of syslog(pri,"%s",buf). tnx JC. + impact: logger could barf or crash if fed messages containing + %. an attacker could easily cause a crash, eliminating qmail's + logs. fix: % is no longer considered safe for logs. +19960912 cleanup: split seek.c into seek_*.c. +19960912 cleanup: replaced seek_to() with seek_set(). +19960912 cleanup: introduced libseek.a. +19960907 cleanup: split case.c into case_*.c. +19960907 cleanup: introduced libcase.a. +19960907 cleanup: split wait.c into wait_*.c. +19960907 cleanup: introduced libwait.a. +19960907 cleanup: renamed auto-haswaitp.h to haswaitp.h. +19960907 cleanup: split open.c into open_*.c. +19960907 cleanup: introduced libopen.a. +19960904 change: added generic pointer to qmail-control.5. tnx HW. +19960904 change: rewrote rcpthosts section in FAQ. tnx HW. +19960904 change: added organization section to FAQ. tnx HW. +19960902 qmail 0.91, gamma. +19960902 change: control_readfile() can now handle partial lines. tnx + JDHB. +19960902 change: eliminated non-fqdn note from FAQ. next version of + tcpserver will use DNS directly. +19960902 change: qlist now uses NEWSENDER, not SENDER. +19960902 change: qmail-pop3d no longer obtains a lock. tnx RS. +19960902 change: put }g on all seds in Makefile. +19960831 change: noted in qmail-control.5 that comments are not allowed + in control files. tnx J2B. +19960829 change: used double union in alloc.c. tnx ME. +19960829 change: replaced semicolon with colon for smtproutes port. +19960829 change: in INSTALL, put make man just before make setup. +19960829 change: changed a few qmail messages into alerts. +19960829 cleanup: renamed datetime_gmt as datetime_tai. +19960829 change: added note to UUCP question that some UUCP software + doesn't want preline -f. tnx SB. +19960829 change: added question 2.4 to FAQ on SLIP/PPP. +19960828 change: replaced owner- with owner-@host-@[] in qmail-inject. +19690828 change: replaced owner- with owner-@host-@[] in qmail-alias. +19960828 change: replaced owner- with owner-@host-@[] in injectbounce(). +19960828 change: replaced owner- with owner-@host-@[] in senderadd() for + owner hack. +19960828 change: qmail-inject -n now prints Return-Path. +19960825 cleanup: revised ending code in token_addrlist(). +19960825 change: tokenize now uses linelen 0 for unparse. +19960825 change: if linelen is 0 in token822_unparse, no length limit. +19960825 change: added LINELEN macro to qmail-inject for unparse. +19960825 change: token822_unparse now takes linelen argument. (leaving + two spaces on the right before linelen.) +19960824 cleanup: renamed token as token822. +19960822 portability problem: under NEWS-OS, /bin/mail and /usr/ucb/mail + invoke sendmail with -E and -J options. tnx TU. impact: + couldn't send mail with those programs. fix: accept opts, + including _optional_ args. ugh. +19960821 change: sendmail now quits if invoked as newaliases. tnx TU. +19960821 portability problem: under NEWS-OS, dirent.h needs sys/types.h. + tnx TU. this POSIX violation also appears in some versions of + FreeBSD. impact: couldn't compile under NEWS-OS. fix: include + sys/types.h in direntry.h* and trydrent.c. [sigh] +19960821 change: added concurrencyremote question to FAQ. +19960821 change: added chkspawn. +19960821 change: moved default SPAWN_NUMD up to 120. +19960818 change: allowed ;port in smtproutes. tnx AL. +19960818 change: introduced port in qmail-remote.c. +19960818 change: qmail-queue records qp in Received lines. 2 lines of + code. tnx ME. +19960818 change: in SECURITY, ``seven most recent sendmail security + holes'' -> ``eight most recent sendmail security holes.'' +19960818 change: qmail-pop3d now appends an extra blank line to every + message, for compatibility with popper. some clients can't + deal with the right thing, unfortunately. tnx FPL. +19960818 change: added qmail-tcpto. +19960818 change: eliminated cc -posix for NeXTs. tnx SA. +19960818 change: eliminated loadfifo. tnx SA. +19960818 change: integrated auto-configured fifo.c code from SA. +19960817 change: put SYSDEPS into a more reasonable order. +19960813 change: indicated possibility of duplication when qmail-remote + gets a dead connection after DATA. tnx ME. +19960813 change: documented qmail-inject environment variables. +19960813 change: supported per-recipient owner hack in qmail-inject. +19960813 change: supported per-message owner hack in qmail-inject. +19960813 change: introduced hackedruser into qmail-inject. +19960813 change: introduced QMAILRUSER, QMAILRHOST. +19960812 change: added QMAILINJECT option to allow address-comment form. +19960812 change: made name-address form the default in qmail-inject. +19960812 change: added QMAILINJECT options f and m to delete From and + Message-ID on input. tnx LL. +19960812 change: added QMAILINJECT environment variable. +19960812 change: added QMAILHOST, QMAILUSER, QMAILNAME to override + MAILHOST, MAILUSER, MAILNAME. tnx MG. +19960812 change: added qmail-showctl. +19960812 portability problem: under Solaris 2.4 and possibly other + systems, the linker does not give generic alignment to an array + of 4096 chars. tnx JP. impact: some subset of the programs + would (reliably) die with a bus error; in the Solaris case, + maildir2mbox. fix: redefine space in alloc.c to be aligned. +19960812 change: qmail-remote no longer does CNAME lookups if there's an + artificial SMTP route. tnx ME. +19960812 change: added flagcname arg to addrmangle() in qmail-remote. +19960812 cleanup: moved host/relayhost processing earlier in + qmail-remote. +19960812 change: qmail-remote stops before DATA if no RCPTs were + successful. tnx JLH. +19960812 change: rewrote rcpthosts explanation in FAQ. +19960811 change: added qmail-log.5. +19960811 change: introduced ALIAS_PATERNALISM. configurability requested + by several people. +19960811 change: eliminated go-writability test for qmeox(). the alleged + value of paternalism is nonexistent if nobody even notices + you're doing it. +19960811 change: in qbiff, changed no-/-allowed to no-/-at-beginning, + no-dots-allowed, must-be-nonempty. tnx MD. +19960811 change: in mbox.5, discouraged mail readers from looking for + From_ lines only after blank lines. too much crap in the world. +19960811 change: added subject line to qreceipt success notices. +19960811 change: added subject line to qmail-send bounce messages. +19960811 change: qmail-alias now expects dash arg. this finally gives + lspawn complete control over the local -> ~user/.qmail-ext map. +19960811 change: qmail-lspawn passes dash arg to qmail-alias. +19960811 change: reorganized qlist acknowledgment format. again. +19960811 change: documented EXT, EXT2, EXT3, EXT4. tnx BB. +19960810 change: qmail-makectl now copies locals to rcpthosts. should be + a better default. suggested by TK. +19960805 portability problem: new makefile generator put in tabs again. + sigh. impact: couldn't compile under some systems. fix: same as + before. tnx TG. +19960804 change: added tcpserver instructions to FAQ. +19960804 change: reorganized FAQ server instructions into a new section. +19960801 qmail 0.90, gamma. +19960801 change: qmail-qmtpd now supports rcpthosts, RELAYCLIENT. +19960731 change: default NUMD is now 29. this prepares for weird systems + where getpwnam() needs more than one descriptor (but the + descriptor limit is still 64! ... you never know), and for + possible future getpwnam() replacements. +19960731 change: popped subfderr buffer up to 32 characters. made sure + that everybody flushed subfderr as necessary. +19960731 change: maildir2qmtp now prints filenames and responses. +19960731 change: maildir2smtp now prints filenames it's trying and + relevant portion of SMTP responses. +19960731 change: used succwrite() in maildir2smtp, maildir2qmtp. + simplifies code quite a bit. +19960731 change: qmail-remote's blast() checks sooner for write errors. +19960731 change: added better -e option to sendmail. tnx TG. +19960731 change: added maildir2qmtp. +19960730 cleanup: eliminated die_nomem() in maildir2smtp.c. +19960730 change: dns_cname now pretends that "foo." is a CNAME for "foo" + to give the desired behavior for people who misuse DNS and + violate RFC 822. tnx RN. +19960730 change: dns_cname now tests for empty names and ] on every + loop. +19960730 change: used LSPAWN_BREAK in qmail-send.c for usermap. +19960730 change: updated header example in qmail-header.5. +19960730 change: added printbreak. auto-configured BREAK in dot-qmail.5, + qmail-lspawn.7, qmail-send.8, qmail-upgrade.7, qlist2. +19960730 change: added printnumd. auto-configured NUMD in qmail-send.8, + qmail-limits.8. +19960730 change: added printsplit. auto-configured split in qmail-upq. +19960730 change: added dot-qmail.5. +19960730 change: qmail-smtpd now treats HELO as including RSET. +19960730 change: added moreinfo to qlist usage message. +19960729 change: improved an error message in qmail-alias. +19960729 cleanup: merged qmeox(), qmeodx(). +19960729 bug: failure to stat .qmail-owner was not an error. impact: if + stat failed temporarily (e.g., because of NFS), .qmail-owner + would be incorrectly ignored, so outgoing message would have + wrong envelope sender. fix: qmail-alias does temp_nfsqmail() if + stat() returns a temporary error. +19960729 change: added RFCOWNER. +19960729 change: added qmtpd setup question to FAQ. +19960729 change: added qmail-qmtpd. +19960728 change: revamped maildir2smtp error messages. +19960728 change: revamped maildirwatch error messages. +19960728 change: revamped maildir2mbox error messages. +19960728 change: used strerr in maildir_scan(). +19960728 change: used strerr in maildir_chdir(). +19960728 change: introduced strerr. +19960728 bug: the new tcp-env tried to read from an ndelay socket. + impact: TCPREMOTEINFO would always be empty. fix: unset ndelay + in remoteinfo.c. +19960728 bug: if maildir2smtp saw a permanent failure after MAIL, it + failed to do RSET. impact: all further messages would be + rejected at the MAIL stage. fix: maildir2smtp now always does + RSET. tnx JW. +19960728 cleanup: qmail-alias now applies lowercase and dot-to-colon + conversion directly to dashext, leaving everything else alone. + this works since all .qmail access is factored through dashext. +19960727 portability problem: under NeXTStep, -posix is almost entirely + broken. impact: qmail daemons would dump core under NeXTStep. + fix: turn off -posix, except for loading qmail-setup, which + needs mkfifo(); NeXT, bless them, didn't put mkfifo() into the + C library where it belongs. this requires a new make command, + namely loadfifo. +19960727 change: all characters 33-126 are now considered safe for logs. + tnx MD. +19960727 cleanup: eliminated qp variable from mailforward(). +19960727 cleanup: maildirwatch.c includes headerbody.h. +19960727 cleanup: eliminated match from maildirwatch.c. +19960727 cleanup: eliminated code variable from maildir2smtp.c:doit(). +19960727 cleanup: maildir2smtp.c includes scan.h. +19960727 cleanup: maildir.c includes str.h. +19960727 cleanup: qmail-popup.c now includes exit.h. +19960727 cleanup: qmail-pop3d.c now includes exit.h. +19960727 cleanup: eliminated path from qmail-start.c. +19960727 cleanup: eliminated birthplusnn from nextretry(). +19960727 cleanup: eliminated r from timeoutconn(). +19960727 cleanup: tcpto.c now includes byte.h. +19960727 cleanup: spawn.c now declares initialize(). +19960727 cleanup: qmail-lspawn.c now includes str.h, byte.h. +19960727 cleanup: qmail-inject.c now includes quote.h. +19960727 change: qmail-check now checks separately for group + readability and other readability. +19960727 bug: maildir2smtp didn't check flagehlo in PIPELINING parsing. + impact: a server that said PIPELINING at any point, not just + EHLO, would receive pipelined data. fix: check flagehlo. +19960727 bug: readsubdir was calling pause(). impact: if a subdirectory + was removed, qmail-send would hang. fix: use rs->pause(). +19960727 change: used error_str in qmail-qread. +19960727 change: qmail-qread now looks for local/remote open errors. +19960727 cleanup: added warn() in qmail-qread.c. +19960727 change: qmail-qread now exits 111 for temporary errors. +19960727 change: used error_str in qmail-setup. +19960727 change: introduced error_str. +19960727 change: replaced qmail-check with make check in INSTALL. +19960727 change: added check target to Makefile. +19960727 change: replaced qmail-setup with make setup in INSTALL. +19960727 change: indirected fake targets through do- targets. +19960727 change: added setup target to Makefile. +19960727 change: qmail-makectl now makes sure that defaultdomain has + at least one dot. e.g., enteract.com -> enteract.com, not com. +19960726 bug: quote() failed to quote commas. impact: addresses + containing commas would not have been quoted correctly for + Return-Path or for SMTP MAIL FROM. fix: quote commas. +19960726 change: sendmail now mentions qmail-qread, not qmail-mailq. +19960726 change: qmail-alias now expects ext arg. this eliminates + LSPAWN_BREAK from qmail-alias and gives lspawn almost complete + control over the local -> ~user/.qmail-ext transformation. the + exception is that qmail-alias always uses ~user/.qmail, + ignoring ext, if local is the same as user. +19960726 change: qmail-lspawn passes ext to qmail-alias. +19960726 change: alloc() now uses up a 4K space before calling malloc(). +19960726 change: ipalloc allocation base is now 10. 100 was silly. +19960726 change: stralloc allocation base is now 30. +19960726 change: injectbounce() now supports the owner hack. +19960726 change: qmail-smtpd no longer requires HELO. tnx K1J. +19960726 cleanup: replaced makereceived() with dohelo(). +19960726 change: qmail-smtpd is back to 555 for syntax errors. +19960725 change: qmail-alias now supports the owner hack. tnx to RN for + prodding me to look at this problem. +19960725 change: senderadd() now supports the owner hack. +19960725 cleanup: split off senderadd(). +19960725 change: added pine-crashing note to FAQ. +19960725 change: added procmail config.h note to INSTALL.mbox. +19960725 change: added elm TMPDIR note to INSTALL.mbox. +19960725 change: added pine.conf note to INSTALL.mbox. +19960724 change: added fixup note to FAQ. +19960724 change: qmail-inject now exits 111 for temporary errors. +19960724 change: qmail-smtpd now appends RELAYCLIENT to incoming + recipient domain names. +19960724 cleanup: moved relayclient out of qmail-smtpd's addrallowed() + into caller. +19960724 change: added rcpthosts wildcards. +19960724 change: added clean target to Makefile. +19960723 change: added virtualdomains exceptions. +19960722 change: added BLURB4. +19960722 change: added BLURB3. +19960722 change: eliminated smarthost and localnet. +19960722 change: incorporated relaymap, contributed by LW. renamed it + as smtproutes. +19960722 change: qmail-popup now supports APOP. suggested by BG, who + distributed similar changes. +19960722 change: qmail-popup now sends APOP timestamp to checkpassword. +19960722 cleanup: in qmail-popup, split off doanddie(). +19960722 change: qmail-popup now prints APOP timestamp in banner. +19960722 change: added hostname argument to qmail-popup. +19960722 cleanup: in qmail-popup, split out() into out(), outflush(). +19960722 cleanup: in qmail-popup, introduced pop3_greet(). +19960721 portability problem: under Unisys SVR4, hostname is not in the + usual path. impact: qmail-makectl fails. fix: added hostname + command here, used it in qmail-makectl. +19960721 portability problem: on some sysctl-based systems, apparently + gethostname() doesn't write anything if the output buffer is + too small. it should write a truncated name. impact: if anyone + has a hostname longer than 64 characters, maildirs could get up + to 64 characters of garbage, rather than a truncated hostname. + fix: qmail-alias now does *host = 0 before calling gethostname. +19960721 change: updated FAQ examples from qsmhook to preline. +19960721 change: added preline. +19960721 change: qsmhook now uses signal_init, signal_uninit. +19960721 change: qsmhook now checks specifically for empty args. +19960721 change: documented mbox. +19960721 change: added EXT, EXT2, EXT3, EXT4. +19960721 change: added LAST response to qmail-pop3d, always returning + OK 0. tnx RN. +19960721 change: added qmail home page to README. +19960721 change: added HELP response to qmail-smtpd. tnx RN. +19960720 change: expanded, vertically, the qmail-inject error message + for unparseable header fields. +19960720 change: logo is now dolphin. tnx CEJ. +19960719 qmail 0.76, beta. +19960719 change: used LSPAWN_BREAK in qmail-alias for deciding how to + handle extensions. this should produce better behavior in the + (unsupported) case that LSPAWN_BREAK is not a hyphen. +19960719 bug: qmail-smtpd didn't check for null arg on MAIL, RCPT. + impact: qmail-smtpd would deref 0 and crash. fix: qmail-smtpd + now gives syntax error on null arg. +19960719 change: documented UFLINE in qmail-command.8. tnx TG. +19960718 change: added maildir2smtp. +19960718 cleanup: introduced maildir.c. used it in maildir2mbox.c, + maildirwatch.c. +19960718 change: added maildirwatch. +19960718 cleanup: maildir2mbox now sets up pq2 as it is deleting from + pq, rather than simultaneously with pq. +19960718 change: added H_DELIVEREDTO. +19960718 portability problem: Unisys requires -lsocket -lnsl. impact: + couldn't compile under Unisys. fix: added unix_sv section to + make-cmds.sh. tnx TVP. +19960718 change: added unix_sv section in find-systype. tnx TVP. +19960717 change: qmail-alias now appends newline if .qmail does not end + with a newline. tnx MC. +19960717 change: qmail-alias now defers delivery for a blank line only + if it is the first line of the file. handles user behavior + described by MC of putting many newlines at end of file. +19960717 bug: qmail-inject looked for dots in user part, not just host + part, when deciding whether to use defaultdomain. impact: the + address joe.bloggs@here didn't have defaultdomain added. fix: + qmail-inject now stops at the @. +19960717 change: updated INSTALL.alias to mention qmsmac. +19960717 change: syntax error code for SMTP is now 501. +19960717 change: added -e option to sendmail. tnx TG. +19960716 change: changed ~alias files to .qmail-local, not .qmaillocal. + suggested by many people. +19960716 change: redid qmail-alias/qmail-lspawn interface. +19960716 change: replaced EXTENSION, USEREXT with LOCAL. +19960716 change: qmail-queue now removes intd, mess upon error, as long + as it doesn't time out. suggested by BB et al. +19960716 change: added flagmademess, flagmadeintd to qmail-queue.c. +19960716 cleanup: changed todofd to intdfd in qmail-queue.c. +19960716 cleanup: added cleanup() to qmail-queue.c. +19960716 change: added timeout to tcp-env.c, default 30 seconds. +19960716 change: remoteinfo_get() now uses timeoutconn(). +19960715 change: added procmail config.h note to FAQ. +19960704 change: qmail-upgrade.7 now warns administrators that ~alias + generally doesn't apply to addresses starting with a user name. +19960703 change: added echo \c note to FAQ. tnx PJG. +19960702 change: qmail-smtpd now accepts HELO without an argument. + tnx K1J, J1B. +19960627 change: qmail-lspawn.8 now mentions that qmail-lspawn doesn't + set up supplementary groups. tnx TG. +19960625 portability problem: under Linux, read(,,0) doesn't do proper + error slippage. impact: timeoutconn() would always report + success; if a connection failed, qmail-remote would report a + greeting failure and skip all further MX records. tnx ME. fix: + timeoutconn() now uses getpeername() to check for success. +19960625 change: qmail-smtpd now mentions disk full for QQT_WRITE. +19960625 change: qmail-inject now mentions disk full for QQT_WRITE. +19960622 change: if RELAYCLIENT is set, qmail-smtpd skips rcpthosts. +19960609 change: updated INSTALL for current SMTP responses. +19960607 change: clarified INSTALL.qsmhook examples. tnx S1R. +19960607 change: added subject parsing to qlist.c. tnx RN. +19960607 cleanup: used case_diffb in qlist.c. +19960607 change: added extra log information to INSTALL examples. +19960606 change: added -Pn to uucp line in FAQ. tnx DWS. +19960605 portability problem: under Solaris, /usr/bin/groups incorrectly + reports your groups in /etc/group, rather than the results of + getgroups(). tnx MD, PJG. impact: test #19 in INSTALL fails. + fix: added special note to test #19 (sigh) about Solaris. +19960605 change: improved maildir setup commands in INSTALL.mbox. +19960605 change: on success, qmail-alias logs forwarding qp. 9 lines + extra code. +19960605 change: qmail-send logs qp for bounce. 6 lines extra code. +19960605 change: qmail-smtpd includes qp in its response when it accepts + a message. 7 lines extra code. requested by MD and others. +19960605 change: added qqtalk_qp. +19960605 change: qmail-send now logs uid and qp from todo file. 14 lines + extra code. +19960605 change: qmail-queue now records uid and qp in u and p lines + in todo file. 7 lines extra code. +19960605 change: improved qmail-alias x-bit error messages. +19960605 change: newline in log is now converted to /, not underscore. +19960604 change: when it accepts a message, qmail-smtpd includes the + local time in its 250 response. +19960604 change: on success, qmail-alias prints delivery counts, + file+forward+program. +19960603 change: qmail-remote now reports IP address on success. tnx MD. +19960603 change: qmail-send now logs success and failure reports, not + just deferral reports. +19960603 change: added netbsd section in find-systype, same as bsd.os + section. this will affect netbsd-* systypes. tnx MBS. +19960530 qmail 0.75, beta. +19960528 change: added qmail.7. tnx MD. +19960525 change: added qmail-pop3d. tnx RN. +19960525 change: added qmail-popup. tnx RN. +19960525 change: added elm filter section to FAQ. tnx GB. +19960502 portability problem: on many systems, select() on an + almost-full pipe incorrectly says writable. tnx ME for running + into this and helping track it down. impact: if qmail-send + writes a pipeful to qmail-lspawn or qmail-rspawn before they + can react (because of high concurrency, high load, or long + addresses), it will receive an incorrect -1/EAGAIN, and will + conclude that spawn died. sysadmin will have to restart qmail, + and messages will be duplicated. fix: in qmail-send.c, + busy-loop if write() to spawn returns any error other than + EPIPE. +19960501 bug: qmail-alias treated NAMETOOLONG and NOTDIR as temporary + errors. impact: qmail-alias never looked for -default; even if + mail was destined to bounce, it would have to time out first. + fix: qmail-alias now uses error_temp(). +19960430 bug: qmail-smtpd treated qq crash as permanent error. impact: + if somebody actively kills qq, mail will be incorrectly + bounced. tnx SS. fix: qmail-smtpd now treats only TOOLONG and + EXECHARD as permanent errors. +19960430 cleanup: eliminated QQT_TTY from qqtalk.h. +19960428 change: added ``warning: '' before trouble-marking message. +19960428 change: added percenthack. requested by GB. +19960428 cleanup: switched to auto-generated Makefile. +19960428 cleanup: switched to auto-generated .o dependencies. +19960428 cleanup: eliminated fmt.o, scan.o from Makefile. +19960428 portability problem: under HP-UX 10, the rc pgrp is sent HUP + when rc finishes. tnx BG. impact: the qmail daemons are killed + when rc finishes. fix: added special note in INSTALL (sigh) to + use csh -cf. +19960427 cleanup: added PORT_SMTP in qmail-remote.c. +19960427 cleanup: introduced timeoutwrite.c. used it in qmail-remote.c. +19960427 cleanup: introduced timeoutread.c. used it in qmail-remote.c. +19960427 cleanup: introduced timeoutconn.c. used it in qmail-remote.c. +19960427 change: added timeoutconnect. default: 60 seconds. +19960427 change: added pop3d instructions to FAQ. tnx RN. +19960427 change: eliminated env manipulation from qmail-start. tnx BG. +19960427 change: headerbody now ends header, inserting blank line, if + first line of a header field doesn't pass hfield_valid. tnx TG. +19960427 change: headerbody now prepends MBOX-Line: to any header line + starting From_. this lets qmail-inject work with elm's bounce. + tnx OR, K1J, et al. +19960426 change: added moreinfo arg to qlist and qlist2. +19960426 change: added signal_uncatchchild() to qmail-send.c. tnx BG. + now, if sysadmin sets SIGCHLD to SIG_IGN before invoking + qmail-send [sigh], qmail-send won't screw up bounce messages. +19960426 change: dns_cname now checks whether last character is ], + rather than whether first character is [, for quick return. +19960426 cleanup: glue is now global in dns.c. +19960426 cleanup: qmail-remote no longer does stralloc_0 for host and + canonhost. +19960426 change: dns_mxip no longer rejects [foo].bar. +19960426 change: dns_mxip no longer requires for bracket that input + be 0-terminated. +19960426 change: qmail-start can now take logger as an argument. +19960426 change: qmail-start now invokes qmail-send in foreground (as + parent of other processes). +19960426 change: added mailsubj. tnx GAW. +19960426 portability problem: under some systems, can't lock read-only + file. impact: maildir2mbox would always fail on those systems. + fix: maildir2mbox now opens a separate lock fd. tnx BG. +19960426 cleanup: removed unnecessary #!/bin/sh and # AUTO from mctl.sh. +19960426 change: added qmail-qstat. +19960426 change: added qmail-qread.8. +19960426 change: renamed qmail-mailq as qmail-qread. +19960419 change: qmail-alias now defers delivery rather than skipping + blank lines in .qmail. +19960419 change: in qmail-lspawn.c, lowercased name before getpwnam(). + really getpwnam() should do this, but oh well. +19960419 change: added username to qmail-lspawn.c, with LSPAWN_USERLEN + in conf-unusual.h. names longer than LSPAWN_USERLEN will skip + getpwnam(). +19960419 change: if qlist doesn't see any cmds, it presumes that you + meant to subscribe. +19960419 change: reorganized qlist acknowledgment format. +19960415 change: reorganized and rewrote FAQ. +19960415 change: renamed HOWTO as FAQ. +19960414 change: in qmail-alias, converted extension to lowercase just + before qmeopen(), qmeox() calls. thus EXTENSION and USEREXT and + RECIPIENT will preserve case passed by qmail-lspawn, while + .qmailext lookups will not. +19960414 change: removed case_lowers(r) from qmail-lspawn.c. tnx JLH. +19960414 change: moved extension . -> : conversion to just before + qmeopen(), qmeox() calls in qmail-alias.c. thus EXTENSION and + USEREXT and RECIPIENT will preserve dots. +19960414 change: qsmhook -x now does case-independent comparison. +19960413 change: added procmail instructions to HOWTO. +19960409 bug: qmail-alias does not check for newlines when it generates + Return-Path. impact: resulting Return-Path header field will be + illegal, if sender address contains newline followed by + something other than whitespace. fix: qmail-alias now replaces + newline with underscore in rpline. +19960409 change: added leaf UUCP description to HOWTO. tnx J2K. +19960409 change: added -B option to sendmail. tnx OR. +19960409 change: qlist now makes lists unwritable (after renaming from + .qtemp to .qmail). tnx MLH. +19960409 change: added flagdtline to qsmhook.c, based on -l option. +19960409 change: added PIPELINING declaration to qmail-smtpd. tnx JGM. +19960409 change: qmail-smtpd now flushes output instantly after DATA, + QUIT, HELO, EHLO, NOOP, VRFY, or any 502. +19960409 change: qmail-smtpd now flushes output upon read() and death. +19960409 change: qmail-smtpd no longer flushes output in out(). +19960409 change: increased qmail-smtpd outbuf size from 128 to 512. +19960409 cleanup: in qmail-smtpd, eliminated ssinit() in favor of FDBUF. +19960409 bug: qmail-alias produced aliasfoo-owner rather than foo-owner + as envelope sender for ~alias/.qmailfoo. tnx DS. impact: wrong + envelope sender whenever ~alias/.qmailfoo-owner existed. fix: + qmail-alias now checks for hyphen at beginning of extension. +19960409 change: added _ESMTP to end of 220. tnx JLH. +19960409 change: moved out("\r\n") out of smtp_greet() into callers. + this improves the flushing behavior on 221. +19960328 qmail 0.74, beta. +19960326 change: changed subdirectory split from 32 to 23. +19960326 portability problem: some versions of make don't understand + that a line with just a tab is blank. impact: couldn't compile + under those systems. fix: eliminated extra tab from Makefile. + tnx TG. +19960325 change: added qmail-mailq. +19960325 change: introduced readsubdir. +19960325 change: qmail-setup makes split; qmail-check checks split. +19960325 change: used split in qmail-send, qmail-clean, qmail-queue + for mess, info, local, remote. +19960325 change: fmtqfn now supports split queue subdirectories. +19960325 cleanup: eliminated cat2s(). +19960325 cleanup: introduced fmtqfn.c. used it in qmail-queue.c, + qmail-send.c, qmail-clean.c. +19960325 change: in protocol between qmail-clean and qmail-send, now + using intd/ instead of mess/. +19960325 change: qmail-queue.c and triggerpull.c now work inside queue + subdirectory. +19960325 change: spawn.c now checks whether message is a regular file. +19960325 change: spawn.c now allows slashes in messid except at + beginning. +19960325 cleanup: introduced fnmake_split in qmail-send.c. +19960325 cleanup: eliminated strnum in qmail-send.c in favor of + fnmake_{info,todo,mess,chanaddr} and fnmake2_bounce. +19960325 cleanup: introduced strnum3 in qmail-send.c for the logging + uses of strnum. +19960325 cleanup: in qmail-send.c, getinfo() now takes id argument. +19960325 cleanup: qmail-send.c now preallocates space for fn, fn2. +19960325 change: time zone is now -0000 instead of +0000. encouraging + DRUMS to use this as an i-don't-know-the-local-time indicator. +19960324 change: qmail-rspawn.c now calls tcpto_clean(). +19960324 cleanup: spawn.c now calls initialize(). +19960324 change: qmail-setup makes lock/tcpto; qmail-check checks it. +19960324 change: qmail-remote now quickly skips connect() to a host that + seems to be down. tnx BP for pressuring me to get this done. +19960323 change: in qmail-alias.8, renamed mboxg as mboxrd. tnx RD. + idea was popularized by RD in June 1995. +19960322 cleanup: eliminated subfd_init(). +19960322 change: qbiff now removes the word Subject. +19960322 change: now /bin/true instead of /dev/null in the generic + INSTALL.ids instructions. tnx JPR. +19960322 change: added hfield_skipname(). tnx RN. +19960322 bug: qmail-inject did not check whether USER needed quoting. + impact: if USER had weird characters, the From address would + generally be wrong, unless the user manually set up MAILUSER + with proper quoting. fix: qmail-inject sets up a quoted-string + if necessary. +19960322 cleanup: separated out quote_need() in quote.c. +19960322 cleanup: added stralloc_catb.c. used it in qmail-alias.c, + qmail-send.c. +19960322 change: qmail-send now uses a quadratic retry schedule from + birth of each message. this also eliminates clustering. +19960322 cleanup: separated out nextretry() in qmail-send.c. +19960322 change: qmail-remote now passes all non-@ addresses through + without comment, not just <> and <#>. +19960322 change: replaced # test with anything@[] test in qmail-inject. +19960322 change: replaced # with #@[] in qlist.c, qmail-alias.c, + qmail-send.c, qreceipt.c. +19960322 change: qmail-lspawn no longer discards messages to <#>. +19960322 cleanup: in qlist, used str_diff for <> and <#> tests. +19960322 change: qmail-alias is now back to testing envelope sender for + <> and <#>, rather than things without an @. +19960321 change: added 8BITMIME support to qmail-smtpd. +19960321 change: added ESMTP support to qmail-smtpd. +19960318 change: used NEWSENDER in place of SENDER for |forward. +19960318 change: added NEWSENDER. +19960318 change: added HCMSSC support to qmail-alias.c. +19960318 change: added HCMSSC support to spawn.c. +19960318 change: added HCMSSC support to qmail-remote.c. +19960318 change: added HCMSSC support to qmail-smtpd.c. +19960317 portability problem: SCO requires -lsocket -lnsl. impact: + couldn't compile under SCO. fix: added SCO section in + make-cmds.sh. tnx JPR. note that this is for OSR 5; 3.2v4.2 + will need more fixes, and old 3.2 is basically hopeless. +19960317 bug: newfield_datemake would leave newfield_date alone if it + was already initialized, even though qmail-send calls + newfield_datemake anew for each bounce. impact: bounce messages + would usually have an incorrect Date field. fix: redid + newfield_datemake to update newfield_date each time. +19960317 change: allowed . and @ in 822 phrases; 822 doesn't allow them, + but they do show up. tnx to the DRUMS group. +19960317 change: replaced GMT with +0000 in date822fmt.c. this confuses + a few versions of getdate(), but the DRUMS group is going to + outlaw GMT, not just recommend against it as in 1123. +19960317 change: redefined ALIAS_EMPTY to take advantage of . for file + deliveries. tnx RN. +19960317 change: qmail-alias now allows . as well as / to start file + deliveries. tnx RN. +19960317 change: qmail-alias now dies (soft) if .qmail is writable to + others, rather than silently ignoring it. +19960317 change: qmail-alias now dies (soft) if flagforwardonly is + violated, rather than silently ignoring the bad instructions. +19960317 change: qmail-alias now ignores x bit on empty .qmail files. +19960317 bug: if RCPT gave 4xx and DATA gave 5xx, qmail-rspawn would + incorrectly assign a permanent failure to that recipient. + impact: in that case, mail would be incorrectly bounced. fix: + remove orr > 0 test from qmail-rspawn.c. +19960310 change: tcp-env now uses signal_uninit(). [sigh] +19960310 change: tcp-env now specifically unsets HOST and INFO if they + are not applicable. just trying to make it more widely usable. +19960310 cleanup: used byte_* in remoteinfo.c, ipme.c, tcp-env.c. +19960310 cleanup: added readwrite.h, eliminated sys.h. +19960310 cleanup: included byte.h in qmail-send.c. +19960310 cleanup: eliminated i and j from forward.c's main(). +19960310 cleanup: eliminated wstat from qlist.c. +19960310 cleanup: eliminated die_nomem() parameter in qmail-setup.c. +19960310 cleanup: eliminated i from qmail-remote's addrmangle(). +19960310 cleanup: added exit.h. +19960310 cleanup: split ipalloc.c off of ip.c. +19960310 cleanup: added fmt_strn.c, eliminated fmt_strncpy.c. +19960310 change: reorganized INSTALL to do some pre-upgrade tests. + tnx RN. +19960310 change: reordered steps in upgrade procedure in INSTALL. +19960308 change: eliminated ownership test in qmail-alias.c. tnx DS. +19960304 change: in SECURITY, ``six most recent sendmail security + holes'' -> ``seven most recent sendmail security holes.'' +19960303 qmail 0.73, beta. +19960303 change: added SYSDEPS. +19960303 cleanup: revamped select.h autoconfiguration. +19960303 cleanup: revamped fork.h autoconfiguration. +19960303 cleanup: revamped direntry.h autoconfiguration. target is now + direntry.h; auto-hasdrent.h is gone. +19960303 change: tryflock.c now includes <sys/types.h>, for consistency + with lock.c. may affect portability. +19960302 portability problem: under BSDI, can't set sticky on normal + files. dorks. impact: the new qlist doesn't work under BSDI; + be glad I test things before release. fix: qmail-alias and + qlist now use executable instead of sticky. +19960302 change: gfrom now quotes >From and >>From etc. as well as From; + in other words, I'm switching from mbox format to mboxg format. +19960302 cleanup: added gfrom.c. used it in qmail-alias.c, maildir2mbox.c. +19960302 change: addbounce() now substitutes \n\n -> \n/ in reports, + and \n -> _ in recips. thus bounces can now be reliably parsed. +19960302 change: if qmail-send had trouble reading the original message + or the list of addresses for a bounce, it used to give up and + send a bounce with "Oh no! I had trouble reading the rest of + your message" or some such. now it aborts the bounce attempt + and tries again later. +19960302 cleanup: added qqtalk_fail(). used it in qmail-alias.c, + qmail-smtpd.c. +19960302 bug: if mailforward() had trouble reading message (e.g., + because of an I/O error), it marked an error but kept reading. + impact: could loop forever. fix: upon error, break. +19960302 change: maildir2mbox now scans (restrictively) for return-path. +19960302 change: qbiff now prints subject and body, up to 74 chars. +19960302 change: added H_SUBJECT to hfield. +19960302 change: qbiff now puts TO before FROM. +19960301 cleanup: added fmt_str.c. used it in many places. +19960301 change: qmail-send now says something if you've told it to exit + but it's waiting for some deliveries. tnx RN. +19960301 change: qmail-alias -n now continues (with warning) if home + directory is sticky. tnx RN. +19960301 change: improved usage messages in qmail-alias.c. tnx RN. +19960301 change: put limit on length of addresses in qlist. +19960301 change: added exit 99 support to qmail-alias. tnx RN. +19960301 change: qmail-alias now exits immediately on temporary or + permanent error. rewrote section in qmail-alias.8 accordingly. +19960301 cleanup: eliminated flagsuccesses from qmail-alias.c. +19960301 change: added usermap. +19960301 bug: failure to append to mbox was a permanent error. impact: + if mbox was temporarily unopenable (e.g., because fds were + low), mail would be incorrectly bounced. fix: failure is now + temporary. tnx DS. +19960229 change: qmail-alias now preserves any envelope sender that + doesn't contain an @, not just <> and <#>. +19960229 cleanup: revamped byte_* interface. +19960229 cleanup: renamed str_cpy as str_copy. +19960229 cleanup: added str_chr.c. used it in qbiff.c, qmail-smtpd.c. +19960229 cleanup: added str_rchr.c. used it in qmail-send.c, quote.c, + qmail-remote.c. +19960229 cleanup: added byte_rchr.c. used it in qmail-smtpd.c, spawn.c. +19960229 cleanup: used USEREXT instead of RECIPIENT in qsmhook.c. +19960229 cleanup: used USEREXT instead of RECIPIENT in qbiff.c. +19960229 cleanup: removed j and k from rewrite() in qmail-send.c. +19960229 portability problem: under HP-UX 10 and Solaris 2.5, can't + setgroups()/setgid() to the system's nogroup/nobody gid. dorks. + impact: inetd chokes, so all SMTP connections are rejected; and + ``alias'' mail, including postmaster, bounces. fix: in + INSTALL.ids, set up a separate powerless gid (tentatively + ``nofiles'') for qmaild and alias. tnx DS and PJG. +19960229 change: qreceipt now uses qqtalk rather than qmail-inject. +19960229 change: qlist now uses qqtalk rather than qmail-inject. +19960229 change: incorporated qmail-setup patch from RN for better + error messages. +19960228 change: added LSPAWN_BREAK in conf-unusual.h; used it in + lspawn.c. configurability requested by PJG. +19960228 portability problem: on several systems, including everything + from DEC, select() on a pipe reader returns 1 if there aren't + any writers yet. pointed out by DS. impact: qmail-send chewed + up lots of CPU time. fix: trigger_set() now opens the pipe for + writing after opening it for reading. also added trynpbg1; on + working systems, no point in wasting the extra fd. +19960228 change: qmail-alias uses .qmail sticky bit for forwardonly. +19960228 change: qlist now sets sticky bit on .qmail file. +19960228 change: un-documented +list. +19960228 portability problem: on HP-UX and possibly other systems, the + supplementary group list does not include the gid. pointed out + by DS. impact: on those systems, tryshsgr could incorrectly set + hasshsgr; this would prevent qmail-send from running. fix: if + tryshsgr sees that getgroups() returns 0, now it actively sets + up a supplementary group list. added chkshsgr to make sure the + setgroups() will work. +19960227 cleanup: eliminated GETSHORT in dns.c in favor of getshort(). +19960227 cleanup: deleted h->len < 3 test from qlist.c:dobody. tnx RN. +19960227 change: replaced ~ with $HOME in INSTALL.mbox. +19960227 change: added note about setgid-mail bits to INSTALL.mbox. +19960227 change: added forward.1. +19960227 change: modified forward to allow multiple addresses. +19960227 change: modified forward to take an entire address, not just a + hostname. +19960227 change: renamed qrelay as forward. +19960227 change: added USEREXT support to qmail-alias. +19960227 change: added -F to sendmail. the need for this was pointed + out by RN. +19960227 change: added 2 bytes of slop in alloc(). +19960227 bug: received_setup() was not allowing space for the final \0. + impact: none; the line length is always between 65 and 75 + characters, which gives at least 45 characters of slop with + existing malloc() implementations. fix: leave space. tnx NH. + note that the bug here is really in fmt_strncpy, which was + written before i was truly free of the curse of libc.a. +19960227 change: added ALIAS_EMPTY in conf-unusual.h; used it in + qmail-alias.c. tnx PJG. +19960227 change: added SPAWN_NUMD in conf-unusual.h; used it in spawn.c. +19960227 change: added conf-unusual.h. +19960227 cleanup: replaced sizeof(short) with 2 in dns.c. +19960227 portability problem: on an Alpha, long is 64 bits. pointed out + by DS. impact: address lookups produced incorrect results on an + Alpha; qmail-makectl and qmail-remote failed. fix: replaced + sizeof(long) with 4 in dns.c. +19960227 portability problem: on an Alpha, bzero() is declared properly + via sys/time.h. impact: couldn't compile on an Alpha. fix: + removed bzero() declaration from select.h. tnx DS. +19960227 portability problem: under SCO, sys/file.h is not protected. + impact: couldn't compile under SCO. fix: include sys/types.h in + lock.c. tnx RN. +19960219 change: added some .qmail-list hints to qlist.1. +19960219 change: added +list support to qmail-alias. +19960215 change: added THANKS. +19960212 bug: foo was not initialized in qrelay.c. impact: depends on + the machine; on some machines, no effect; on other machines, + guaranteed core dump. fix: initialized foo. tnx DS. +19960209 qmail 0.72, beta. +19960209 change: qmail-alias now replaces dot, not slash, with colon. + also, qmeopen() makes sure that .qmail file is S_IFREG; I hope + this doesn't cause portability problems. +19960209 change: added success-reporting procedure to INSTALL. +19960208 change: added VERSION. +19960208 change: added qlist2. +19960208 change: revamped qlist interface. tnx RN. +19960208 change: improved an error message in qlist.c. +19960208 change: added qrelay. added relay section to HOWTO. tnx DS. +19960208 cleanup: included substdio.h in qqtalk.h. +19960207 bug: prioq_delmin() wasn't guaranteeing heap structure on the + last element. impact: scheduled passes could have been delayed, + conceivably as long as half an hour. fix: prioq_delmin() now + checks when it can safely move the last element. +19960207 change: added maildirmake.1, maildir2mbox.1. +19960206 change: revised logo paragraph in THOUGHTS. +19960206 change: replaced nowhere.org with nowhere.mil in examples. + nowhere.org is a real domain... [sigh] +19960206 change: added qreceipt.1. +19960206 portability problem: IRIX doesn't have vfork. pointed out by + DS. impact: couldn't compile under IRIX. fix: added fork.h, + tryvfork.c. +19960206 portability problem: IRIX doesn't have ranlib. pointed out by + DS. impact: couldn't compile under IRIX. fix: added IRIX + section in make-cmds.sh. +19960205 cleanup: removed warning from substdio_copy() documentation; in + fact, substdio_copy() can be used safely on a fed substdio. +19960205 change: added qbiff.1. +19960204 change: implemented localnet. removed relevant paragraph from + THOUGHTS. tnx IS. +19960204 change: in qmail-remote.8, explained the dangers of smarthost. + tnx IS. +19960204 change: implemented virtualdomains wildcards. tnx JLH. +19960203 change: qmail-send now handles virtualdomains _after_ locals. + updated INSTALL.qsmhook appropriately. +19960203 change: added note to INSTALL.alias about ~ftp, ~www, ~uucp + being owned by root. +19960130 cleanup: in qlist.c, renamed flagremoved as flagwasthere. +19960130 bug: qmail-send did not pay attention to flagexitasap in + pass_dochan(). impact: qmail-send would happily start new + deliveries even if it wanted to exit. fix: qmail-send now + returns immediately in pass_selprep() and pass_dochan() if + flagexitasap. +19960130 change: in qlist.c and qlist.1, renamed ext as list. +19960130 change: in qlist.c and qlist.1, renamed manager as owner. +19960129 qmail 0.71, beta. +19960129 change: mentioned djb-qmailbeta in README. tnx MWE. +19960129 change: added a note to INSTALL.mbox making clear that + Mailbox is in mbox format. tnx MWE. +19960129 change: qlist now warns you if it didn't see any cmds. tnx RN. +19960129 change: incorporated qlist patch from RN to refuse double subs. +19960129 change: added qlist.1, contributed by RN. mangled it a bit. +19960129 bug: comment was not allowed in ``phrase (comment) <route>''; + pointed out by RN. impact: some correct address lists could be + mis-parsed by qmail-inject or qlist. fix: token.c now allows + TOKEN_COMMENT in the appropriate scan. +19960128 change: added a logo paragraph to THOUGHTS. +19960127 change: implemented rcpthosts. +19960127 change: split up some uses of putflush in qmail-remote, + qmail-smtpd, spawn.c. eliminated NODELAY and corresponding + paragraph in THOUGHTS. +19960127 change: added quote2(). used it in qmail-alias, qmail-send, + qreceipt. now addresses are properly quoted in the From, To, + and internal Return-Path of bounces; the From and To of + receipts; and the Return-Path/RPLINE of delivered messages. + removed relevant paragraph from THOUGHTS. +19960127 change: in RFCLOOPS, documented fact that Delivered-To address + is conventionally not quoted. +19960127 change: knocked default SMTP timeouts down to 20 minutes. +19960127 change: added INSTALL.ids. tnx RN. +19960127 change: in INSTALL, noted that nogroup should already exist. +19960127 bug: pass_selprep checked pqchan[c] even if pass[c].id. impact: + qmail-send wasted CPU time whenever more than one message was + waiting on a blocked channel. fix: pass_selprep now checks + !pass[c].id. +19960127 bug: programs invoked from qmail-alias were immune to SIGPIPE. + impact: a delivery pipeline such as |yes|head -1000 would loop + forever, since yes does not check for write errors. fix: added + signal_uninit(). used it before execvp in qmail-alias. [sigh] +19960127 cleanup: added date822fmt.c. used it in newfield.c, qmail-queue. +19960127 cleanup: added fmt_uint0.c. used it in myctime.c, newfield.c, + qmail-queue. +19960127 cleanup: added dnsdoe.c. used it in dnscname, dnsfq, dnsip, + dnsmxip, dnsptr. +19960127 cleanup: eliminated temp from dnsfq.c. +19960127 bug: gen_allocdefs was making assumptions incompatible with the + alloc_re interface. impact: qmail-send would dump core if you + ran out of memory. fix: changed alloc_re interface. +19960126 portability problem: some versions of Linux don't have + net/route.h. pointed out by RN. impact: couldn't compile under + those versions. fix: ipme.c no longer includes net/route.h; + hopefully this won't cause new portability problems. +19960126 change: added chmod instructions to INSTALL and INSTALL.alias. + tnx RN. +19960126 change: INSTALL now refers to the traditional sendmail spot + (/usr/lib), not the BSD 4.4 spot (/usr/sbin). tnx RN. +19960126 change: make auto-uids.h now creates auto-uids.h.tmp first. + thus, if someone disobeys the installation instructions, and + his make fails to remove targets upon error, he'll still be + okay. tnx RN. +19960126 change: added forgeries.7. +19960125 cleanup: eliminated flagverbose, flagmetoo in sendmail. +19960125 cleanup: added substdio_copy.c. used it at several spots. +19960125 cleanup: added constmap.c. qmail-send now uses constmap for + locals and virtualdomains. this will speed things up: no + problem now to have thousands of virtual domains. removed + relevant paragraph from THOUGHTS. +19960125 change: added linux section in find-systype. this will affect + linux-* systypes. tnx RN for relevant info. +19960124 change: added -od, -oe, -p, -f to sendmail. the need for + these was pointed out by TN. +19960124 bug: qmail-smtpd was reading from descriptor 1. impact: none; + in normal use, both 0 and 1 point to the network. fix: changed + 1 to 0. +19960124 bug: qmail-alias treated any .qmail open failure as permanent. + impact: if a .qmail file was temporarily unopenable (e.g., + because of NFS), it was incorrectly ignored. fix: qmail-alias + now dies QLX_SOFT on any open failure other than ENOENT. +19960124 change: added freebsd section in find-systype, same as bsd.os + section. this will affect freebsd-* systypes. +19960124 cleanup: find-systype now immediately converts sys to lowercase. +19960124 change: qmail-setup now copies man pages into /var/qmail/man; + qmail-check checks /var/qmail/man. using .0 style, which might + cause trouble on various machines, but better than not trying. +19960124 change: in qmail-remote.c, changed perm_control to temp_control + (and D to Z, thanks); thus failure to read control files (e.g., + because of permissions) is now a temporary error. +19960124 bug: in qmail-remote.c, temp_chdir() used D, not Z. impact: if + chdir() to CONF_HOME failed (e.g., because of NFS), message + would be bounced. fix: changed D to Z. +19960124 change: reorganized README. +19960124 portability problem: Linux has the fifo kernel bug that I had + hoped I'd never run into. impact: messages under Linux (and any + other systems with this bug) were picked up only in sweeps, not + instantly. fix: triggerpull.c now writes a byte (non-blocking) + to the fifo. updated INTERNALS accordingly. +19960124 bug: in qmail-remote.c, if quit() saw a remote write error, it + would call writeerr() even though a message report had already + been produced. impact: the mess report would include an extra + ``ZConnected but communications failed,'' which was confusing + to humans. fix: quit() now simply skips the wait-for-QUIT + smtpcode() upon write error. +19960124 portability problem: Linux does not have SIGSYS or SIGEMT. + impact: couldn't compile under Linux. fix: added appropriate + ifdefs in signal.c. +19960124 qmail 0.70, beta. diff --git a/FAQ b/FAQ @@ -0,0 +1,574 @@ +1. Controlling the appearance of outgoing messages +1.1. How do I set up host masquerading? +1.2. How do I set up user masquerading? + +2. Routing outgoing messages +2.1. How do I send local messages to another host? +2.2. How do I set up a null client? +2.3. How do I send outgoing mail through UUCP? +2.4. How do I set up a separate queue for a SLIP/PPP link? +2.5. How do I deal with ``CNAME lookup failed temporarily''? + +3. Routing incoming messages by host +3.1. How do I receive mail for another host name? +3.2. How do I set up a virtual domain? +3.3. How do I set up several virtual domains for one user? + +4. Routing incoming messages by user +4.1. How do I forward unrecognized usernames to another host? +4.2. How do I set up a mailing list? +4.3. How do I use majordomo with qmail? +4.4. How do I use procmail with qmail? +4.5. How do I use elm's filter with qmail? +4.6. How do I create aliases with dots? + +5. Setting up servers +5.1. How do I run qmail-smtpd under tcpserver? +5.2. How do I set up qmail-qmtpd? +5.3. How do I set up qmail-pop3d? +5.4. How do I allow selected clients to use this host as a relay? +5.5. How do I fix up messages from broken SMTP clients? + +6. Configuring MUAs to work with qmail +6.1. How do I make BSD mail generate a Date with the local time zone? +6.2. How do I stop pine from crashing? +6.3. How do I make MH work with qmail? +6.4. How do I stop Sun's dtcm from hanging? + +7. Managing the mail system +7.1. How do I safely stop qmail-send? +7.2. How do I manually run the queue? +7.3. How do I rejuvenate a message? +7.4. How do I organize a big network? +7.5. How do I back up and restore the queue disk? + +8. Miscellany +8.1. How do I tell qmail to do more deliveries at once? +8.2. How do I keep a copy of all incoming and outgoing mail messages? +8.3. How do I switch slowly from sendmail to qmail? + + + +1. Controlling the appearance of outgoing messages + + +1.1. How do I set up host masquerading? All the users on this host, +zippy.af.mil, are users on af.mil. When joe sends a message to fred, the +message should say ``From: joe@af.mil'' and ``To: fred@af.mil'', without +``zippy'' anywhere. + +Answer: echo af.mil > /var/qmail/control/defaulthost; chmod 644 +/var/qmail/control/defaulthost. + + +1.2. How do I set up user masquerading? I'd like my own From lines to +show boss@af.mil rather than god@heaven.af.mil. + +Answer: Add MAILHOST=af.mil and MAILUSER=boss to your environment. To +override From lines supplied by your MUA, add QMAILINJECT=f to your +environment. + + + +2. Routing outgoing messages + + +2.1. How do I send local messages to another host? All the mail for +af.mil should be delivered to our disk server, pokey.af.mil. I've set up +an MX from af.mil to pokey.af.mil, but when a user on the af.mil host +sends a message to boss@af.mil, af.mil tries to deliver it locally. How +do I stop that? + +Answer: Remove af.mil from /var/qmail/control/locals. If qmail-send is +running, give it a HUP. Make sure the MX is set up properly before you +do this. Also make sure that pokey can receive mail for af.mil---see +question 3.1. + + +2.2. How do I set up a null client? I'd like zippy.af.mil to +send all mail to bigbang.af.mil. + +Answer: echo :bigbang.af.mil > /var/qmail/control/smtproutes; +chmod 644 /var/qmail/control/smtproutes. Disable local delivery as in +question 2.1. Turn off qmail-smtpd in /etc/inetd.conf. + + +2.3. How do I send outgoing mail through UUCP? I need qmail to send all +outgoing mail via UUCP to my upstream UUCP site, gonzo. + +Answer: Put + + :alias-uucp + +into control/virtualdomains and + + |preline -df /usr/bin/uux - -r -gC -a"$SENDER" gonzo!rmail "($EXT2@$HOST)" + +into ~alias/.qmail-uucp-default. (For some UUCP software you will need +to use -d instead of -df. Also, you may need to insert a space between +-a and "$SENDER" for bounces to work properly.) If qmail-send is +running, give it a HUP. + + +2.4. How do I set up a separate queue for a SLIP/PPP link? + +Answer: Use serialmail (http://pobox.com/~djb/serialmail.html). + + +2.5. How do I deal with ``CNAME lookup failed temporarily''? The log +showed that a message was deferred for this reason. Why is qmail doing +CNAME lookups, anyway? + +Answer: The SMTP standard does not permit aliased hostnames, so qmail +has to do a CNAME lookup in DNS for every sender and recipient host. If +the relevant DNS server is down, qmail defers the message. It will try +again soon. + + + +3. Routing incoming messages by host + + +3.1. How do I receive mail for another host name? I'd like our disk +server, pokey.af.mil, to receive mail addressed to af.mil. I've set up +an MX from af.mil to pokey.af.mil, but how do I get pokey to treat +af.mil as a name for the local host? + +Answer: Add af.mil to /var/qmail/control/locals and to +/var/qmail/control/rcpthosts. If qmail-send is running, give it a HUP. + + +3.2. How do I set up a virtual domain? I'd like any mail for +nowhere.mil, including root@nowhere.mil and postmaster@nowhere.mil and +so on, to be delivered to Bob. I've set up the MX already. + +Answer: Put + + nowhere.mil:bob + +into control/virtualdomains. Add nowhere.mil to control/rcpthosts. If +qmail-send is running, give it a HUP. + +Now mail for whatever@nowhere.mil will be delivered locally to +bob-whatever. Bob can set up ~bob/.qmail-default to catch all the +possible addresses, ~bob/.qmail-info to catch info@nowhere.mil, etc. + + +3.3. How do I set up several virtual domains for one user? Bob wants +another virtual domain, everywhere.org, but he wants to handle +nowhere.mil users and everywhere.org users differently. How can we do +that without setting up a second account? + +Answer: Put two lines into control/virtualdomains: + + nowhere.mil:bob-nowhere + everywhere.org:bob-everywhere + +Add nowhere.mil and everywhere.org to control/rcpthosts. If qmail-send +is running, give it a HUP. + +Now Bob can set up separate .qmail-nowhere-* and everywhere-* files. He +can even set up .qmail-nowhere-default and .qmail-everywhere-default. + + + +4. Routing incoming messages by user + + +4.1. How do I forward unrecognized usernames to another host? I'd like +to set up a LUSER_RELAY pointing at bigbang.af.mil. + +Answer: Put + + | forward "$LOCAL"@bigbang.af.mil + +into ~alias/.qmail-default. + + +4.2. How do I set up a mailing list? I'd like me-sos@my.host.name to be +forwarded to a bunch of people. + +Answer: Put a list of addresses into ~me/.qmail-sos, one per line. Then +incoming mail for me-sos will be forwarded to each of those addresses. +You should also touch ~me/.qmail-sos-owner so that bounces come back to +you rather than the original sender. If you want subscriptions to be +handled automatically, put + + | qlist2 sos my.host.name + +into ~me/.qmail-sos-request. Anyone who wants to subscribe can simply +send a message to me-sos-request@my.host.name. + + +4.3. How do I use majordomo with qmail? + +Answer: You need to patch majordomo so that it creates qmail-style +lists. See ftp://koobera.math.uic.edu/pub/software/majordomo+qmail.gz. +Exception: qmsmac understands sendmail-style :include: files, so you +shouldn't patch majordomo if you're using qmsmac. + + +4.4. How do I use procmail with qmail? + +Answer: Put + + | preline procmail + +into ~/.qmail. You'll have to use a full path for procmail unless +procmail is in the system's startup PATH. Note that procmail will try to +deliver to /usr/spool/mail/$USER by default; to change this, change +SYSTEM_MBOX in procmail's config.h. + + +4.5. How do I use elm's filter with qmail? + +Answer: Put + + | preline filter + +into ~/.qmail. You'll have to use a full path for filter unless filter +is in the system's startup PATH. + + +4.6. How do I create aliases with dots? I tried setting up +~alias/.qmail-P.D.Q.Bach, but it doesn't do anything. + +Answer: Use .qmail-p:d:q:bach. Dots are converted to colons, and +uppercase is converted to lowercase. + + + +5. Setting up servers + + +5.1. How do I run qmail-smtpd under tcpserver? inetd is barfing at high +loads, cutting off service for ten-minute stretches. I'd also like +better connection logging. + +Answer: First, install the tcpserver program, part of the ucspi-tcp +package (http://pobox.com/~djb/ucspi-tcp.html). Second, remove the smtp +line from /etc/inetd.conf, and put the line + + tcpserver -u 7770 -g 2108 0 smtp /var/qmail/bin/qmail-smtpd & + +into your system startup files. Replace 7770 with your qmaild uid, and +replace 2108 with your nofiles gid. Don't forget the &. The change will +take effect at your next reboot. + +By default, tcpserver allows at most 40 simultaneous qmail-smtpd +processes. To raise this limit to 400, use tcpserver -c 400. To keep +track of who's connecting and for how long, run (on two lines) + + tcpserver -v -u 7770 -g 2108 0 smtp /var/qmail/bin/qmail-smtpd \ + 2>&1 | /var/qmail/bin/splogger smtpd 3 & + + +5.2. How do I set up qmail-qmtpd? + +Answer: Two steps. First, put a + + qmtp 209/tcp + +line into /etc/services. Second, put (all on one line) + + qmtp stream tcp nowait qmaild + /var/qmail/bin/tcp-env tcp-env /var/qmail/bin/qmail-qmtpd + +into /etc/inetd.conf, and give inetd a HUP. + +If you have tcpserver installed, skip the inetd step, and set up + + tcpserver -u 7770 -g 2108 0 qmtp /var/qmail/bin/qmail-qmtpd & + +replacing 7770 and 2108 with the qmaild uid and nofiles gid. See +question 5.1 for more details. + + +5.3. How do I set up qmail-pop3d? + +Answer: Four steps. First, install the checkpassword program +(http://pobox.com/~djb/checkpwd.html). Second, make sure you have a + + pop3 110/tcp + +line in /etc/services. Third, put (all on one line) + + pop3 stream tcp nowait root /var/qmail/bin/qmail-popup + qmail-popup YOURHOST /bin/checkpassword /var/qmail/bin/qmail-pop3d Maildir + +into /etc/inetd.conf, and give inetd a HUP; replace YOURHOST with your +host's fully qualified domain name. Fourth, set up Maildir delivery for +any user who wants to read mail via POP. + +If you have tcpserver installed, skip the inetd step, and set up (on two +lines) + + tcpserver 0 pop3 /var/qmail/bin/qmail-popup YOURHOST \ + /bin/checkpassword /var/qmail/bin/qmail-pop3d Maildir & + +replacing YOURHOST with your host's fully qualified domain name. See +question 5.1 for more details. + +Security note: pop3d should be used only within a secure network; +otherwise an eavesdropper can steal passwords. + + +5.4. How do I allow selected clients to use this host as a relay? I see +that qmail-smtpd rejects messages to any host not listed in +control/rcpthosts. I know I could entirely disable this feature by +removing control/rcpthosts, but I want to be more selective. + +Answer: Three steps. First, install tcp-wrappers, available separately, +including hosts_options. Second, change your qmail-smtpd line in +inetd.conf to + + smtp stream tcp nowait qmaild /usr/local/bin/tcpd + /var/qmail/bin/tcp-env /var/qmail/bin/qmail-smtpd + +(all on one line) and give inetd a HUP. Third, in tcpd's hosts.allow, +make a line setting the environment variable RELAYCLIENT to the empty +string for the selected clients: + + tcp-env: 1.2.3.4, 1.2.3.5: setenv = RELAYCLIENT + +Here 1.2.3.4 and 1.2.3.5 are the clients' IP addresses. qmail-smtpd +ignores control/rcpthosts when RELAYCLIENT is set. (It also appends +RELAYCLIENT to each envelope recipient address. See question 5.5 for an +application.) + +Alternative procedure, if you are using tcpserver: Install tcpcontrol +(http://pobox.com/~djb/tcpcontrol.html). Create /etc/tcp.smtp containing + + 1.2.3.6:allow,RELAYCLIENT="" + 127.:allow,RELAYCLIENT="" + +to allow clients with IP addresses 1.2.3.6 and 127.*. Run + + tcpmakectl /etc/tcp.smtp.cdb /etc/tcp.smtp.tmp < /etc/tcp.smtp + +Finally, insert + + tcpcontrol /etc/tcp.smtp.cdb + +before /var/qmail/bin/qmail-smtpd in your tcpserver line. + + +5.5. How do I fix up messages from broken SMTP clients? + +Answer: Three steps. First, put + + | [ "@$HOST" = "@fixme" ] || ( echo Permission denied; exit 100 ) + | qmail-inject -f "$SENDER" -- "$EXT2" + +into ~alias/.qmail-fixup-default. Second, put + + fixme:fixup + +into /var/qmail/control/virtualdomains, and give qmail-send a HUP. +Third, follow the procedure in question 5.4, but set RELAYCLIENT to the +string ``@fixme'': + + tcp-env: 1.2.3.6, 1.2.3.7: setenv = RELAYCLIENT @fixme + +Here 1.2.3.6 and 1.2.3.7 are the clients' IP addresses. If you are using +tcpserver and tcpcontrol instead of inetd and tcpd, put + + 1.2.3.6:allow,RELAYCLIENT="@fixme" + 1.2.3.7:allow,RELAYCLIENT="@fixme" + +into /etc/tcp.smtp, and run tcpmakectl as in question 5.4. + + + +6. Configuring MUAs to work with qmail + + +6.1. How do I make BSD mail generate a Date with the local time zone? +When I send mail, I'd rather use the local time zone than GMT, since +some MUAs don't know how to display Date in the receiver's time zone. + +Answer: Put + + set sendmail=/var/qmail/bin/datemail + +into your .mailrc or your system-wide Mail.rc. Beware that BSD mail is +neither secure nor reliable. + + +6.2. How do I stop pine from crashing? When I ask any version of pine +past 3.91 to send mail, it crashes. + +Answer: Put + + sendmail-path=/usr/lib/sendmail -oem -oi -t + +into /usr/local/lib/pine.conf. (This will work with sendmail too.) +Beware that pine is neither secure nor reliable. + + +6.3. How do I make MH work with qmail? + +Answer: Put + + postproc: /usr/mh/lib/spost + +into each user's .mh_profile. (This will work with sendmail too.) Beware +that MH is neither secure nor reliable. + + +6.4. How do I stop Sun's dtcm from hanging? + +Answer: There is a novice programming error in dtcm, known as ``failure +to close the output side of the pipe in the child.'' Sun has, at the +time of this writing, not yet provided a patch. Sorry. + + + +7. Managing the mail system + + +7.1. How do I safely stop qmail-send? Back when we were running +sendmail, it was always tricky to kill sendmail without risking the loss +of current deliveries; what should I do with qmail-send? + +Answer: Go ahead and kill the qmail-send process. It will shut down +cleanly. Wait for ``exiting'' to show up in the log. To restart it, run +qmail-start the same way as it's run from your system boot scripts. + + +7.2. How do I manually run the queue? I'd like qmail to try delivering +all the remote messages right now. + +Answer: Give the qmail-send process an ALRM. + + +7.3. How do I rejuvenate a message? Somebody broke into Eric's computer +again; it's going to be down for at least another two days. I know Eric +has been expecting an important message---in fact, I see it sitting here +in /var/qmail/queue/mess/15/26902. It's been in the queue for six days; +how can I make sure it isn't bounced tomorrow? + +Answer: Just touch /var/qmail/queue/info/15/26902. (This is the only +form of queue modification that's safe while qmail is running.) + + +7.4. How do I organize a big network? I have a lot of machines, and I +don't know where to start. + +Answer: First, choose the domain name where your users will receive +mail. This is normally the shortest domain name you control. If you are +in charge of *.movie.edu, you can use addresses like joe@movie.edu. + +Second, choose the machine that will know what to do with different +users at movie.edu. Set up a host name in DNS for this machine: + + mailhost.movie.edu IN A 1.2.3.4 + 4.3.2.1.in-addr.arpa IN PTR mailhost.movie.edu + +Here 1.2.3.4 is the IP address of that machine. + +Third, make a list of machines where mail should end up. For example, if +mail for Bob should end up on Bob's workstation, put Bob's workstation +onto the list. For each of these machines, set up a host name in DNS: + + bobshost.movie.edu IN A 1.2.3.7 + 7.3.2.1.in-addr.arpa IN PTR bobshost.movie.edu + +Fourth, install qmail on bobshost.movie.edu. qmail will automatically +configure itself to accept messages for bob@bobshost.movie.edu and +deliver them to ~bob/Mailbox on bobshost. Do the same for the other +machines where mail should end up. + +Fifth, install qmail on mailhost.movie.edu. Put + + movie.edu:alias-movie + +into control/virtualdomains on mailhost. Then forward bob@movie.edu to +bob@bobshost.movie.edu, by putting + + bob@bobshost.movie.edu + +into ~alias/.qmail-movie-bob. Do the same for other users. + +Sixth, put movie.edu into control/rcpthosts on mailhost.movie.edu, so +that mailhost.movie.edu will accept messages for users at movie.edu. + +Seventh, set up an MX record in DNS to deliver movie.edu messages to +mailhost: + + movie.edu IN MX 10 mailhost.movie.edu + +Eighth, on all your machines, put movie.edu into control/defaulthost. + + +7.5. How do I back up and restore the queue disk? + +Answer: You can't. + +One difficulty is that you can't get a consistent snapshot of the queue +while qmail-send is running. Another difficulty is that messages in the +queue must have filenames that match their inode numbers. + +However, the big problem is that backups---even twice-daily backups--- +are far too unreliable for mail. If your disk dies, there will be very +little overlap between the messages saved in the last backup and the +messages that were lost. + +There are several ways to add real reliability to a mail server. Battery +backups will keep your server alive, letting you park the disk to avoid +a head crash, when the power goes out. Solid-state disks have their own +battery backups. RAID boxes let you replace dead disks without losing +any data. + + + +8. Miscellany + + +8.1. How do I tell qmail to do more deliveries at once? It's running +only 20 parallel qmail-remote processes. + +Answer: Decide how many deliveries you want to allow at once. Put that +number into control/concurrencyremote. Restart qmail-send as in question +7.1. If your system has resource limits, make sure you set the +descriptors limit to at least double the concurrency plus 5; otherwise +you'll get lots of unnecessary deferrals whenever a big burst of mail +shows up. Note that qmail also imposes a compile-time concurrency limit, +120 by default; this is set in conf-spawn. + + +8.2. How do I keep a copy of all incoming and outgoing mail messages? + +Answer: Set QUEUE_EXTRA to "Tlog\0" and QUEUE_EXTRALEN to 5 in extra.h. +Recompile qmail. Put ./msg-log into ~alias/.qmail-log. + +You can also use QUEUE_EXTRA to, e.g., record the Message-ID of every +message: run + + | awk '/^$/ { exit } /^[mM][eE][sS][sS][aA][gG][eE]-/ { print }' + +from ~alias/.qmail-log. + + +8.3. How do I switch slowly from sendmail to qmail? I'm thinking of +moving the heaven.af.mil network over to qmail, but first I'd like to +give my users a chance to try out qmail without affecting current +sendmail deliveries. We're using NFS. + +Answer: Find a host in your network, say pc.heaven.af.mil, that isn't +running an SMTP server. (If addresses at pc.heaven.af.mil are used, you +should already have an MX pointing pc.heaven.af.mil to your mail hub.) + +Set up a new MX record pointing lists.heaven.af.mil to pc.heaven.af.mil. +Install qmail on pc.heaven.af.mil. Replace pc with lists in the control +files. Make the qmail man pages available on all your machines. + +Now tell your users about qmail. A user can forward joe@heaven.af.mil to +joe@lists.heaven.af.mil to get ~/Mailbox delivery; he can set up .qmail +files; he can start running his own mailing lists @lists.heaven.af.mil. + +When you're ready to turn sendmail off, you can set up pc.heaven.af.mil +as your new mail hub. Add heaven.af.mil to control/locals, and change +the heaven.af.mil MX to point to pc.heaven.af.mil. Make sure you leave +lists.heaven.af.mil in control/locals so that transition addresses will +continue to work. diff --git a/FILES b/FILES @@ -0,0 +1,401 @@ +BLURB +BLURB2 +BLURB3 +BLURB4 +README +FAQ +INSTALL +INSTALL.alias +INSTALL.boot +INSTALL.ctl +INSTALL.ids +INSTALL.mbox +INSTALL.qsmhook +UPGRADE +THOUGHTS +TODO +THANKS +CHANGES +RFCHCSC +RFCLOOPS +RFCMXPS +RFCNETSTR +RFCNRUDT +RFCQMTP +RFCQSBMF +RFCVERP +SECURITY +INTERNALS +FILES +VERSION +SYSDEPS +TARGETS +Makefile +conf-break +auto_break.h +conf-spawn +auto_spawn.h +chkspawn.c +conf-split +auto_split.h +conf-patrn +auto_patrn.h +conf-users +conf-groups +auto_uids.h +auto_usera.h +extra.h +addresses.5 +condredirect.1 +dot-qmail.9 +envelopes.5 +forgeries.7 +forward.1 +maildir2mbox.1 +maildirmake.1 +maildirwatch.1 +mailsubj.1 +mbox.5 +preline.1 +qbiff.1 +qlist.1 +qmail-clean.8 +qmail-command.8 +qmail-control.9 +qmail-getpw.9 +qmail-header.5 +qmail-inject.8 +qmail-limits.9 +qmail-local.8 +qmail-log.5 +qmail-lspawn.8 +qmail-newu.9 +qmail-pop3d.8 +qmail-popup.8 +qmail-pw2u.9 +qmail-qmtpd.8 +qmail-qread.8 +qmail-qstat.8 +qmail-queue.8 +qmail-remote.8 +qmail-rspawn.8 +qmail-send.9 +qmail-showctl.8 +qmail-smtpd.8 +qmail-start.9 +qmail-tcpto.8 +qmail-upgrade.9 +qmail-users.9 +qmail.7 +qreceipt.1 +splogger.8 +tcp-env.1 +qmail-clean.c +qmail-config.sh +qmail-getpw.c +qmail-hier.c +qmail-inject.c +qmail-local.c +qmail-lspawn.c +qmail-newu.c +qmail-pop3d.c +qmail-popup.c +qmail-pw2u.c +qmail-qmtpd.c +qmail-qread.c +qmail-qstat.sh +qmail-queue.c +qmail-remote.c +qmail-rspawn.c +qmail-send.c +qmail-showctl.c +qmail-smtpd.c +qmail-start.c +qmail-tcpto.c +spawn.c +dnscname.c +dnsfq.c +dnsip.c +dnsmxip.c +dnsptr.c +hostname.c +ipmeprint.c +tcp-env.c +sendmail.c +qlist.c +qreceipt.c +qsmhook.c +qbiff.c +forward.c +preline.c +predate.c +condredirect.c +maildirmake.c +maildir2mbox.c +maildirwatch.c +splogger.c +qail.sh +elq.sh +pinq.sh +qlist2.sh +qmail-upq.sh +datemail.sh +mailsubj.sh +qlx.h +constmap.h +constmap.c +dnsdoe.h +dnsdoe.c +fmtqfn.h +fmtqfn.c +gfrom.h +gfrom.c +myctime.h +myctime.c +newfield.h +newfield.c +qsutil.h +qsutil.c +readsubdir.h +readsubdir.c +received.h +received.c +tcpto.h +tcpto.c +tcpto_clean.c +trigger.h +trigger.c +triggerpull.h +triggerpull.c +trynpbg1.c +trysyslog.c +conf-cc +conf-ld +find-systype.sh +make-compile.sh +make-load.sh +make-makelib.sh +trycpp.c +warn-auto.sh +auto-str.c +auto-int.c +auto-int8.c +auto-gid.c +auto-uid.c +install.c +instcheck.c +alloc.3 +alloc.h +alloc.c +alloc_re.c +case.3 +case.h +case_diffb.c +case_diffs.c +case_lowerb.c +case_lowers.c +case_starts.c +cdb.3 +cdb.h +cdb_hash.c +cdb_seek.c +cdb_unpack.c +cdbmake.h +cdbmake_add.c +cdbmake_hash.c +cdbmake_pack.c +cdbmss.h +cdbmss.c +coe.3 +coe.h +coe.c +fd.h +fd_copy.3 +fd_copy.c +fd_move.3 +fd_move.c +fifo_make.3 +fifo.h +fifo.c +trymkffo.c +fork.h1 +fork.h2 +tryvfork.c +now.3 +now.h +now.c +open.h +open_append.c +open_excl.c +open_read.c +open_trunc.c +open_write.c +seek.h +seek_cur.c +seek_end.c +seek_set.c +seek_trunc.c +conf-qmail +auto_qmail.h +qmail.h +qmail.c +gen_alloc.h +gen_allocdefs.h +stralloc.3 +stralloc.h +stralloc_eady.c +stralloc_pend.c +stralloc_copy.c +stralloc_opyb.c +stralloc_opys.c +stralloc_cat.c +stralloc_catb.c +stralloc_cats.c +stralloc_arts.c +strerr.h +strerr_sys.c +strerr_die.c +substdio.h +substdio.c +substdi.c +substdo.c +substdio_copy.c +subfd.h +subfderr.c +subfdouts.c +subfdout.c +subfdins.c +subfdin.c +readwrite.h +exit.h +timeoutconn.h +timeoutconn.c +timeoutread.h +timeoutread.c +timeoutwrite.h +timeoutwrite.c +remoteinfo.h +remoteinfo.c +uint32.h1 +uint32.h2 +tryulong32.c +wait.3 +wait.h +wait_pid.c +wait_nohang.c +trywaitp.c +sig.h +sig_alarm.c +sig_block.c +sig_catch.c +sig_pause.c +sig_pipe.c +sig_child.c +sig_term.c +sig_hup.c +sig_misc.c +sig_bug.c +trysgact.c +trysgprm.c +env.3 +env.h +env.c +envread.c +byte.h +byte_chr.c +byte_copy.c +byte_cr.c +byte_diff.c +byte_rchr.c +byte_zero.c +str.h +str_chr.c +str_cpy.c +str_diff.c +str_diffn.c +str_len.c +str_rchr.c +str_start.c +lock.h +lock_ex.c +lock_exnb.c +lock_un.c +tryflock.c +getln.3 +getln.h +getln.c +getln2.3 +getln2.c +sgetopt.3 +sgetopt.h +sgetopt.c +subgetopt.3 +subgetopt.h +subgetopt.c +error.3 +error_str.3 +error_temp.3 +error.h +error.c +error_str.c +error_temp.c +fmt.h +fmt_str.c +fmt_strn.c +fmt_uint.c +fmt_uint0.c +fmt_ulong.c +scan.h +scan_ulong.c +scan_8long.c +scan_nbblong.c +slurpclose.h +slurpclose.c +quote.h +quote.c +hfield.h +hfield.c +headerbody.h +headerbody.c +token822.h +token822.c +control.h +control.c +datetime.3 +datetime.h +datetime.c +datetime_un.c +prioq.h +prioq.c +date822fmt.h +date822fmt.c +dns.h +dns.c +trylsock.c +tryrsolv.c +ip.h +ip.c +ipalloc.h +ipalloc.c +select.h1 +select.h2 +trysysel.c +ndelay.h +ndelay.c +ndelay_off.c +direntry.3 +direntry.h1 +direntry.h2 +trydrent.c +prot.h +prot.c +chkshsgr.c +warn-shsgr +tryshsgr.c +ipme.h +ipme.c +trysalen.c +maildir.5 +maildir.h +maildir.c +tcp-environ.5 diff --git a/INSTALL b/INSTALL @@ -0,0 +1,181 @@ +SAVE COPIES OF YOUR OUTGOING MAIL! Like any other piece of software (and +information generally), the qmail system comes with NO WARRANTY. It's +much more secure and reliable than sendmail, but that's not saying much. + + +Things you have to decide before starting: + +* The qmail home directory, normally /var/qmail. To change this +directory, edit conf-qmail now. + +* The names of the qmail users and the qmail groups. To change these +names, edit conf-users and conf-groups now. + + +Installation steps that won't interfere with sendmail: + + 1. Create the qmail home directory: + # mkdir /var/qmail + 2. Read INSTALL.ids. You must set up the qmail group and the qmail + users before compiling the programs. + 3. Compile the programs: + # make + 4. Create the formatted man pages, *.0: + # make man + 5. Create the qmail directory tree: + # make setup + 6. Run instcheck to make sure it doesn't print any warnings: + # make check + 7. Read INSTALL.ctl and FAQ. Minimal survival command: + # ./qmail-config + 8. Read INSTALL.alias. Minimal survival command: + # (cd ~alias; touch .qmail-postmaster .qmail-mailer-daemon .qmail-root) + # chmod 644 ~alias/.qmail* + 9. Read INSTALL.mbox. +10. Read qmail-upgrade.0. This is what your users will need to know + about the switch from sendmail to qmail. + + +Pre-upgrade tests: + +11. Enable deliveries of messages injected into qmail: + # env - PATH="/var/qmail/bin:$PATH" \ + qmail-start ./Mailbox splogger qmail & + Make sure to include the ./ in ./Mailbox. +12. Look for a + qmail: running + line in syslog. qmail-send always prints either ``cannot start'' or + ``running''. (The big number is a splogger timestamp.) +13. Do a ps and look for the qmail daemons. There should be four of + them, all idle: qmail-send, running as qmails; qmail-lspawn, running + as root; qmail-rspawn, running as qmailr; and qmail-clean, running + as qmailq. You will also see the splogger process. +14. Local-local test: Send yourself an empty message. (Replace ``me'' + with your username. Make sure to include the ``to:'' colon.) + % echo to: me | /var/qmail/bin/qmail-inject + The message will show up immediately in ~/Mailbox, and syslog will + show something like this: + qmail: new msg 53 + qmail: info msg 53: bytes 246 from <me@domain> qp 20345 uid 666 + qmail: starting delivery 1: msg 53 to local me@domain + qmail: delivery 1: success: did_1+0+0/ + qmail: end msg 53 + (53 is an inode number; 20345 is a process ID; your numbers will + probably be different.) +15. Local-error test: Send a message to a nonexistent local address. + % echo to: nonexistent | /var/qmail/bin/qmail-inject + qmail: new msg 53 + qmail: info msg 53: bytes 246 from <me@domain> qp 20351 uid 666 + qmail: starting delivery 2: msg 53 to local nonexistent@domain + qmail: delivery 2: failure: No_such_address.__#5.1.1_/ + qmail: bounce msg 53 qp 20357 + qmail: end msg 53 + qmail: new msg 54 + qmail: info msg 54: bytes 743 from <> qp 20357 uid 666 + qmail: starting delivery 3: msg 54 to local me@domain + qmail: delivery 3: success: did_1+0+0/ + qmail: end msg 54 + You will now have a bounce message in ~/Mailbox. +16. Local-remote test: Send an empty message to your account on another + machine. + % echo to: me@wherever | /var/qmail/bin/qmail-inject + qmail: new msg 53 + qmail: info msg 53: bytes 246 from <me@domain> qp 20372 uid 666 + qmail: starting delivery 4: msg 53 to remote me@wherever + qmail: delivery 4: success: 1.2.3.4_accepted_message./... + qmail: end msg 53 + There will be a pause between ``starting delivery'' and ``success''; + SMTP is slow. Check that the message is in your mailbox on the other + machine. +17. Local-postmaster test: Send mail to postmaster, any capitalization. + % echo to: POSTmaster | /var/qmail/bin/qmail-inject + Look for the message in ~alias/Mailbox. +18. Double-bounce test: Send a message with a completely bad envelope. + % /var/qmail/bin/qmail-inject -f nonexistent + To: unknownuser + Subject: testing + + This is a test. This is only a test. + % + (Use end-of-file, not dot, to end the message.) Look for the double + bounce in ~alias/Mailbox. +19. Group membership test: + % cat > ~me/.qmail-groups + |groups >> MYGROUPS; exit 0 + % /var/qmail/bin/qmail-inject me-groups < /dev/null + % cat ~me/MYGROUPS + MYGROUPS will show your normal gid and nothing else. (Under Solaris, + make sure to use /usr/ucb/groups; /usr/bin/groups is broken.) + + +Upgrading from sendmail to qmail: + +20. Read INSTALL.boot. You must replace the sendmail invocation in your + boot scripts with an appropriate qmail invocation. +21. Kill the sendmail daemon. You should first kill -STOP the daemon; if + any children are running, you should kill -CONT, wait, kill -STOP + again, and repeat ad nauseam. If there aren't any children, kill + -TERM and then kill -CONT. +22. Replace sendmail with a link to qmail's ``sendmail'' wrapper: + # mv /usr/lib/sendmail /usr/lib/sendmail.bak + # ln -s /var/qmail/bin/sendmail /usr/lib/sendmail +23. Set up qmail-smtpd in /etc/inetd.conf (all on one line): + smtp stream tcp nowait qmaild /var/qmail/bin/tcp-env + tcp-env /var/qmail/bin/qmail-smtpd + Also comment out comsat in /etc/inetd.conf. +24. Reboot. (Or kill -HUP your inetd and make sure the qmail daemons + are running.) +25. Try to flush the sendmail queue: + # /usr/lib/sendmail.bak -q + You can safely run sendmail.bak -q (or even sendmail.bak -q15m) + while qmail is running. Do this until the sendmail queue is empty. + This may take several days. +26. Disable all the sendmail and binmail programs in your system. The + safest approach is to chmod 0 everything. Some locations to check: + /usr/sbin/sendmail, /usr/lib/sendmail.bak, /usr/lib/sendmail.mx, + /bin/mail, /usr/libexec/mail.local. +27. Make sure that ``mail'' still invokes a reasonable mailer. Under + SVR4 you may want to link mail to mailx. + + +Post-upgrade tests (can be done immediately after step 24): + +28. SMTP server test: Forge some mail locally via SMTP. + % telnet 127.0.0.1 25 + Trying 127.0.0.1... + Connected to 127.0.0.1. + Escape character is '^]'. + 220 domain ESMTP + helo dude + 250-domain + 250-PIPELINING + 250 8BITMIME + mail <me@domain> + 250 ok + rcpt <me@domain> + 250 ok + data + 354 go ahead + Subject: testing + + This is a test. + . + 250 ok 812345679 qp 12345 + quit + 221 domain + Connection closed by foreign host. + % + Look for the message in your mailbox. +29. Remote-local test: Send yourself some mail from another machine. +30. Remote-error test: I think you can figure this one out. +31. UA test: Try sending mail, first to a local account, then to a + remote account, with your normal user agent. +32. Remote-postmaster test: Send mail from another machine to + PoStMaStEr@domain. Look for the message in ~alias/Mailbox. + + +That's it! To report success: + % ( echo 'First M. Last'; cat `cat SYSDEPS` ) \ + | mail djb-qst@koobera.math.uic.edu +Replace First M. Last with your name. If you have questions about qmail, +contact qmail@pobox.com. diff --git a/INSTALL.alias b/INSTALL.alias @@ -0,0 +1,40 @@ +qmail lets each user control all addresses of the form user-anything. +Addresses that don't start with a username are controlled by a special +user, alias. Delivery instructions for foo go into ~alias/.qmail-foo; +delivery instructions for user-foo go into ~user/.qmail-foo. See +qmail-upgrade.0 and dot-qmail.0 for the full story. + +qmail doesn't have any built-in support for /etc/aliases. If you have a +big /etc/aliases and you'd like to keep it, install the qmsmac package, +available separately. /etc/aliases should already include the aliases +discussed below---Postmaster, MAILER-DAEMON, and root. + +If you don't have a big /etc/aliases, you'll find it easier to use +qmail's native alias mechanism. Here's a checklist of aliases you should +set up right now. + +* Postmaster. You're not an Internet citizen if this address doesn't +work. Simply touch (and chmod 644) ~alias/.qmail-postmaster; any mail +for Postmaster will be delivered to ~alias/Mailbox. + +* MAILER-DAEMON. Not required, but users sometimes respond to bounce +messages. Touch (and chmod 644) ~alias/.qmail-mailer-daemon. + +* root. Under qmail, root never receives mail. Your system may generate +mail messages to root every night; if you don't have an alias for root, +those messages will bounce. (They'll end up double-bouncing to the +postmaster.) Set up an alias for root in ~alias/.qmail-root. .qmail +files are similar to .forward files, but beware that they are strictly +line-oriented---see dot-qmail.0 for details. + +* Other non-user accounts. Under qmail, non-user accounts don't get +mail; ``user'' means a non-root account that owns ~account. Set up +aliases for any non-user accounts that normally receive mail. + +Note that special accounts such as ftp, www, and uucp should always have +home directories owned by root. + +* Default. If you want, you can touch ~alias/.qmail-default to catch +everything else. Beware: this will also catch typos and other addresses +that should probably be bounced instead. It won't catch addresses that +start with a user name---the user can set up his own ~/.qmail-default. diff --git a/INSTALL.boot b/INSTALL.boot @@ -0,0 +1,16 @@ +The qmail daemons have to be restarted whenever your system reboots. +Meanwhile, sendmail doesn't have to be started any more. Here's what you +should do. + +Find sendmail in your boot scripts. It's usually in either /etc/rc or +/etc/init.d/sendmail. It looks like + + sendmail -bd -q15m + +-q15m means it should run the queue every 15 minutes; you may see a +different number. Comment out this line, and replace it with + + env - PATH="/var/qmail/bin:$PATH" \ + csh -cf 'qmail-start ./Mailbox splogger qmail &' + +That's it. (Make sure you include the ./ and the &.) diff --git a/INSTALL.ctl b/INSTALL.ctl @@ -0,0 +1,29 @@ +As you've seen, qmail has essentially no pre-compilation configuration. +You should never have to recompile it unless you want to change the +qmail home directory, usernames, or uids. + +qmail does allow quite a bit of easy post-installation configuration. If +you care how your machine greets other machines via SMTP, for example, +you can put an appropriate line into /var/qmail/control/smtpgreeting. + +But this is all optional---if control/smtpgreeting doesn't exist, qmail +will do something reasonable by default. You shouldn't worry much about +configuration right now. You can always come back and tune things later. + +There's one big exception. You MUST tell qmail your hostname. The easy +way to do this is to run the qmail-config script: + + # ./qmail-config + +qmail-config finds your fully-qualified hostname in DNS and puts it into +control/me. It also selects good defaults for a few other controls. + +(Why doesn't qmail do these lookups on the fly? This was a deliberate +design decision. qmail does all its local functions---header rewriting, +checking if a recipient is local, etc.---without talking to the network. +The point is that qmail can continue accepting and delivering local mail +even if your network connection goes down.) + +Next, read through FAQ for information on setting up optional features +like masquerading. If you really want to learn right now what all the +configuration possibilities are, see qmail-control.0. diff --git a/INSTALL.ids b/INSTALL.ids @@ -0,0 +1,59 @@ +Here's how to set up the qmail groups and the qmail users. + +On some systems there are commands that make this easy. Solaris: + + # groupadd nofiles + # useradd -g nofiles -d /var/qmail/alias alias + # useradd -g nofiles -d /var/qmail qmaild + # useradd -g nofiles -d /var/qmail qmaill + # useradd -g nofiles -d /var/qmail qmailp + # groupadd qmail + # useradd -g qmail -d /var/qmail qmailq + # useradd -g qmail -d /var/qmail qmailr + # useradd -g qmail -d /var/qmail qmails + +BSDI 2.0: + + # addgroup nofiles + # adduser -g nofiles -H/var/qmail/alias -G,,, -s/dev/null -P'*' alias + # adduser -g nofiles -H/var/qmail -G,,, -s/dev/null -P'*' qmaild + # adduser -g nofiles -H/var/qmail -G,,, -s/dev/null -P'*' qmaill + # adduser -g nofiles -H/var/qmail -G,,, -s/dev/null -P'*' qmailp + # addgroup qmail + # adduser -g qmail -H/var/qmail -G,,, -s/dev/null -P'*' qmailq + # adduser -g qmail -H/var/qmail -G,,, -s/dev/null -P'*' qmailr + # adduser -g qmail -H/var/qmail -G,,, -s/dev/null -P'*' qmails + +AIX: + + # mkgroup -A nofiles + # mkuser pgrp=nofiles home=/var/qmail/alias shell=/bin/true alias + # mkuser pgrp=nofiles home=/var/qmail shell=/bin/true qmaild + # mkuser pgrp=nofiles home=/var/qmail shell=/bin/true qmaill + # mkuser pgrp=nofiles home=/var/qmail shell=/bin/true qmailp + # mkgroup -A qmail + # mkuser pgrp=qmail home=/var/qmail shell=/bin/true qmailq + # mkuser pgrp=qmail home=/var/qmail shell=/bin/true qmailr + # mkuser pgrp=qmail home=/var/qmail shell=/bin/true qmails + +On other systems, you will have to edit /etc/group and /etc/passwd +manually. First add two new lines to /etc/group, something like + + qmail:*:2107: + nofiles:*:2108: + +where 2107 and 2108 are different from the other gids in /etc/group. +Next (using vipw) add six new lines to /etc/passwd, something like + + alias:*:7790:2108::/var/qmail/alias:/bin/true + qmaild:*:7791:2108::/var/qmail:/bin/true + qmaill:*:7792:2108::/var/qmail:/bin/true + qmailp:*:7793:2108::/var/qmail:/bin/true + qmailq:*:7794:2107::/var/qmail:/bin/true + qmailr:*:7795:2107::/var/qmail:/bin/true + qmails:*:7796:2107::/var/qmail:/bin/true + +where 7790 through 7796 are _new_ uids, 2107 is the qmail gid, and 2108 +is the nofiles gid. Make sure you use the nofiles gid for qmaild, +qmaill, qmailp, and alias, and the qmail gid for qmailq, qmailr, and +qmails. diff --git a/INSTALL.mbox b/INSTALL.mbox @@ -0,0 +1,112 @@ +The qmail package includes a local delivery agent, qmail-local, which +provides user-controlled mailing lists, cross-host alias loop detection, +and many other important qmail features. + +There's one part of qmail-local that you need to know about right now: +qmail-local doesn't support an insecure central mail spool. It delivers +mail by default into ~user/Mailbox (in mbox format). + +This file explains what you should do to deal with this change. It also +points out some reasons that you might want to make an even bigger +change, switching from mbox format to a new format, maildir. + +If you desperately don't want to change anything, see INSTALL.qsmhook. + + +Contents: +1. Throw away /usr/spool/mail! +2. The trouble with mbox +3. Sun's Network F_ail_u_re System + + +1. Throw away /usr/spool/mail! + +/usr/spool/mail, often called /var/spool/mail or /var/mail, is a +security disaster. A user's mailbox belongs in his home directory, not a +shared directory. Even if you don't install qmail, you should destroy +/usr/spool/mail. This takes four steps: + + A. Convince your local mailer to deliver to ~user/Mailbox. If you're + using something like procmail, this is easy---just change SYSTEM_MBOX + in config.h. If you're installing qmail, you don't have to do + anything. Otherwise, take a look at hlfsd from + ftp.cs.columbia.edu/pub/amd. + + B. Move each /usr/spool/mail/user to ~user/Mailbox. For safety, do + this in single-user mode---you don't want to risk corrupting + mailboxes. (qmail makes it easy to turn off deliveries temporarily: + just kill the qmail-send daemon. But you aren't running qmail yet.) + When you're done, remove /usr/spool/mail. + + C. Put ``setenv MAIL $HOME/Mailbox'' in your system-wide .cshrc, + ``MAIL=$HOME/Mailbox; export MAIL'' in your system-wide .profile, + ``inbox-path=Mailbox'' in your system-wide pine.conf. If you're using + qpopper 2.2, you'll have to recompile with -DHOMEDIRMAIL in CFLAGS + and with /.mail changed to /Mailbox in pop_dropcopy.c. If you're + using elm on a multiuser system, you'll have to recompile elm with + "mailbox" changed to "Mailbox" around line 388 of newmbox.c. + + D. Announce the change. + +Some vendors, in a misguided attempt to solve the security problems of +/usr/spool/mail, have made all MUAs (e.g., /usr/ucb/Mail) setgid mail. +After you get rid of /usr/spool/mail, you can also disable those +setgid-mail bits. + + +2. The trouble with mbox + +The mbox format---the format of ~user/Mailbox, understood by BSD Mail +and lots of other MUAs---is inherently unreliable. + +Think about it: what happens if the system crashes while a program is +appending a new message to ~user/Mailbox? The message will be truncated. +Even worse, if it was truncated in the middle of a line, it will end up +being merged with the next message! Sure, the mailer understands that it +wasn't successful, so it'll try delivering the message again later, but +it can't fix your corrupted mbox. + +Other formats, such as mh folders, are just as unreliable. + +qmail supports maildir, a crashproof format for incoming mail messages. +maildir is fast and easy for MUAs to use. Even better, maildir works +wonders over NFS---see below. + +I don't want to cram maildir down people's throats, so it's not the +default. Nevertheless, I encourage you to start asking for maildir +versions of your favorite MUAs, and to switch over to maildir as soon as +you can. + +WARNING: qmail uses flock() to lock ~user/Mailbox. This agrees with the +modern mail.local locking choice. If your MUA doesn't use flock(), your +best bet is to switch to maildir, and to set up synchronous maildir2mbox +execution, as described below. + + +3. Sun's Network F_ail_u_re System + +Anyone who tells you that mail can be safely delivered in mbox format +over NFS is pulling your leg---as explained above, mbox format is +inherently unreliable even on a single machine. + +Anyway, NFS is the most unreliable computing environment ever invented, +and qmail doesn't even pretend to support mbox over NFS. + +You should switch to maildir, which works fine over NFS without any +locking. You can safely read your mail over NFS if it's in maildir +format. Any number of machines can deliver mail to you at the same time. +(On the other hand, for efficiency, it's better to get NFS out of the +picture---your mail should be delivered on the server that contains your +home directory.) + +Here's how to set up qmail to use maildir for your incoming mail: + + % maildirmake $HOME/Maildir + % echo ./Maildir/ > ~/.qmail + +Make sure you include the trailing slash on Maildir/. + +Until your MUA supports maildir, you'll probably want to convert maildir +format to (gaaack) mbox format. I've supplied a maildir2mbox utility +that does the trick, along with some tiny qail and elq and pinq wrappers +that call maildir2mbox before calling Mail or elm or pine. diff --git a/INSTALL.qsmhook b/INSTALL.qsmhook @@ -0,0 +1,53 @@ +You can set up qmail to use the same local delivery agent as sendmail, +through a mechanism called qsmhook. This file says how. + +Before you do this, may I ask why? If you simply don't want the hassle +of moving user mailboxes from /usr/spool/mail to ~, please reconsider--- +/usr/spool/mail has always been a big security problem. See, for +example, CERT advisory 95:02. + +If you're trying to preserve /etc/aliases and ~user/.forward, you're +looking the wrong way---those are handled by sendmail internally, not by +the local delivery agent. You can use your old /etc/aliases with qmail +by installing the qmsmac package. + +Perhaps you've set up an advanced agent like procmail. But most people +use procmail for nothing more than sorting mail into several mailboxes; +and that's much easier with qmail's local forwarding mechanism, which +gives each user control over user-anything. If you have a few users who +really do need procmail, they can easily run procmail from their own +.qmail files. + +Do you still want to set up qsmhook? Send me some e-mail and let me know +why. Perhaps I can provide something for you in a future qmail release. + +Here's what to do. First, tack ``:alias'' onto the end of each address +in /var/qmail/control/locals, and put the results into +/var/qmail/control/virtualdomains. For example, if control/locals has + + localhost + silverton.berkeley.edu + +then control/virtualdomains should now have (without extra spaces) + + localhost:alias + silverton.berkeley.edu:alias + +Second, cp /dev/null control/locals. + +Third, put a line into ~alias/.qmail-default, based on sendmail's Mlocal +line. For example, if sendmail.cf has + + Mlocal, P=/bin/mail, F=lsDFMPrmn, S=10, R=20, A=mail -d $u + +then ~alias/.qmail-default should have + + |qsmhook -x alias- -lsDFMPmn /bin/mail -r %g -d %u + +As another example, if sendmail.cf has + + Mlocal, P=/usr/lib/mail.local, F=flsSDFMmnP, S=10, R=20, A=mail.local -d $u + +then ~alias/.qmail-default should have + + |qsmhook -x alias- -lsDFMmnP /usr/lib/mail.local -f %g -d %u diff --git a/INTERNALS b/INTERNALS @@ -0,0 +1,155 @@ +1. Overview + +Here's the data flow in the qmail suite: + + qmail-smtpd --- qmail-queue --- qmail-send --- qmail-rspawn --- qmail-remote + / | \ +qmail-inject _/ qmail-clean \_ qmail-lspawn --- qmail-local + +Every message is added to a central queue directory by qmail-queue. +qmail-queue is invoked as needed, usually by qmail-inject for locally +generated messages, qmail-smtpd for messages received through SMTP, +qmail-local for forwarded messages, or qmail-send for bounce messages. + +Every message is then delivered by qmail-send, in cooperation with +qmail-lspawn and qmail-rspawn, and cleaned up by qmail-clean. These four +programs are long-running daemons. + +The queue is designed to be crashproof, provided that the underlying +filesystem is crashproof. All cleanups are handled by qmail-send and +qmail-clean without human intervention. See section 6 for more details. + + +2. Queue structure + +Each message in the queue is identified by a unique number, let's say +457. The queue is organized into several directories, each of which may +contain files related to message 457: + + mess/457: the message + todo/457: the envelope: where the message came from, where it's going + intd/457: the envelope, under construction by qmail-queue + info/457: the envelope sender address, after preprocessing + local/457: local envelope recipient addresses, after preprocessing + remote/457: remote envelope recipient addresses, after preprocessing + bounce/457: permanent delivery errors + +Here are all possible states for a message. + means a file exists; - +means it does not exist; ? means it may or may not exist. + + S1. -mess -intd -todo -info -local -remote -bounce + S2. +mess -intd -todo -info -local -remote -bounce + S3. +mess +intd -todo -info -local -remote -bounce + S4. +mess ?intd +todo ?info ?local ?remote -bounce (queued) + S5. +mess -intd -todo +info ?local ?remote ?bounce (preprocessed) + +Guarantee: If mess/457 exists, it has inode number 457. + + +3. How messages enter the queue + +To add a message to the queue, qmail-queue first creates a file in a +separate directory, pid/, with a unique name. The filesystem assigns +that file a unique inode number. qmail-queue looks at that number, say +457. By the guarantee above, message 457 must be in state S1. + +qmail-queue renames pid/whatever as mess/457, moving to S2. It writes +the message to mess/457. It then creates intd/457, moving to S3, and +writes the envelope information to intd/457. + +Finally qmail-queue creates a new link, todo/457, for intd/457, moving +to S4. At that instant the message has been successfully queued, and +qmail-queue leaves it for further handling by qmail-send. + +qmail-queue starts a 24-hour timer before touching any files, and +commits suicide if the timer expires. + + +4. How queued messages are preprocessed + +Once a message has been queued, qmail-send must decide which recipients +are local and which recipients are remote. It may also rewrite some +recipient addresses. + +When qmail-send notices todo/457, it knows that message 457 is in S4. It +removes info/457, local/457, and remote/457 if they exist. Then it reads +through todo/457. It creates info/457, possibly local/457, and possibly +remote/457. When it is done, it removes intd/457. The message is still +in S4 at this point. Finally qmail-send removes todo/457, moving to S5. +At that instant the message has been successfully preprocessed. + + +5. How preprocessed messages are delivered + +Messages at S5 are handled as follows. Each address in local/457 and +remote/457 is marked either NOT DONE or DONE. + + DONE: The message was successfully delivered, or the last delivery + attempt met with permanent failure. Either way, qmail-send + should not attempt further delivery to this address. + + NOT DONE: If there have been any delivery attempts, they have all + met with temporary failure. Either way, qmail-send should + try delivery in the future. + +qmail-send may at its leisure try to deliver a message to a NOT DONE +address. If the message is successfully delivered, qmail-send marks the +address as DONE. If the delivery attempt meets with permanent failure, +qmail-send first appends a note to bounce/457, creating bounce/457 if +necessary; then it marks the address as DONE. + +qmail-send may handle bounce/457 at any time, as follows: it (1) injects +a new bounce message, created from bounce/457 and mess/457; (2) deletes +bounce/457. + +When all addresses in local/457 are DONE, qmail-send deletes local/457. +Same for remote/457. + +When local/457 and remote/457 are gone, qmail-send eliminates the +message, as follows. First, if bounce/457 exists, qmail-send handles it +as described above. Once bounce/457 is definitely gone, qmail-send +deletes info/457, moving to S2, and finally mess/457, moving to S1. + + +6. Cleanups + +If the computer crashes while qmail-queue is trying to queue a message, +or while qmail-send is eliminating a message, the message may be left in +state S2 or S3. + +When qmail-send sees a message in state S2 or S3---other than one +it is currently eliminating!---where mess/457 is more than 36 hours old, +it deletes intd/457 if that exists, then deletes mess/457. Note that any +qmail-queue handling the message must be dead. + +Similarly, when qmail-send sees a file in the pid/ directory that is +more than 36 hours old, it deletes it. + +Cleanups are not necessary if the computer crashes while qmail-send is +delivering a message. At worst a message may be delivered twice. (There +is no way for a distributed mail system to eliminate the possibility of +duplication. What if an SMTP connection is broken just before the server +acknowledges successful receipt of the message? The client must assume +the worst and send the message again. Similarly, if the computer crashes +just before qmail-send marks a message as DONE, the new qmail-send must +assume the worst and send the message again. The usual solutions in the +database literature---e.g., keeping log files---amount to saying that +it's the recipient's computer's job to discard duplicate messages.) + + +7. Further notes + +Currently info/457 serves two purposes: first, it records the envelope +sender; second, its modification time is used to decide when a message +has been in the queue too long. In the future info/457 may store more +information. Any non-backwards-compatible changes will be identified by +version numbers. + +When qmail-queue has successfully placed a message into the queue, it +pulls a trigger offered by qmail-send. Here is the current triggering +mechanism: lock/trigger is a named pipe. Before scanning todo/, +qmail-send opens lock/trigger O_NDELAY for reading. It then selects for +readability on lock/trigger. qmail-queue pulls the trigger by writing a +byte O_NDELAY to lock/trigger. This makes lock/trigger readable and +wakes up qmail-send. Before scanning todo/ again, qmail-send closes and +reopens lock/trigger. diff --git a/Makefile b/Makefile @@ -0,0 +1,2181 @@ +SHELL=/bin/sh + +default: it + +addresses.0: \ +addresses.5 + nroff -man addresses.5 > addresses.0 + +alloc.a: \ +makelib alloc.o alloc_re.o + ./makelib alloc.a alloc.o alloc_re.o + +alloc.o: \ +compile alloc.c alloc.h alloc.c error.h alloc.c + ./compile alloc.c + +alloc_re.o: \ +compile alloc_re.c alloc.h alloc_re.c byte.h alloc_re.c + ./compile alloc_re.c + +auto-ccld.sh: \ +conf-cc conf-ld warn-auto.sh + ( cat warn-auto.sh; \ + echo CC=\'`head -1 conf-cc`\'; \ + echo LD=\'`head -1 conf-ld`\' \ + ) > auto-ccld.sh + +auto-gid: \ +load auto-gid.o substdio.a error.a str.a fs.a + ./load auto-gid substdio.a error.a str.a fs.a + +auto-gid.o: \ +compile auto-gid.c auto-gid.c auto-gid.c subfd.h substdio.h subfd.h \ +auto-gid.c substdio.h substdio.h auto-gid.c readwrite.h auto-gid.c \ +exit.h auto-gid.c scan.h auto-gid.c fmt.h auto-gid.c + ./compile auto-gid.c + +auto-int: \ +load auto-int.o substdio.a error.a str.a fs.a + ./load auto-int substdio.a error.a str.a fs.a + +auto-int.o: \ +compile auto-int.c substdio.h auto-int.c readwrite.h auto-int.c \ +exit.h auto-int.c scan.h auto-int.c fmt.h auto-int.c + ./compile auto-int.c + +auto-int8: \ +load auto-int8.o substdio.a error.a str.a fs.a + ./load auto-int8 substdio.a error.a str.a fs.a + +auto-int8.o: \ +compile auto-int8.c substdio.h auto-int8.c readwrite.h auto-int8.c \ +exit.h auto-int8.c scan.h auto-int8.c fmt.h auto-int8.c + ./compile auto-int8.c + +auto-str: \ +load auto-str.o substdio.a error.a str.a + ./load auto-str substdio.a error.a str.a + +auto-str.o: \ +compile auto-str.c substdio.h auto-str.c readwrite.h auto-str.c \ +exit.h auto-str.c + ./compile auto-str.c + +auto-uid: \ +load auto-uid.o substdio.a error.a str.a fs.a + ./load auto-uid substdio.a error.a str.a fs.a + +auto-uid.o: \ +compile auto-uid.c auto-uid.c auto-uid.c subfd.h substdio.h subfd.h \ +auto-uid.c substdio.h substdio.h auto-uid.c readwrite.h auto-uid.c \ +exit.h auto-uid.c scan.h auto-uid.c fmt.h auto-uid.c + ./compile auto-uid.c + +auto_break.c: \ +auto-str conf-break + ./auto-str auto_break \ + "`head -1 conf-break`" > auto_break.c + +auto_break.o: \ +compile auto_break.c + ./compile auto_break.c + +auto_patrn.c: \ +auto-int8 conf-patrn + ./auto-int8 auto_patrn `head -1 conf-patrn` > auto_patrn.c + +auto_patrn.o: \ +compile auto_patrn.c + ./compile auto_patrn.c + +auto_qmail.c: \ +auto-str conf-qmail + ./auto-str auto_qmail `head -1 conf-qmail` > auto_qmail.c + +auto_qmail.o: \ +compile auto_qmail.c + ./compile auto_qmail.c + +auto_spawn.c: \ +auto-int conf-spawn + ./auto-int auto_spawn `head -1 conf-spawn` > auto_spawn.c + +auto_spawn.o: \ +compile auto_spawn.c + ./compile auto_spawn.c + +auto_split.c: \ +auto-int conf-split + ./auto-int auto_split `head -1 conf-split` > auto_split.c + +auto_split.o: \ +compile auto_split.c + ./compile auto_split.c + +auto_uids.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` \ + &&./auto-uid auto_uidl `head -3 conf-users | tail -1` \ + &&./auto-uid auto_uido `head -4 conf-users | tail -1` \ + &&./auto-uid auto_uidp `head -5 conf-users | tail -1` \ + &&./auto-uid auto_uidq `head -6 conf-users | tail -1` \ + &&./auto-uid auto_uidr `head -7 conf-users | tail -1` \ + &&./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 + +auto_uids.o: \ +compile auto_uids.c + ./compile auto_uids.c + +auto_usera.c: \ +auto-str conf-users + ./auto-str auto_usera `head -1 conf-users` > auto_usera.c + +auto_usera.o: \ +compile auto_usera.c + ./compile auto_usera.c + +byte_chr.o: \ +compile byte_chr.c byte.h byte_chr.c + ./compile byte_chr.c + +byte_copy.o: \ +compile byte_copy.c byte.h byte_copy.c + ./compile byte_copy.c + +byte_cr.o: \ +compile byte_cr.c byte.h byte_cr.c + ./compile byte_cr.c + +byte_diff.o: \ +compile byte_diff.c byte.h byte_diff.c + ./compile byte_diff.c + +byte_rchr.o: \ +compile byte_rchr.c byte.h byte_rchr.c + ./compile byte_rchr.c + +byte_zero.o: \ +compile byte_zero.c byte.h byte_zero.c + ./compile byte_zero.c + +case.a: \ +makelib case_diffb.o case_diffs.o case_lowerb.o case_lowers.o \ +case_starts.o + ./makelib case.a case_diffb.o case_diffs.o case_lowerb.o \ + case_lowers.o case_starts.o + +case_diffb.o: \ +compile case_diffb.c case.h case_diffb.c + ./compile case_diffb.c + +case_diffs.o: \ +compile case_diffs.c case.h case_diffs.c + ./compile case_diffs.c + +case_lowerb.o: \ +compile case_lowerb.c case.h case_lowerb.c + ./compile case_lowerb.c + +case_lowers.o: \ +compile case_lowers.c case.h case_lowers.c + ./compile case_lowers.c + +case_starts.o: \ +compile case_starts.c case.h case_starts.c + ./compile case_starts.c + +cdb.a: \ +makelib cdb_hash.o cdb_unpack.o cdb_seek.o + ./makelib cdb.a cdb_hash.o cdb_unpack.o cdb_seek.o + +cdb_hash.o: \ +compile cdb_hash.c cdb.h uint32.h cdb.h cdb_hash.c + ./compile cdb_hash.c + +cdb_seek.o: \ +compile cdb_seek.c cdb_seek.c cdb_seek.c cdb.h uint32.h cdb.h \ +cdb_seek.c + ./compile cdb_seek.c + +cdb_unpack.o: \ +compile cdb_unpack.c cdb.h uint32.h cdb.h cdb_unpack.c + ./compile cdb_unpack.c + +cdbmake.a: \ +makelib cdbmake_pack.o cdbmake_hash.o cdbmake_add.o + ./makelib cdbmake.a cdbmake_pack.o cdbmake_hash.o \ + cdbmake_add.o + +cdbmake_add.o: \ +compile cdbmake_add.c cdbmake.h uint32.h cdbmake.h cdbmake_add.c + ./compile cdbmake_add.c + +cdbmake_hash.o: \ +compile cdbmake_hash.c cdbmake.h uint32.h cdbmake.h cdbmake_hash.c + ./compile cdbmake_hash.c + +cdbmake_pack.o: \ +compile cdbmake_pack.c cdbmake.h uint32.h cdbmake.h cdbmake_pack.c + ./compile cdbmake_pack.c + +cdbmss.o: \ +compile cdbmss.c readwrite.h cdbmss.c seek.h cdbmss.c alloc.h \ +cdbmss.c cdbmss.h cdbmake.h uint32.h cdbmake.h cdbmss.h substdio.h \ +cdbmss.h cdbmss.c + ./compile cdbmss.c + +check: \ +it man conf-qmail + ./qmail-hier | ./instcheck `head -1 conf-qmail` + +chkshsgr: \ +load chkshsgr.o + ./load chkshsgr + +chkshsgr.o: \ +compile chkshsgr.c exit.h chkshsgr.c + ./compile chkshsgr.c + +chkspawn: \ +load chkspawn.o substdio.a error.a str.a fs.a auto_spawn.o + ./load chkspawn substdio.a error.a str.a fs.a auto_spawn.o + +chkspawn.o: \ +compile chkspawn.c substdio.h chkspawn.c subfd.h substdio.h \ +substdio.h subfd.h chkspawn.c fmt.h chkspawn.c select.h select.h \ +select.h select.h chkspawn.c exit.h chkspawn.c auto_spawn.h \ +chkspawn.c + ./compile chkspawn.c + +clean: \ +TARGETS + rm -f `cat TARGETS` + +coe.o: \ +compile coe.c coe.c coe.h coe.c + ./compile coe.c + +compile: \ +make-compile warn-auto.sh systype + ( cat warn-auto.sh; ./make-compile "`cat systype`" ) > \ + compile + chmod 755 compile + +condredirect: \ +load condredirect.o qmail.o fd.a sig.a wait.a seek.a env.a alloc.a \ +substdio.a error.a str.a auto_qmail.o + ./load condredirect qmail.o fd.a sig.a wait.a seek.a env.a \ + alloc.a substdio.a error.a str.a auto_qmail.o + +condredirect.0: \ +condredirect.1 + nroff -man condredirect.1 > condredirect.0 + +condredirect.o: \ +compile condredirect.c sig.h condredirect.c readwrite.h \ +condredirect.c exit.h condredirect.c env.h condredirect.c error.h \ +condredirect.c fork.h condredirect.c wait.h condredirect.c seek.h \ +condredirect.c qmail.h substdio.h qmail.h condredirect.c stralloc.h \ +gen_alloc.h stralloc.h condredirect.c subfd.h substdio.h substdio.h \ +subfd.h condredirect.c substdio.h substdio.h condredirect.c + ./compile condredirect.c + +constmap.o: \ +compile constmap.c constmap.h constmap.c alloc.h constmap.c case.h \ +constmap.c + ./compile constmap.c + +control.o: \ +compile control.c readwrite.h control.c open.h control.c getln.h \ +control.c stralloc.h gen_alloc.h stralloc.h control.c substdio.h \ +control.c error.h control.c control.h control.c alloc.h control.c \ +scan.h control.c + ./compile control.c + +date822fmt.o: \ +compile date822fmt.c datetime.h date822fmt.c fmt.h date822fmt.c \ +date822fmt.h date822fmt.c + ./compile date822fmt.c + +datemail: \ +warn-auto.sh datemail.sh conf-qmail conf-break conf-split + cat warn-auto.sh datemail.sh \ + | sed s}QMAIL}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPLIT}"`head -1 conf-split`"}g \ + > datemail + chmod 755 datemail + +datetime.a: \ +makelib datetime.o datetime_un.o + ./makelib datetime.a datetime.o datetime_un.o + +datetime.o: \ +compile datetime.c datetime.h datetime.c + ./compile datetime.c + +datetime_un.o: \ +compile datetime_un.c datetime.h datetime_un.c + ./compile datetime_un.c + +direntry.h: \ +compile trydrent.c direntry.h1 direntry.h2 + ( ./compile trydrent.c >/dev/null 2>&1 \ + && cat direntry.h2 || cat direntry.h1 ) > direntry.h + rm -f trydrent.o + +dns.lib: \ +tryrsolv.c compile load socket.lib dns.o ipalloc.o ip.o stralloc.a \ +alloc.a error.a fs.a str.a + ( ( ./compile tryrsolv.c && ./load tryrsolv dns.o \ + ipalloc.o ip.o stralloc.a alloc.a error.a fs.a str.a \ + -lresolv `cat socket.lib` ) >/dev/null 2>&1 \ + && echo -lresolv || exit 0 ) > dns.lib + rm -f tryrsolv.o tryrsolv + +dns.o: \ +compile dns.c dns.c dns.c dns.c dns.c dns.c dns.c dns.c ip.h dns.c \ +ipalloc.h ip.h ip.h ipalloc.h gen_alloc.h ipalloc.h dns.c fmt.h dns.c \ +alloc.h dns.c str.h dns.c stralloc.h gen_alloc.h stralloc.h dns.c \ +dns.h dns.c case.h dns.c + ./compile dns.c + +dnscname: \ +load dnscname.o dns.o dnsdoe.o ip.o ipalloc.o stralloc.a alloc.a \ +substdio.a error.a str.a fs.a dns.lib socket.lib + ./load dnscname dns.o dnsdoe.o ip.o ipalloc.o stralloc.a \ + alloc.a substdio.a error.a str.a fs.a `cat dns.lib` `cat \ + socket.lib` + +dnscname.o: \ +compile dnscname.c substdio.h dnscname.c subfd.h substdio.h \ +substdio.h subfd.h dnscname.c stralloc.h gen_alloc.h stralloc.h \ +dnscname.c dns.h dnscname.c dnsdoe.h dnscname.c readwrite.h \ +dnscname.c exit.h dnscname.c + ./compile dnscname.c + +dnsdoe.o: \ +compile dnsdoe.c substdio.h dnsdoe.c subfd.h substdio.h substdio.h \ +subfd.h dnsdoe.c exit.h dnsdoe.c dns.h dnsdoe.c dnsdoe.h dnsdoe.c + ./compile dnsdoe.c + +dnsfq: \ +load dnsfq.o dns.o dnsdoe.o ip.o ipalloc.o stralloc.a alloc.a \ +substdio.a error.a str.a fs.a dns.lib socket.lib + ./load dnsfq dns.o dnsdoe.o ip.o ipalloc.o stralloc.a \ + alloc.a substdio.a error.a str.a fs.a `cat dns.lib` `cat \ + socket.lib` + +dnsfq.o: \ +compile dnsfq.c substdio.h dnsfq.c subfd.h substdio.h substdio.h \ +subfd.h dnsfq.c stralloc.h gen_alloc.h stralloc.h dnsfq.c dns.h \ +dnsfq.c dnsdoe.h dnsfq.c ip.h dnsfq.c ipalloc.h ip.h ip.h ipalloc.h \ +gen_alloc.h ipalloc.h dnsfq.c exit.h dnsfq.c + ./compile dnsfq.c + +dnsip: \ +load dnsip.o dns.o dnsdoe.o ip.o ipalloc.o stralloc.a alloc.a \ +substdio.a error.a str.a fs.a dns.lib socket.lib + ./load dnsip dns.o dnsdoe.o ip.o ipalloc.o stralloc.a \ + alloc.a substdio.a error.a str.a fs.a `cat dns.lib` `cat \ + socket.lib` + +dnsip.o: \ +compile dnsip.c substdio.h dnsip.c subfd.h substdio.h substdio.h \ +subfd.h dnsip.c stralloc.h gen_alloc.h stralloc.h dnsip.c dns.h \ +dnsip.c dnsdoe.h dnsip.c ip.h dnsip.c ipalloc.h ip.h ip.h ipalloc.h \ +gen_alloc.h ipalloc.h dnsip.c exit.h dnsip.c + ./compile dnsip.c + +dnsmxip: \ +load dnsmxip.o dns.o dnsdoe.o ip.o ipalloc.o now.o stralloc.a alloc.a \ +substdio.a error.a str.a fs.a dns.lib socket.lib + ./load dnsmxip dns.o dnsdoe.o ip.o ipalloc.o now.o \ + stralloc.a alloc.a substdio.a error.a str.a fs.a `cat \ + dns.lib` `cat socket.lib` + +dnsmxip.o: \ +compile dnsmxip.c substdio.h dnsmxip.c subfd.h substdio.h substdio.h \ +subfd.h dnsmxip.c stralloc.h gen_alloc.h stralloc.h dnsmxip.c fmt.h \ +dnsmxip.c dns.h dnsmxip.c dnsdoe.h dnsmxip.c ip.h dnsmxip.c ipalloc.h \ +ip.h ip.h ipalloc.h gen_alloc.h ipalloc.h dnsmxip.c now.h datetime.h \ +now.h dnsmxip.c exit.h dnsmxip.c + ./compile dnsmxip.c + +dnsptr: \ +load dnsptr.o dns.o dnsdoe.o ip.o ipalloc.o stralloc.a alloc.a \ +substdio.a error.a str.a fs.a dns.lib socket.lib + ./load dnsptr dns.o dnsdoe.o ip.o ipalloc.o stralloc.a \ + alloc.a substdio.a error.a str.a fs.a `cat dns.lib` `cat \ + socket.lib` + +dnsptr.o: \ +compile dnsptr.c substdio.h dnsptr.c subfd.h substdio.h substdio.h \ +subfd.h dnsptr.c stralloc.h gen_alloc.h stralloc.h dnsptr.c str.h \ +dnsptr.c scan.h dnsptr.c dns.h dnsptr.c dnsdoe.h dnsptr.c ip.h \ +dnsptr.c exit.h dnsptr.c + ./compile dnsptr.c + +dot-qmail.0: \ +dot-qmail.5 + nroff -man dot-qmail.5 > dot-qmail.0 + +dot-qmail.5: \ +dot-qmail.9 conf-break conf-spawn + cat dot-qmail.9 \ + | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPAWN}"`head -1 conf-spawn`"}g \ + > dot-qmail.5 + +elq: \ +warn-auto.sh elq.sh conf-qmail conf-break conf-split + cat warn-auto.sh elq.sh \ + | sed s}QMAIL}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPLIT}"`head -1 conf-split`"}g \ + > elq + chmod 755 elq + +env.a: \ +makelib env.o envread.o + ./makelib env.a env.o envread.o + +env.o: \ +compile env.c str.h env.c alloc.h env.c env.h env.c + ./compile env.c + +envelopes.0: \ +envelopes.5 + nroff -man envelopes.5 > envelopes.0 + +envread.o: \ +compile envread.c env.h envread.c str.h envread.c + ./compile envread.c + +error.a: \ +makelib error.o error_str.o error_temp.o + ./makelib error.a error.o error_str.o error_temp.o + +error.o: \ +compile error.c error.c error.h error.c + ./compile error.c + +error_str.o: \ +compile error_str.c error_str.c error.h error_str.c + ./compile error_str.c + +error_temp.o: \ +compile error_temp.c error_temp.c error.h error_temp.c + ./compile error_temp.c + +fd.a: \ +makelib fd_copy.o fd_move.o + ./makelib fd.a fd_copy.o fd_move.o + +fd_copy.o: \ +compile fd_copy.c fd_copy.c fd.h fd_copy.c + ./compile fd_copy.c + +fd_move.o: \ +compile fd_move.c fd.h fd_move.c + ./compile fd_move.c + +fifo.o: \ +compile fifo.c fifo.c fifo.c hasmkffo.h fifo.c fifo.h fifo.c + ./compile fifo.c + +find-systype: \ +find-systype.sh auto-ccld.sh + cat auto-ccld.sh find-systype.sh > find-systype + chmod 755 find-systype + +fmt_str.o: \ +compile fmt_str.c fmt.h fmt_str.c + ./compile fmt_str.c + +fmt_strn.o: \ +compile fmt_strn.c fmt.h fmt_strn.c + ./compile fmt_strn.c + +fmt_uint.o: \ +compile fmt_uint.c fmt.h fmt_uint.c + ./compile fmt_uint.c + +fmt_uint0.o: \ +compile fmt_uint0.c fmt.h fmt_uint0.c + ./compile fmt_uint0.c + +fmt_ulong.o: \ +compile fmt_ulong.c fmt.h fmt_ulong.c + ./compile fmt_ulong.c + +fmtqfn.o: \ +compile fmtqfn.c fmtqfn.h fmtqfn.c fmt.h fmtqfn.c auto_split.h \ +fmtqfn.c + ./compile fmtqfn.c + +forgeries.0: \ +forgeries.7 + nroff -man forgeries.7 > forgeries.0 + +fork.h: \ +compile load tryvfork.c fork.h1 fork.h2 + ( ( ./compile tryvfork.c && ./load tryvfork ) >/dev/null \ + 2>&1 \ + && cat fork.h2 || cat fork.h1 ) > fork.h + rm -f tryvfork.o tryvfork + +forward: \ +load forward.o stralloc.a alloc.a qmail.o fd.a wait.a sig.a env.a \ +substdio.a error.a str.a auto_qmail.o + ./load forward stralloc.a alloc.a qmail.o fd.a wait.a \ + sig.a env.a substdio.a error.a str.a auto_qmail.o + +forward.0: \ +forward.1 + nroff -man forward.1 > forward.0 + +forward.o: \ +compile forward.c sig.h forward.c readwrite.h forward.c exit.h \ +forward.c env.h forward.c qmail.h substdio.h qmail.h forward.c \ +stralloc.h gen_alloc.h stralloc.h forward.c subfd.h substdio.h \ +substdio.h subfd.h forward.c substdio.h substdio.h forward.c + ./compile forward.c + +fs.a: \ +makelib fmt_str.o fmt_strn.o fmt_uint.o fmt_uint0.o fmt_ulong.o \ +scan_ulong.o scan_8long.o scan_nbblong.o + ./makelib fs.a fmt_str.o fmt_strn.o fmt_uint.o fmt_uint0.o \ + fmt_ulong.o scan_ulong.o scan_8long.o scan_nbblong.o + +getln.a: \ +makelib getln.o getln2.o + ./makelib getln.a getln.o getln2.o + +getln.o: \ +compile getln.c substdio.h getln.c byte.h getln.c stralloc.h \ +gen_alloc.h stralloc.h getln.c getln.h getln.c + ./compile getln.c + +getln2.o: \ +compile getln2.c substdio.h getln2.c stralloc.h gen_alloc.h \ +stralloc.h getln2.c byte.h getln2.c getln.h getln2.c + ./compile getln2.c + +getopt.a: \ +makelib subgetopt.o sgetopt.o + ./makelib getopt.a subgetopt.o sgetopt.o + +gfrom.o: \ +compile gfrom.c str.h gfrom.c gfrom.h gfrom.c + ./compile gfrom.c + +hasflock.h: \ +tryflock.c compile load + ( ( ./compile tryflock.c && ./load tryflock ) >/dev/null \ + 2>&1 \ + && echo \#define HASFLOCK 1 || exit 0 ) > hasflock.h + rm -f tryflock.o tryflock + +hasmkffo.h: \ +trymkffo.c compile load + ( ( ./compile trymkffo.c && ./load trymkffo ) >/dev/null \ + 2>&1 \ + && echo \#define HASMKFIFO 1 || exit 0 ) > hasmkffo.h + rm -f trymkffo.o trymkffo + +hasnpbg1.h: \ +trynpbg1.c compile load open.h open.a fifo.h fifo.o select.h + ( ( ./compile trynpbg1.c \ + && ./load trynpbg1 fifo.o open.a && ./trynpbg1 ) \ + >/dev/null 2>&1 \ + && echo \#define HASNAMEDPIPEBUG1 1 || exit 0 ) > \ + hasnpbg1.h + rm -f trynpbg1.o trynpbg1 + +hassalen.h: \ +trysalen.c compile + ( ./compile trysalen.c >/dev/null 2>&1 \ + && echo \#define HASSALEN 1 || exit 0 ) > hassalen.h + rm -f trysalen.o + +hassgact.h: \ +trysgact.c compile load + ( ( ./compile trysgact.c && ./load trysgact ) >/dev/null \ + 2>&1 \ + && echo \#define HASSIGACTION 1 || exit 0 ) > hassgact.h + rm -f trysgact.o trysgact + +hassgprm.h: \ +trysgprm.c compile load + ( ( ./compile trysgprm.c && ./load trysgprm ) >/dev/null \ + 2>&1 \ + && echo \#define HASSIGPROCMASK 1 || exit 0 ) > hassgprm.h + rm -f trysgprm.o trysgprm + +hasshsgr.h: \ +chkshsgr warn-shsgr tryshsgr.c compile load + ./chkshsgr || ( cat warn-shsgr; exit 1 ) + ( ( ./compile tryshsgr.c \ + && ./load tryshsgr && ./tryshsgr ) >/dev/null 2>&1 \ + && echo \#define HASSHORTSETGROUPS 1 || exit 0 ) > \ + hasshsgr.h + rm -f tryshsgr.o tryshsgr + +haswaitp.h: \ +trywaitp.c compile load + ( ( ./compile trywaitp.c && ./load trywaitp ) >/dev/null \ + 2>&1 \ + && echo \#define HASWAITPID 1 || exit 0 ) > haswaitp.h + rm -f trywaitp.o trywaitp + +headerbody.o: \ +compile headerbody.c stralloc.h gen_alloc.h stralloc.h headerbody.c \ +substdio.h headerbody.c getln.h headerbody.c hfield.h headerbody.c \ +headerbody.h headerbody.c + ./compile headerbody.c + +hfield.o: \ +compile hfield.c hfield.h hfield.c + ./compile hfield.c + +hostname: \ +load hostname.o substdio.a error.a str.a dns.lib socket.lib + ./load hostname substdio.a error.a str.a `cat dns.lib` \ + `cat socket.lib` + +hostname.o: \ +compile hostname.c substdio.h hostname.c subfd.h substdio.h \ +substdio.h subfd.h hostname.c readwrite.h hostname.c exit.h \ +hostname.c + ./compile hostname.c + +install: \ +load install.o fifo.o getln.a strerr.a substdio.a stralloc.a alloc.a \ +open.a error.a str.a fs.a + ./load install fifo.o getln.a strerr.a substdio.a \ + stralloc.a alloc.a open.a error.a str.a fs.a + +install.o: \ +compile install.c substdio.h install.c stralloc.h gen_alloc.h \ +stralloc.h install.c getln.h install.c readwrite.h install.c exit.h \ +install.c open.h install.c error.h install.c strerr.h install.c \ +byte.h install.c fifo.h install.c + ./compile install.c + +instcheck: \ +load instcheck.o getln.a strerr.a substdio.a stralloc.a alloc.a \ +error.a str.a fs.a + ./load instcheck getln.a strerr.a substdio.a stralloc.a \ + alloc.a error.a str.a fs.a + +instcheck.o: \ +compile instcheck.c instcheck.c instcheck.c substdio.h instcheck.c \ +stralloc.h gen_alloc.h stralloc.h instcheck.c getln.h instcheck.c \ +readwrite.h instcheck.c exit.h instcheck.c error.h instcheck.c \ +strerr.h instcheck.c byte.h instcheck.c + ./compile instcheck.c + +ip.o: \ +compile ip.c fmt.h ip.c scan.h ip.c ip.h ip.c + ./compile ip.c + +ipalloc.o: \ +compile ipalloc.c alloc.h ipalloc.c gen_allocdefs.h gen_allocdefs.h \ +gen_allocdefs.h ipalloc.c ip.h ipalloc.c ipalloc.h ip.h ip.h \ +ipalloc.h gen_alloc.h ipalloc.h ipalloc.c + ./compile ipalloc.c + +ipme.o: \ +compile ipme.c ipme.c ipme.c ipme.c ipme.c ipme.c ipme.c ipme.c \ +hassalen.h ipme.c byte.h ipme.c ip.h ipme.c ipalloc.h ip.h ip.h \ +ipalloc.h gen_alloc.h ipalloc.h ipme.c stralloc.h gen_alloc.h \ +stralloc.h ipme.c ipme.h ip.h ip.h ipme.h ipalloc.h ipalloc.h ipme.h \ +ipme.c ipme.c + ./compile ipme.c + +ipmeprint: \ +load ipmeprint.o ipme.o ip.o ipalloc.o stralloc.a alloc.a substdio.a \ +error.a str.a fs.a socket.lib + ./load ipmeprint ipme.o ip.o ipalloc.o stralloc.a alloc.a \ + substdio.a error.a str.a fs.a `cat socket.lib` + +ipmeprint.o: \ +compile ipmeprint.c subfd.h substdio.h subfd.h ipmeprint.c substdio.h \ +substdio.h ipmeprint.c ip.h ipmeprint.c ipme.h ip.h ip.h ipme.h \ +ipalloc.h ip.h ip.h ipalloc.h gen_alloc.h ipalloc.h ipme.h \ +ipmeprint.c exit.h ipmeprint.c + ./compile ipmeprint.c + +it: \ +qmail-local qmail-lspawn qmail-getpw qmail-remote qmail-rspawn \ +qmail-clean qmail-send qmail-start splogger qmail-queue qmail-inject \ +predate datemail mailsubj qmail-upq qmail-config qmail-showctl \ +qmail-newu qmail-pw2u qmail-qread qmail-qstat qmail-tcpto qmail-pop3d \ +qmail-popup qmail-qmtpd qmail-smtpd sendmail tcp-env dnscname dnsptr \ +dnsip dnsmxip dnsfq hostname ipmeprint qlist qlist2 qreceipt qsmhook \ +qbiff forward preline condredirect maildirmake maildir2mbox \ +maildirwatch qail elq pinq qmail-hier install instcheck + +load: \ +make-load warn-auto.sh systype + ( cat warn-auto.sh; ./make-load "`cat systype`" ) > load + chmod 755 load + +lock.a: \ +makelib lock_ex.o lock_exnb.o lock_un.o + ./makelib lock.a lock_ex.o lock_exnb.o lock_un.o + +lock_ex.o: \ +compile lock_ex.c lock_ex.c lock_ex.c lock_ex.c hasflock.h lock_ex.c \ +lock.h lock_ex.c + ./compile lock_ex.c + +lock_exnb.o: \ +compile lock_exnb.c lock_exnb.c lock_exnb.c lock_exnb.c hasflock.h \ +lock_exnb.c lock.h lock_exnb.c + ./compile lock_exnb.c + +lock_un.o: \ +compile lock_un.c lock_un.c lock_un.c lock_un.c hasflock.h lock_un.c \ +lock.h lock_un.c + ./compile lock_un.c + +maildir.0: \ +maildir.5 + nroff -man maildir.5 > maildir.0 + +maildir.o: \ +compile maildir.c maildir.c maildir.c prioq.h datetime.h prioq.h \ +gen_alloc.h prioq.h maildir.c env.h maildir.c stralloc.h gen_alloc.h \ +stralloc.h maildir.c direntry.h direntry.h direntry.h maildir.c \ +datetime.h datetime.h maildir.c now.h datetime.h datetime.h now.h \ +maildir.c str.h maildir.c maildir.h strerr.h maildir.h maildir.c + ./compile maildir.c + +maildir2mbox: \ +load maildir2mbox.o maildir.o prioq.o now.o myctime.o gfrom.o lock.a \ +getln.a env.a open.a strerr.a stralloc.a alloc.a substdio.a error.a \ +str.a fs.a datetime.a + ./load maildir2mbox maildir.o prioq.o now.o myctime.o \ + gfrom.o lock.a getln.a env.a open.a strerr.a stralloc.a \ + alloc.a substdio.a error.a str.a fs.a datetime.a + +maildir2mbox.0: \ +maildir2mbox.1 + nroff -man maildir2mbox.1 > maildir2mbox.0 + +maildir2mbox.o: \ +compile maildir2mbox.c readwrite.h maildir2mbox.c prioq.h datetime.h \ +prioq.h gen_alloc.h prioq.h maildir2mbox.c env.h maildir2mbox.c \ +stralloc.h gen_alloc.h stralloc.h maildir2mbox.c subfd.h substdio.h \ +subfd.h maildir2mbox.c substdio.h substdio.h maildir2mbox.c getln.h \ +maildir2mbox.c error.h maildir2mbox.c open.h maildir2mbox.c lock.h \ +maildir2mbox.c gfrom.h maildir2mbox.c str.h maildir2mbox.c exit.h \ +maildir2mbox.c myctime.h maildir2mbox.c maildir.h strerr.h maildir.h \ +maildir2mbox.c + ./compile maildir2mbox.c + +maildirmake: \ +load maildirmake.o substdio.a error.a str.a + ./load maildirmake substdio.a error.a str.a + +maildirmake.0: \ +maildirmake.1 + nroff -man maildirmake.1 > maildirmake.0 + +maildirmake.o: \ +compile maildirmake.c subfd.h substdio.h subfd.h maildirmake.c \ +substdio.h substdio.h maildirmake.c error.h maildirmake.c exit.h \ +maildirmake.c + ./compile maildirmake.c + +maildirwatch: \ +load maildirwatch.o hfield.o headerbody.o maildir.o prioq.o now.o \ +getln.a env.a open.a strerr.a stralloc.a alloc.a substdio.a error.a \ +str.a + ./load maildirwatch hfield.o headerbody.o maildir.o \ + prioq.o now.o getln.a env.a open.a strerr.a stralloc.a \ + alloc.a substdio.a error.a str.a + +maildirwatch.0: \ +maildirwatch.1 + nroff -man maildirwatch.1 > maildirwatch.0 + +maildirwatch.o: \ +compile maildirwatch.c getln.h maildirwatch.c substdio.h \ +maildirwatch.c subfd.h substdio.h substdio.h subfd.h maildirwatch.c \ +prioq.h datetime.h prioq.h gen_alloc.h prioq.h maildirwatch.c \ +stralloc.h gen_alloc.h stralloc.h maildirwatch.c str.h maildirwatch.c \ +exit.h maildirwatch.c hfield.h maildirwatch.c readwrite.h \ +maildirwatch.c open.h maildirwatch.c headerbody.h maildirwatch.c \ +maildir.h strerr.h maildir.h maildirwatch.c + ./compile maildirwatch.c + +mailsubj: \ +warn-auto.sh mailsubj.sh conf-qmail conf-break conf-split + cat warn-auto.sh mailsubj.sh \ + | sed s}QMAIL}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPLIT}"`head -1 conf-split`"}g \ + > mailsubj + chmod 755 mailsubj + +mailsubj.0: \ +mailsubj.1 + nroff -man mailsubj.1 > mailsubj.0 + +make-compile: \ +make-compile.sh auto-ccld.sh + cat auto-ccld.sh make-compile.sh > make-compile + chmod 755 make-compile + +make-load: \ +make-load.sh auto-ccld.sh + cat auto-ccld.sh make-load.sh > make-load + chmod 755 make-load + +make-makelib: \ +make-makelib.sh auto-ccld.sh + cat auto-ccld.sh make-makelib.sh > make-makelib + chmod 755 make-makelib + +makelib: \ +make-makelib warn-auto.sh systype + ( cat warn-auto.sh; ./make-makelib "`cat systype`" ) > \ + makelib + chmod 755 makelib + +man: \ +qmail-local.0 qmail-lspawn.0 qmail-getpw.0 qmail-remote.0 \ +qmail-rspawn.0 qmail-clean.0 qmail-send.0 qmail-start.0 splogger.0 \ +qmail-queue.0 qmail-inject.0 mailsubj.0 qmail-showctl.0 qmail-newu.0 \ +qmail-pw2u.0 qmail-qread.0 qmail-qstat.0 qmail-tcpto.0 qmail-pop3d.0 \ +qmail-popup.0 qmail-qmtpd.0 qmail-smtpd.0 tcp-env.0 qlist.0 \ +qreceipt.0 qbiff.0 forward.0 preline.0 condredirect.0 maildirmake.0 \ +maildir2mbox.0 maildirwatch.0 qmail.0 qmail-upgrade.0 qmail-limits.0 \ +qmail-log.0 qmail-control.0 qmail-header.0 qmail-users.0 dot-qmail.0 \ +qmail-command.0 tcp-environ.0 maildir.0 mbox.0 addresses.0 \ +envelopes.0 forgeries.0 + +mbox.0: \ +mbox.5 + nroff -man mbox.5 > mbox.0 + +myctime.o: \ +compile myctime.c datetime.h myctime.c fmt.h myctime.c myctime.h \ +myctime.c + ./compile myctime.c + +ndelay.a: \ +makelib ndelay.o ndelay_off.o + ./makelib ndelay.a ndelay.o ndelay_off.o + +ndelay.o: \ +compile ndelay.c ndelay.c ndelay.c ndelay.h ndelay.c + ./compile ndelay.c + +ndelay_off.o: \ +compile ndelay_off.c ndelay_off.c ndelay_off.c ndelay.h ndelay_off.c + ./compile ndelay_off.c + +newfield.o: \ +compile newfield.c fmt.h newfield.c datetime.h newfield.c stralloc.h \ +gen_alloc.h stralloc.h newfield.c date822fmt.h newfield.c newfield.h \ +stralloc.h stralloc.h newfield.h newfield.c + ./compile newfield.c + +now.o: \ +compile now.c now.c datetime.h now.c now.h datetime.h datetime.h \ +now.h now.c + ./compile now.c + +open.a: \ +makelib open_append.o open_excl.o open_read.o open_trunc.o \ +open_write.o + ./makelib open.a open_append.o open_excl.o open_read.o \ + open_trunc.o open_write.o + +open_append.o: \ +compile open_append.c open_append.c open_append.c open.h \ +open_append.c + ./compile open_append.c + +open_excl.o: \ +compile open_excl.c open_excl.c open_excl.c open.h open_excl.c + ./compile open_excl.c + +open_read.o: \ +compile open_read.c open_read.c open_read.c open.h open_read.c + ./compile open_read.c + +open_trunc.o: \ +compile open_trunc.c open_trunc.c open_trunc.c open.h open_trunc.c + ./compile open_trunc.c + +open_write.o: \ +compile open_write.c open_write.c open_write.c open.h open_write.c + ./compile open_write.c + +pinq: \ +warn-auto.sh pinq.sh conf-qmail conf-break conf-split + cat warn-auto.sh pinq.sh \ + | sed s}QMAIL}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPLIT}"`head -1 conf-split`"}g \ + > pinq + chmod 755 pinq + +predate: \ +load predate.o datetime.a sig.a fd.a wait.a substdio.a error.a str.a \ +fs.a + ./load predate datetime.a sig.a fd.a wait.a substdio.a \ + error.a str.a fs.a + +predate.o: \ +compile predate.c predate.c predate.c datetime.h predate.c fork.h \ +predate.c wait.h predate.c fd.h predate.c fmt.h predate.c substdio.h \ +predate.c subfd.h substdio.h substdio.h subfd.h predate.c readwrite.h \ +predate.c exit.h predate.c + ./compile predate.c + +preline: \ +load preline.o fd.a wait.a sig.a env.a getopt.a substdio.a error.a \ +str.a + ./load preline fd.a wait.a sig.a env.a getopt.a substdio.a \ + error.a str.a + +preline.0: \ +preline.1 + nroff -man preline.1 > preline.0 + +preline.o: \ +compile preline.c fd.h preline.c sgetopt.h subgetopt.h sgetopt.h \ +preline.c readwrite.h preline.c subfd.h substdio.h subfd.h preline.c \ +substdio.h substdio.h preline.c exit.h preline.c fork.h preline.c \ +wait.h preline.c env.h preline.c sig.h preline.c error.h preline.c + ./compile preline.c + +prioq.o: \ +compile prioq.c alloc.h prioq.c gen_allocdefs.h gen_allocdefs.h \ +gen_allocdefs.h prioq.c prioq.h datetime.h prioq.h gen_alloc.h \ +prioq.h prioq.c + ./compile prioq.c + +prot.o: \ +compile prot.c hasshsgr.h prot.c prot.h prot.c + ./compile prot.c + +qail: \ +warn-auto.sh qail.sh conf-qmail conf-break conf-split + cat warn-auto.sh qail.sh \ + | sed s}QMAIL}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPLIT}"`head -1 conf-split`"}g \ + > qail + chmod 755 qail + +qbiff: \ +load qbiff.o headerbody.o hfield.o getln.a env.a open.a stralloc.a \ +alloc.a substdio.a error.a str.a + ./load qbiff headerbody.o hfield.o getln.a env.a open.a \ + stralloc.a alloc.a substdio.a error.a str.a + +qbiff.0: \ +qbiff.1 + nroff -man qbiff.1 > qbiff.0 + +qbiff.o: \ +compile qbiff.c qbiff.c qbiff.c qbiff.c readwrite.h qbiff.c \ +stralloc.h gen_alloc.h stralloc.h qbiff.c substdio.h qbiff.c subfd.h \ +substdio.h substdio.h subfd.h qbiff.c open.h qbiff.c byte.h qbiff.c \ +str.h qbiff.c headerbody.h qbiff.c hfield.h qbiff.c env.h qbiff.c \ +exit.h qbiff.c + ./compile qbiff.c + +qlist: \ +load qlist.o headerbody.o hfield.o token822.o qmail.o getln.a env.a \ +case.a sig.a fd.a wait.a open.a lock.a stralloc.a alloc.a substdio.a \ +error.a str.a auto_qmail.o + ./load qlist headerbody.o hfield.o token822.o qmail.o \ + getln.a env.a case.a sig.a fd.a wait.a open.a lock.a \ + stralloc.a alloc.a substdio.a error.a str.a auto_qmail.o + +qlist.0: \ +qlist.1 + nroff -man qlist.1 > qlist.0 + +qlist.o: \ +compile qlist.c sig.h qlist.c readwrite.h qlist.c substdio.h qlist.c \ +stralloc.h gen_alloc.h stralloc.h qlist.c subfd.h substdio.h \ +substdio.h subfd.h qlist.c getln.h qlist.c alloc.h qlist.c str.h \ +qlist.c env.h qlist.c hfield.h qlist.c case.h qlist.c token822.h \ +gen_alloc.h token822.h qlist.c error.h qlist.c gen_alloc.h qlist.c \ +gen_allocdefs.h gen_allocdefs.h gen_allocdefs.h qlist.c headerbody.h \ +qlist.c exit.h qlist.c open.h qlist.c lock.h qlist.c qmail.h \ +substdio.h substdio.h qmail.h qlist.c qlist.c + ./compile qlist.c + +qlist2: \ +warn-auto.sh qlist2.sh conf-qmail conf-break conf-split + cat warn-auto.sh qlist2.sh \ + | sed s}QMAIL}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPLIT}"`head -1 conf-split`"}g \ + > qlist2 + chmod 755 qlist2 + +qmail-clean: \ +load qmail-clean.o fmtqfn.o now.o getln.a sig.a stralloc.a alloc.a \ +substdio.a error.a str.a fs.a auto_qmail.o auto_split.o + ./load qmail-clean fmtqfn.o now.o getln.a sig.a stralloc.a \ + alloc.a substdio.a error.a str.a fs.a auto_qmail.o \ + auto_split.o + +qmail-clean.0: \ +qmail-clean.8 + nroff -man qmail-clean.8 > qmail-clean.0 + +qmail-clean.o: \ +compile qmail-clean.c qmail-clean.c qmail-clean.c readwrite.h \ +qmail-clean.c sig.h qmail-clean.c now.h datetime.h now.h \ +qmail-clean.c str.h qmail-clean.c direntry.h direntry.h direntry.h \ +qmail-clean.c getln.h qmail-clean.c stralloc.h gen_alloc.h stralloc.h \ +qmail-clean.c substdio.h qmail-clean.c subfd.h substdio.h substdio.h \ +subfd.h qmail-clean.c byte.h qmail-clean.c scan.h qmail-clean.c fmt.h \ +qmail-clean.c error.h qmail-clean.c exit.h qmail-clean.c fmtqfn.h \ +qmail-clean.c auto_qmail.h qmail-clean.c + ./compile qmail-clean.c + +qmail-command.0: \ +qmail-command.8 + nroff -man qmail-command.8 > qmail-command.0 + +qmail-config: \ +warn-auto.sh qmail-config.sh conf-qmail conf-break conf-split + cat warn-auto.sh qmail-config.sh \ + | sed s}QMAIL}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPLIT}"`head -1 conf-split`"}g \ + > qmail-config + chmod 755 qmail-config + +qmail-control.0: \ +qmail-control.5 + nroff -man qmail-control.5 > qmail-control.0 + +qmail-control.5: \ +qmail-control.9 conf-break conf-spawn + cat qmail-control.9 \ + | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPAWN}"`head -1 conf-spawn`"}g \ + > qmail-control.5 + +qmail-getpw: \ +load qmail-getpw.o case.a substdio.a error.a str.a fs.a auto_break.o \ +auto_usera.o + ./load qmail-getpw case.a substdio.a error.a str.a fs.a \ + auto_break.o auto_usera.o + +qmail-getpw.0: \ +qmail-getpw.8 + nroff -man qmail-getpw.8 > qmail-getpw.0 + +qmail-getpw.8: \ +qmail-getpw.9 conf-break conf-spawn + cat qmail-getpw.9 \ + | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPAWN}"`head -1 conf-spawn`"}g \ + > qmail-getpw.8 + +qmail-getpw.o: \ +compile qmail-getpw.c qmail-getpw.c qmail-getpw.c qmail-getpw.c \ +readwrite.h qmail-getpw.c substdio.h qmail-getpw.c subfd.h substdio.h \ +substdio.h subfd.h qmail-getpw.c error.h qmail-getpw.c exit.h \ +qmail-getpw.c byte.h qmail-getpw.c str.h qmail-getpw.c case.h \ +qmail-getpw.c fmt.h qmail-getpw.c auto_usera.h qmail-getpw.c \ +auto_break.h qmail-getpw.c qlx.h qmail-getpw.c + ./compile qmail-getpw.c + +qmail-header.0: \ +qmail-header.5 + nroff -man qmail-header.5 > qmail-header.0 + +qmail-hier: \ +load qmail-hier.o substdio.a error.a str.a fs.a auto_split.o \ +auto_uids.o + ./load qmail-hier substdio.a error.a str.a fs.a \ + auto_split.o auto_uids.o + +qmail-hier.o: \ +compile qmail-hier.c subfd.h substdio.h subfd.h qmail-hier.c \ +substdio.h substdio.h qmail-hier.c auto_split.h qmail-hier.c \ +auto_uids.h qmail-hier.c fmt.h qmail-hier.c + ./compile qmail-hier.c + +qmail-inject: \ +load qmail-inject.o headerbody.o hfield.o newfield.o quote.o now.o \ +control.o date822fmt.o qmail.o fd.a wait.a open.a getln.a sig.a \ +getopt.a datetime.a token822.o env.a stralloc.a alloc.a substdio.a \ +error.a str.a fs.a auto_qmail.o + ./load qmail-inject headerbody.o hfield.o newfield.o \ + quote.o now.o control.o date822fmt.o qmail.o fd.a wait.a \ + open.a getln.a sig.a getopt.a datetime.a token822.o env.a \ + stralloc.a alloc.a substdio.a error.a str.a fs.a \ + auto_qmail.o + +qmail-inject.0: \ +qmail-inject.8 + nroff -man qmail-inject.8 > qmail-inject.0 + +qmail-inject.o: \ +compile qmail-inject.c sig.h qmail-inject.c substdio.h qmail-inject.c \ +stralloc.h gen_alloc.h stralloc.h qmail-inject.c subfd.h substdio.h \ +substdio.h subfd.h qmail-inject.c sgetopt.h subgetopt.h sgetopt.h \ +qmail-inject.c getln.h qmail-inject.c alloc.h qmail-inject.c str.h \ +qmail-inject.c fmt.h qmail-inject.c hfield.h qmail-inject.c \ +token822.h gen_alloc.h token822.h qmail-inject.c control.h \ +qmail-inject.c env.h qmail-inject.c gen_alloc.h qmail-inject.c \ +gen_allocdefs.h gen_allocdefs.h gen_allocdefs.h qmail-inject.c \ +error.h qmail-inject.c qmail.h substdio.h substdio.h qmail.h \ +qmail-inject.c now.h datetime.h now.h qmail-inject.c exit.h \ +qmail-inject.c quote.h qmail-inject.c headerbody.h qmail-inject.c \ +auto_qmail.h qmail-inject.c newfield.h stralloc.h stralloc.h \ +newfield.h qmail-inject.c + ./compile qmail-inject.c + +qmail-limits.0: \ +qmail-limits.7 + nroff -man qmail-limits.7 > qmail-limits.0 + +qmail-limits.7: \ +qmail-limits.9 conf-break conf-spawn + cat qmail-limits.9 \ + | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPAWN}"`head -1 conf-spawn`"}g \ + > qmail-limits.7 + +qmail-local: \ +load qmail-local.o qmail.o quote.o now.o gfrom.o myctime.o \ +slurpclose.o case.a getln.a getopt.a sig.a open.a seek.a lock.a fd.a \ +wait.a env.a stralloc.a alloc.a substdio.a error.a str.a fs.a \ +datetime.a auto_qmail.o auto_patrn.o socket.lib + ./load qmail-local qmail.o quote.o now.o gfrom.o myctime.o \ + slurpclose.o case.a getln.a getopt.a sig.a open.a seek.a \ + lock.a fd.a wait.a env.a stralloc.a alloc.a substdio.a \ + error.a str.a fs.a datetime.a auto_qmail.o auto_patrn.o \ + `cat socket.lib` + +qmail-local.0: \ +qmail-local.8 + nroff -man qmail-local.8 > qmail-local.0 + +qmail-local.o: \ +compile qmail-local.c qmail-local.c qmail-local.c readwrite.h \ +qmail-local.c sig.h qmail-local.c env.h qmail-local.c byte.h \ +qmail-local.c exit.h qmail-local.c fork.h qmail-local.c open.h \ +qmail-local.c wait.h qmail-local.c lock.h qmail-local.c seek.h \ +qmail-local.c substdio.h qmail-local.c getln.h qmail-local.c subfd.h \ +substdio.h substdio.h subfd.h qmail-local.c sgetopt.h subgetopt.h \ +sgetopt.h qmail-local.c alloc.h qmail-local.c error.h qmail-local.c \ +stralloc.h gen_alloc.h stralloc.h qmail-local.c fmt.h qmail-local.c \ +str.h qmail-local.c now.h datetime.h now.h qmail-local.c case.h \ +qmail-local.c quote.h qmail-local.c qmail.h substdio.h substdio.h \ +qmail.h qmail-local.c slurpclose.h qmail-local.c myctime.h \ +qmail-local.c gfrom.h qmail-local.c auto_patrn.h qmail-local.c + ./compile qmail-local.c + +qmail-log.0: \ +qmail-log.5 + nroff -man qmail-log.5 > qmail-log.0 + +qmail-lspawn: \ +load qmail-lspawn.o spawn.o prot.o slurpclose.o coe.o sig.a wait.a \ +case.a cdb.a fd.a open.a stralloc.a alloc.a substdio.a error.a str.a \ +fs.a auto_qmail.o auto_uids.o auto_spawn.o + ./load qmail-lspawn spawn.o prot.o slurpclose.o coe.o \ + sig.a wait.a case.a cdb.a fd.a open.a stralloc.a alloc.a \ + substdio.a error.a str.a fs.a auto_qmail.o auto_uids.o \ + auto_spawn.o + +qmail-lspawn.0: \ +qmail-lspawn.8 + nroff -man qmail-lspawn.8 > qmail-lspawn.0 + +qmail-lspawn.o: \ +compile qmail-lspawn.c fd.h qmail-lspawn.c wait.h qmail-lspawn.c \ +prot.h qmail-lspawn.c substdio.h qmail-lspawn.c stralloc.h \ +gen_alloc.h stralloc.h qmail-lspawn.c scan.h qmail-lspawn.c exit.h \ +qmail-lspawn.c fork.h qmail-lspawn.c error.h qmail-lspawn.c cdb.h \ +uint32.h cdb.h qmail-lspawn.c case.h qmail-lspawn.c slurpclose.h \ +qmail-lspawn.c auto_qmail.h qmail-lspawn.c auto_uids.h qmail-lspawn.c \ +qlx.h qmail-lspawn.c + ./compile qmail-lspawn.c + +qmail-newu: \ +load qmail-newu.o cdbmss.o getln.a open.a seek.a cdbmake.a case.a \ +stralloc.a alloc.a substdio.a error.a str.a auto_qmail.o + ./load qmail-newu cdbmss.o getln.a open.a seek.a cdbmake.a \ + case.a stralloc.a alloc.a substdio.a error.a str.a \ + auto_qmail.o + +qmail-newu.0: \ +qmail-newu.8 + nroff -man qmail-newu.8 > qmail-newu.0 + +qmail-newu.8: \ +qmail-newu.9 conf-break conf-spawn + cat qmail-newu.9 \ + | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPAWN}"`head -1 conf-spawn`"}g \ + > qmail-newu.8 + +qmail-newu.o: \ +compile qmail-newu.c stralloc.h gen_alloc.h stralloc.h qmail-newu.c \ +subfd.h substdio.h subfd.h qmail-newu.c getln.h qmail-newu.c \ +substdio.h substdio.h qmail-newu.c cdbmss.h cdbmake.h uint32.h \ +cdbmake.h cdbmss.h substdio.h substdio.h cdbmss.h qmail-newu.c exit.h \ +qmail-newu.c readwrite.h qmail-newu.c open.h qmail-newu.c error.h \ +qmail-newu.c case.h qmail-newu.c auto_qmail.h qmail-newu.c + ./compile qmail-newu.c + +qmail-pop3d: \ +load qmail-pop3d.o prioq.o now.o sig.a open.a getln.a stralloc.a \ +alloc.a substdio.a error.a str.a fs.a + ./load qmail-pop3d prioq.o now.o sig.a open.a getln.a \ + stralloc.a alloc.a substdio.a error.a str.a fs.a + +qmail-pop3d.0: \ +qmail-pop3d.8 + nroff -man qmail-pop3d.8 > qmail-pop3d.0 + +qmail-pop3d.o: \ +compile qmail-pop3d.c qmail-pop3d.c qmail-pop3d.c direntry.h \ +direntry.h direntry.h qmail-pop3d.c sig.h qmail-pop3d.c getln.h \ +qmail-pop3d.c stralloc.h gen_alloc.h stralloc.h qmail-pop3d.c \ +substdio.h qmail-pop3d.c alloc.h qmail-pop3d.c datetime.h \ +qmail-pop3d.c prot.h qmail-pop3d.c open.h qmail-pop3d.c prioq.h \ +datetime.h datetime.h prioq.h gen_alloc.h prioq.h qmail-pop3d.c \ +scan.h qmail-pop3d.c fmt.h qmail-pop3d.c error.h qmail-pop3d.c str.h \ +qmail-pop3d.c exit.h qmail-pop3d.c now.h datetime.h datetime.h now.h \ +qmail-pop3d.c readwrite.h qmail-pop3d.c + ./compile qmail-pop3d.c + +qmail-popup: \ +load qmail-popup.o now.o fd.a sig.a wait.a getln.a stralloc.a alloc.a \ +substdio.a error.a str.a fs.a + ./load qmail-popup now.o fd.a sig.a wait.a getln.a \ + stralloc.a alloc.a substdio.a error.a str.a fs.a + +qmail-popup.0: \ +qmail-popup.8 + nroff -man qmail-popup.8 > qmail-popup.0 + +qmail-popup.o: \ +compile qmail-popup.c qmail-popup.c qmail-popup.c fd.h qmail-popup.c \ +sig.h qmail-popup.c getln.h qmail-popup.c stralloc.h gen_alloc.h \ +stralloc.h qmail-popup.c substdio.h qmail-popup.c subfd.h substdio.h \ +substdio.h subfd.h qmail-popup.c alloc.h qmail-popup.c datetime.h \ +qmail-popup.c error.h qmail-popup.c wait.h qmail-popup.c str.h \ +qmail-popup.c now.h datetime.h datetime.h now.h qmail-popup.c fmt.h \ +qmail-popup.c exit.h qmail-popup.c readwrite.h qmail-popup.c + ./compile qmail-popup.c + +qmail-pw2u: \ +load qmail-pw2u.o constmap.o control.o open.a getln.a case.a getopt.a \ +stralloc.a alloc.a substdio.a error.a str.a fs.a auto_usera.o \ +auto_break.o auto_qmail.o + ./load qmail-pw2u constmap.o control.o open.a getln.a \ + case.a getopt.a stralloc.a alloc.a substdio.a error.a str.a \ + fs.a auto_usera.o auto_break.o auto_qmail.o + +qmail-pw2u.0: \ +qmail-pw2u.8 + nroff -man qmail-pw2u.8 > qmail-pw2u.0 + +qmail-pw2u.8: \ +qmail-pw2u.9 conf-break conf-spawn + cat qmail-pw2u.9 \ + | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPAWN}"`head -1 conf-spawn`"}g \ + > qmail-pw2u.8 + +qmail-pw2u.o: \ +compile qmail-pw2u.c qmail-pw2u.c qmail-pw2u.c substdio.h \ +qmail-pw2u.c readwrite.h qmail-pw2u.c subfd.h substdio.h substdio.h \ +subfd.h qmail-pw2u.c sgetopt.h subgetopt.h sgetopt.h qmail-pw2u.c \ +control.h qmail-pw2u.c constmap.h qmail-pw2u.c stralloc.h gen_alloc.h \ +stralloc.h qmail-pw2u.c fmt.h qmail-pw2u.c str.h qmail-pw2u.c scan.h \ +qmail-pw2u.c open.h qmail-pw2u.c error.h qmail-pw2u.c getln.h \ +qmail-pw2u.c auto_break.h qmail-pw2u.c auto_qmail.h qmail-pw2u.c \ +auto_usera.h qmail-pw2u.c + ./compile qmail-pw2u.c + +qmail-qmtpd: \ +load qmail-qmtpd.o control.o constmap.o received.o date822fmt.o now.o \ +qmail.o fd.a wait.a datetime.a open.a getln.a sig.a case.a env.a \ +stralloc.a alloc.a substdio.a error.a str.a fs.a auto_qmail.o + ./load qmail-qmtpd control.o constmap.o received.o \ + date822fmt.o now.o qmail.o fd.a wait.a datetime.a open.a \ + getln.a sig.a case.a env.a stralloc.a alloc.a substdio.a \ + error.a str.a fs.a auto_qmail.o + +qmail-qmtpd.0: \ +qmail-qmtpd.8 + nroff -man qmail-qmtpd.8 > qmail-qmtpd.0 + +qmail-qmtpd.o: \ +compile qmail-qmtpd.c stralloc.h gen_alloc.h stralloc.h qmail-qmtpd.c \ +substdio.h qmail-qmtpd.c subfd.h substdio.h substdio.h subfd.h \ +qmail-qmtpd.c qmail.h substdio.h substdio.h qmail.h qmail-qmtpd.c \ +now.h datetime.h now.h qmail-qmtpd.c str.h qmail-qmtpd.c fmt.h \ +qmail-qmtpd.c env.h qmail-qmtpd.c sig.h qmail-qmtpd.c auto_qmail.h \ +qmail-qmtpd.c now.h qmail-qmtpd.c datetime.h datetime.h qmail-qmtpd.c \ +date822fmt.h qmail-qmtpd.c readwrite.h qmail-qmtpd.c control.h \ +qmail-qmtpd.c constmap.h qmail-qmtpd.c received.h qmail-qmtpd.c + ./compile qmail-qmtpd.c + +qmail-qread: \ +load qmail-qread.o fmtqfn.o readsubdir.o date822fmt.o datetime.a \ +open.a getln.a stralloc.a alloc.a substdio.a error.a str.a fs.a \ +auto_qmail.o auto_split.o + ./load qmail-qread fmtqfn.o readsubdir.o date822fmt.o \ + datetime.a open.a getln.a stralloc.a alloc.a substdio.a \ + error.a str.a fs.a auto_qmail.o auto_split.o + +qmail-qread.0: \ +qmail-qread.8 + nroff -man qmail-qread.8 > qmail-qread.0 + +qmail-qread.o: \ +compile qmail-qread.c qmail-qread.c qmail-qread.c stralloc.h \ +gen_alloc.h stralloc.h qmail-qread.c substdio.h qmail-qread.c subfd.h \ +substdio.h substdio.h subfd.h qmail-qread.c fmt.h qmail-qread.c str.h \ +qmail-qread.c getln.h qmail-qread.c fmtqfn.h qmail-qread.c \ +readsubdir.h direntry.h direntry.h direntry.h readsubdir.h \ +qmail-qread.c auto_qmail.h qmail-qread.c open.h qmail-qread.c \ +datetime.h qmail-qread.c date822fmt.h qmail-qread.c readwrite.h \ +qmail-qread.c error.h qmail-qread.c exit.h qmail-qread.c + ./compile qmail-qread.c + +qmail-qstat: \ +warn-auto.sh qmail-qstat.sh conf-qmail conf-break conf-split + cat warn-auto.sh qmail-qstat.sh \ + | sed s}QMAIL}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPLIT}"`head -1 conf-split`"}g \ + > qmail-qstat + chmod 755 qmail-qstat + +qmail-qstat.0: \ +qmail-qstat.8 + nroff -man qmail-qstat.8 > qmail-qstat.0 + +qmail-queue: \ +load qmail-queue.o triggerpull.o fmtqfn.o now.o date822fmt.o \ +datetime.a seek.a ndelay.a open.a sig.a alloc.a substdio.a error.a \ +str.a fs.a auto_qmail.o auto_split.o auto_uids.o + ./load qmail-queue triggerpull.o fmtqfn.o now.o \ + date822fmt.o datetime.a seek.a ndelay.a open.a sig.a \ + alloc.a substdio.a error.a str.a fs.a auto_qmail.o \ + auto_split.o auto_uids.o + +qmail-queue.0: \ +qmail-queue.8 + nroff -man qmail-queue.8 > qmail-queue.0 + +qmail-queue.o: \ +compile qmail-queue.c qmail-queue.c qmail-queue.c readwrite.h \ +qmail-queue.c sig.h qmail-queue.c exit.h qmail-queue.c open.h \ +qmail-queue.c seek.h qmail-queue.c fmt.h qmail-queue.c alloc.h \ +qmail-queue.c substdio.h qmail-queue.c datetime.h qmail-queue.c now.h \ +datetime.h datetime.h now.h qmail-queue.c triggerpull.h qmail-queue.c \ +extra.h qmail-queue.c auto_qmail.h qmail-queue.c auto_uids.h \ +qmail-queue.c date822fmt.h qmail-queue.c fmtqfn.h qmail-queue.c + ./compile qmail-queue.c + +qmail-remote: \ +load qmail-remote.o control.o constmap.o timeoutread.o timeoutwrite.o \ +timeoutconn.o tcpto.o now.o dns.o ip.o ipalloc.o ipme.o quote.o \ +ndelay.a case.a sig.a open.a lock.a seek.a getln.a stralloc.a alloc.a \ +substdio.a error.a str.a fs.a auto_qmail.o dns.lib socket.lib + ./load qmail-remote control.o constmap.o timeoutread.o \ + timeoutwrite.o timeoutconn.o tcpto.o now.o dns.o ip.o \ + ipalloc.o ipme.o quote.o ndelay.a case.a sig.a open.a \ + lock.a seek.a getln.a stralloc.a alloc.a substdio.a error.a \ + str.a fs.a auto_qmail.o `cat dns.lib` `cat socket.lib` + +qmail-remote.0: \ +qmail-remote.8 + nroff -man qmail-remote.8 > qmail-remote.0 + +qmail-remote.o: \ +compile qmail-remote.c qmail-remote.c qmail-remote.c qmail-remote.c \ +qmail-remote.c sig.h qmail-remote.c getln.h qmail-remote.c stralloc.h \ +gen_alloc.h stralloc.h qmail-remote.c substdio.h qmail-remote.c \ +subfd.h substdio.h substdio.h subfd.h qmail-remote.c scan.h \ +qmail-remote.c case.h qmail-remote.c error.h qmail-remote.c \ +auto_qmail.h qmail-remote.c control.h qmail-remote.c dns.h \ +qmail-remote.c alloc.h qmail-remote.c quote.h qmail-remote.c ip.h \ +qmail-remote.c ipalloc.h ip.h ip.h ipalloc.h gen_alloc.h ipalloc.h \ +qmail-remote.c ipme.h ip.h ip.h ipme.h ipalloc.h ipalloc.h ipme.h \ +qmail-remote.c gen_alloc.h qmail-remote.c gen_allocdefs.h \ +gen_allocdefs.h gen_allocdefs.h qmail-remote.c str.h qmail-remote.c \ +now.h datetime.h now.h qmail-remote.c exit.h qmail-remote.c \ +constmap.h qmail-remote.c tcpto.h qmail-remote.c timeoutconn.h \ +qmail-remote.c timeoutread.h qmail-remote.c timeoutwrite.h \ +qmail-remote.c + ./compile qmail-remote.c + +qmail-rspawn: \ +load qmail-rspawn.o spawn.o tcpto_clean.o now.o coe.o sig.a open.a \ +seek.a lock.a wait.a fd.a stralloc.a alloc.a substdio.a error.a str.a \ +auto_qmail.o auto_uids.o auto_spawn.o + ./load qmail-rspawn spawn.o tcpto_clean.o now.o coe.o \ + sig.a open.a seek.a lock.a wait.a fd.a stralloc.a alloc.a \ + substdio.a error.a str.a auto_qmail.o auto_uids.o \ + auto_spawn.o + +qmail-rspawn.0: \ +qmail-rspawn.8 + nroff -man qmail-rspawn.8 > qmail-rspawn.0 + +qmail-rspawn.o: \ +compile qmail-rspawn.c fd.h qmail-rspawn.c wait.h qmail-rspawn.c \ +substdio.h qmail-rspawn.c exit.h qmail-rspawn.c fork.h qmail-rspawn.c \ +error.h qmail-rspawn.c tcpto.h qmail-rspawn.c + ./compile qmail-rspawn.c + +qmail-send: \ +load qmail-send.o qsutil.o control.o constmap.o newfield.o prioq.o \ +trigger.o fmtqfn.o quote.o now.o readsubdir.o qmail.o date822fmt.o \ +datetime.a case.a ndelay.a getln.a wait.a seek.a fd.a sig.a open.a \ +lock.a stralloc.a alloc.a substdio.a error.a str.a fs.a auto_qmail.o \ +auto_split.o + ./load qmail-send qsutil.o control.o constmap.o newfield.o \ + prioq.o trigger.o fmtqfn.o quote.o now.o readsubdir.o \ + qmail.o date822fmt.o datetime.a case.a ndelay.a getln.a \ + wait.a seek.a fd.a sig.a open.a lock.a stralloc.a alloc.a \ + substdio.a error.a str.a fs.a auto_qmail.o auto_split.o + +qmail-send.0: \ +qmail-send.8 + nroff -man qmail-send.8 > qmail-send.0 + +qmail-send.8: \ +qmail-send.9 conf-break conf-spawn + cat qmail-send.9 \ + | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPAWN}"`head -1 conf-spawn`"}g \ + > qmail-send.8 + +qmail-send.o: \ +compile qmail-send.c qmail-send.c qmail-send.c readwrite.h \ +qmail-send.c sig.h qmail-send.c direntry.h direntry.h direntry.h \ +qmail-send.c control.h qmail-send.c select.h select.h select.h \ +select.h qmail-send.c open.h qmail-send.c seek.h qmail-send.c exit.h \ +qmail-send.c lock.h qmail-send.c ndelay.h qmail-send.c now.h \ +datetime.h now.h qmail-send.c getln.h qmail-send.c substdio.h \ +qmail-send.c alloc.h qmail-send.c error.h qmail-send.c stralloc.h \ +gen_alloc.h stralloc.h qmail-send.c str.h qmail-send.c byte.h \ +qmail-send.c fmt.h qmail-send.c scan.h qmail-send.c case.h \ +qmail-send.c auto_qmail.h qmail-send.c trigger.h qmail-send.c \ +newfield.h stralloc.h stralloc.h newfield.h qmail-send.c quote.h \ +qmail-send.c qmail.h substdio.h substdio.h qmail.h qmail-send.c \ +qsutil.h qmail-send.c prioq.h datetime.h datetime.h prioq.h \ +gen_alloc.h prioq.h qmail-send.c constmap.h qmail-send.c fmtqfn.h \ +qmail-send.c readsubdir.h direntry.h readsubdir.h qmail-send.c + ./compile qmail-send.c + +qmail-showctl: \ +load qmail-showctl.o control.o open.a getln.a stralloc.a alloc.a \ +substdio.a error.a str.a fs.a auto_qmail.o + ./load qmail-showctl control.o open.a getln.a stralloc.a \ + alloc.a substdio.a error.a str.a fs.a auto_qmail.o + +qmail-showctl.0: \ +qmail-showctl.8 + nroff -man qmail-showctl.8 > qmail-showctl.0 + +qmail-showctl.o: \ +compile qmail-showctl.c substdio.h qmail-showctl.c subfd.h substdio.h \ +substdio.h subfd.h qmail-showctl.c exit.h qmail-showctl.c fmt.h \ +qmail-showctl.c str.h qmail-showctl.c control.h qmail-showctl.c \ +constmap.h qmail-showctl.c stralloc.h gen_alloc.h stralloc.h \ +qmail-showctl.c direntry.h direntry.h direntry.h qmail-showctl.c \ +auto_qmail.h qmail-showctl.c + ./compile qmail-showctl.c + +qmail-smtpd: \ +load qmail-smtpd.o ip.o ipme.o ipalloc.o control.o constmap.o \ +received.o date822fmt.o now.o qmail.o fd.a wait.a datetime.a open.a \ +getln.a sig.a case.a env.a stralloc.a alloc.a substdio.a error.a \ +str.a fs.a auto_qmail.o socket.lib + ./load qmail-smtpd ip.o ipme.o ipalloc.o control.o \ + constmap.o received.o date822fmt.o now.o qmail.o fd.a \ + wait.a datetime.a open.a getln.a sig.a case.a env.a \ + stralloc.a alloc.a substdio.a error.a str.a fs.a \ + auto_qmail.o `cat socket.lib` + +qmail-smtpd.0: \ +qmail-smtpd.8 + nroff -man qmail-smtpd.8 > qmail-smtpd.0 + +qmail-smtpd.o: \ +compile qmail-smtpd.c sig.h qmail-smtpd.c readwrite.h qmail-smtpd.c \ +getln.h qmail-smtpd.c stralloc.h gen_alloc.h stralloc.h qmail-smtpd.c \ +substdio.h qmail-smtpd.c alloc.h qmail-smtpd.c auto_qmail.h \ +qmail-smtpd.c control.h qmail-smtpd.c received.h qmail-smtpd.c \ +constmap.h qmail-smtpd.c error.h qmail-smtpd.c ipme.h ip.h ipme.h \ +ipalloc.h ip.h ip.h ipalloc.h gen_alloc.h ipalloc.h ipme.h \ +qmail-smtpd.c ip.h ip.h qmail-smtpd.c qmail.h substdio.h substdio.h \ +qmail.h qmail-smtpd.c str.h qmail-smtpd.c fmt.h qmail-smtpd.c byte.h \ +qmail-smtpd.c case.h qmail-smtpd.c env.h qmail-smtpd.c now.h \ +datetime.h now.h qmail-smtpd.c exit.h qmail-smtpd.c + ./compile qmail-smtpd.c + +qmail-start: \ +load qmail-start.o prot.o fd.a auto_uids.o + ./load qmail-start prot.o fd.a auto_uids.o + +qmail-start.0: \ +qmail-start.8 + nroff -man qmail-start.8 > qmail-start.0 + +qmail-start.8: \ +qmail-start.9 conf-break conf-spawn + cat qmail-start.9 \ + | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPAWN}"`head -1 conf-spawn`"}g \ + > qmail-start.8 + +qmail-start.o: \ +compile qmail-start.c fd.h qmail-start.c prot.h qmail-start.c exit.h \ +qmail-start.c fork.h qmail-start.c auto_uids.h qmail-start.c + ./compile qmail-start.c + +qmail-tcpto: \ +load qmail-tcpto.o ip.o now.o open.a lock.a substdio.a error.a str.a \ +fs.a auto_qmail.o + ./load qmail-tcpto ip.o now.o open.a lock.a substdio.a \ + error.a str.a fs.a auto_qmail.o + +qmail-tcpto.0: \ +qmail-tcpto.8 + nroff -man qmail-tcpto.8 > qmail-tcpto.0 + +qmail-tcpto.o: \ +compile qmail-tcpto.c substdio.h qmail-tcpto.c subfd.h substdio.h \ +substdio.h subfd.h qmail-tcpto.c auto_qmail.h qmail-tcpto.c fmt.h \ +qmail-tcpto.c ip.h qmail-tcpto.c lock.h qmail-tcpto.c error.h \ +qmail-tcpto.c exit.h qmail-tcpto.c datetime.h qmail-tcpto.c now.h \ +datetime.h datetime.h now.h qmail-tcpto.c + ./compile qmail-tcpto.c + +qmail-upgrade.0: \ +qmail-upgrade.7 + nroff -man qmail-upgrade.7 > qmail-upgrade.0 + +qmail-upgrade.7: \ +qmail-upgrade.9 conf-break conf-spawn + cat qmail-upgrade.9 \ + | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPAWN}"`head -1 conf-spawn`"}g \ + > qmail-upgrade.7 + +qmail-upq: \ +warn-auto.sh qmail-upq.sh conf-qmail conf-break conf-split + cat warn-auto.sh qmail-upq.sh \ + | sed s}QMAIL}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPLIT}"`head -1 conf-split`"}g \ + > qmail-upq + chmod 755 qmail-upq + +qmail-users.0: \ +qmail-users.5 + nroff -man qmail-users.5 > qmail-users.0 + +qmail-users.5: \ +qmail-users.9 conf-break conf-spawn + cat qmail-users.9 \ + | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \ + | sed s}BREAK}"`head -1 conf-break`"}g \ + | sed s}SPAWN}"`head -1 conf-spawn`"}g \ + > qmail-users.5 + +qmail.0: \ +qmail.7 + nroff -man qmail.7 > qmail.0 + +qmail.o: \ +compile qmail.c substdio.h qmail.c readwrite.h qmail.c wait.h qmail.c \ +exit.h qmail.c fork.h qmail.c fd.h qmail.c qmail.h substdio.h \ +substdio.h qmail.h qmail.c auto_qmail.h qmail.c + ./compile qmail.c + +qreceipt: \ +load qreceipt.o headerbody.o hfield.o quote.o token822.o qmail.o \ +getln.a fd.a wait.a sig.a env.a stralloc.a alloc.a substdio.a error.a \ +str.a auto_qmail.o + ./load qreceipt headerbody.o hfield.o quote.o token822.o \ + qmail.o getln.a fd.a wait.a sig.a env.a stralloc.a alloc.a \ + substdio.a error.a str.a auto_qmail.o + +qreceipt.0: \ +qreceipt.1 + nroff -man qreceipt.1 > qreceipt.0 + +qreceipt.o: \ +compile qreceipt.c sig.h qreceipt.c env.h qreceipt.c substdio.h \ +qreceipt.c stralloc.h gen_alloc.h stralloc.h qreceipt.c subfd.h \ +substdio.h substdio.h subfd.h qreceipt.c getln.h qreceipt.c alloc.h \ +qreceipt.c str.h qreceipt.c hfield.h qreceipt.c token822.h \ +gen_alloc.h token822.h qreceipt.c error.h qreceipt.c gen_alloc.h \ +qreceipt.c gen_allocdefs.h gen_allocdefs.h gen_allocdefs.h qreceipt.c \ +headerbody.h qreceipt.c exit.h qreceipt.c open.h qreceipt.c quote.h \ +qreceipt.c qmail.h substdio.h substdio.h qmail.h qreceipt.c + ./compile qreceipt.c + +qsmhook: \ +load qsmhook.o sig.a case.a fd.a wait.a getopt.a env.a stralloc.a \ +alloc.a substdio.a error.a str.a + ./load qsmhook sig.a case.a fd.a wait.a getopt.a env.a \ + stralloc.a alloc.a substdio.a error.a str.a + +qsmhook.o: \ +compile qsmhook.c fd.h qsmhook.c stralloc.h gen_alloc.h stralloc.h \ +qsmhook.c readwrite.h qsmhook.c sgetopt.h subgetopt.h sgetopt.h \ +qsmhook.c wait.h qsmhook.c env.h qsmhook.c byte.h qsmhook.c str.h \ +qsmhook.c alloc.h qsmhook.c exit.h qsmhook.c fork.h qsmhook.c case.h \ +qsmhook.c subfd.h substdio.h subfd.h qsmhook.c error.h qsmhook.c \ +substdio.h substdio.h qsmhook.c sig.h qsmhook.c + ./compile qsmhook.c + +qsutil.o: \ +compile qsutil.c stralloc.h gen_alloc.h stralloc.h qsutil.c \ +readwrite.h qsutil.c substdio.h qsutil.c qsutil.h qsutil.c + ./compile qsutil.c + +quote.o: \ +compile quote.c stralloc.h gen_alloc.h stralloc.h quote.c str.h \ +quote.c quote.h quote.c + ./compile quote.c + +readsubdir.o: \ +compile readsubdir.c readsubdir.h direntry.h direntry.h direntry.h \ +readsubdir.h readsubdir.c fmt.h readsubdir.c scan.h readsubdir.c \ +str.h readsubdir.c auto_split.h readsubdir.c + ./compile readsubdir.c + +received.o: \ +compile received.c fmt.h received.c qmail.h substdio.h qmail.h \ +received.c now.h datetime.h now.h received.c datetime.h datetime.h \ +received.c date822fmt.h received.c received.h received.c + ./compile received.c + +remoteinfo.o: \ +compile remoteinfo.c remoteinfo.c remoteinfo.c remoteinfo.c \ +remoteinfo.c byte.h remoteinfo.c substdio.h remoteinfo.c ip.h \ +remoteinfo.c fmt.h remoteinfo.c timeoutconn.h remoteinfo.c \ +timeoutread.h remoteinfo.c timeoutwrite.h remoteinfo.c remoteinfo.h \ +remoteinfo.c + ./compile remoteinfo.c + +scan_8long.o: \ +compile scan_8long.c scan.h scan_8long.c + ./compile scan_8long.c + +scan_nbblong.o: \ +compile scan_nbblong.c scan.h scan_nbblong.c + ./compile scan_nbblong.c + +scan_ulong.o: \ +compile scan_ulong.c scan.h scan_ulong.c + ./compile scan_ulong.c + +seek.a: \ +makelib seek_cur.o seek_end.o seek_set.o seek_trunc.o + ./makelib seek.a seek_cur.o seek_end.o seek_set.o \ + seek_trunc.o + +seek_cur.o: \ +compile seek_cur.c seek_cur.c seek.h seek_cur.c + ./compile seek_cur.c + +seek_end.o: \ +compile seek_end.c seek_end.c seek.h seek_end.c + ./compile seek_end.c + +seek_set.o: \ +compile seek_set.c seek_set.c seek.h seek_set.c + ./compile seek_set.c + +seek_trunc.o: \ +compile seek_trunc.c seek_trunc.c seek.h seek_trunc.c + ./compile seek_trunc.c + +select.h: \ +compile trysysel.c select.h1 select.h2 + ( ./compile trysysel.c >/dev/null 2>&1 \ + && cat select.h2 || cat select.h1 ) > select.h + rm -f trysysel.o trysysel + +sendmail: \ +load sendmail.o env.a getopt.a alloc.a substdio.a error.a str.a \ +auto_qmail.o + ./load sendmail env.a getopt.a alloc.a substdio.a error.a \ + str.a auto_qmail.o + +sendmail.o: \ +compile sendmail.c sgetopt.h subgetopt.h sgetopt.h sendmail.c \ +substdio.h sendmail.c subfd.h substdio.h substdio.h subfd.h \ +sendmail.c alloc.h sendmail.c auto_qmail.h sendmail.c exit.h \ +sendmail.c env.h sendmail.c str.h sendmail.c + ./compile sendmail.c + +setup: \ +it man conf-qmail + ./qmail-hier | ./install `head -1 conf-qmail` + +sgetopt.o: \ +compile sgetopt.c substdio.h sgetopt.c subfd.h substdio.h substdio.h \ +subfd.h sgetopt.c sgetopt.h sgetopt.h subgetopt.h sgetopt.h sgetopt.c \ +subgetopt.h subgetopt.h sgetopt.c + ./compile sgetopt.c + +shar: \ +FILES BLURB BLURB2 BLURB3 BLURB4 README FAQ INSTALL INSTALL.alias \ +INSTALL.boot INSTALL.ctl INSTALL.ids INSTALL.mbox INSTALL.qsmhook \ +UPGRADE THOUGHTS TODO THANKS CHANGES RFCHCSC RFCLOOPS RFCMXPS \ +RFCNETSTR RFCNRUDT RFCQMTP RFCQSBMF RFCVERP SECURITY INTERNALS FILES \ +VERSION SYSDEPS TARGETS Makefile conf-break auto_break.h conf-spawn \ +auto_spawn.h chkspawn.c conf-split auto_split.h conf-patrn \ +auto_patrn.h conf-users conf-groups auto_uids.h auto_usera.h extra.h \ +addresses.5 condredirect.1 dot-qmail.9 envelopes.5 forgeries.7 \ +forward.1 maildir2mbox.1 maildirmake.1 maildirwatch.1 mailsubj.1 \ +mbox.5 preline.1 qbiff.1 qlist.1 qmail-clean.8 qmail-command.8 \ +qmail-control.5 qmail-getpw.9 qmail-header.5 qmail-inject.8 \ +qmail-limits.9 qmail-local.8 qmail-log.5 qmail-lspawn.8 qmail-newu.8 \ +qmail-pop3d.8 qmail-popup.8 qmail-pw2u.9 qmail-qmtpd.8 qmail-qread.8 \ +qmail-qstat.8 qmail-queue.8 qmail-remote.8 qmail-rspawn.8 \ +qmail-send.9 qmail-showctl.8 qmail-smtpd.8 qmail-start.8 \ +qmail-tcpto.8 qmail-upgrade.9 qmail-users.5 qmail.7 qreceipt.1 \ +splogger.8 tcp-env.1 qmail-clean.c qmail-config.sh qmail-getpw.c \ +qmail-hier.c qmail-inject.c qmail-local.c qmail-lspawn.c qmail-newu.c \ +qmail-pop3d.c qmail-popup.c qmail-pw2u.c qmail-qmtpd.c qmail-qread.c \ +qmail-qstat.sh qmail-queue.c qmail-remote.c qmail-rspawn.c \ +qmail-send.c qmail-showctl.c qmail-smtpd.c qmail-start.c \ +qmail-tcpto.c spawn.c dnscname.c dnsfq.c dnsip.c dnsmxip.c dnsptr.c \ +hostname.c ipmeprint.c tcp-env.c sendmail.c qlist.c qreceipt.c \ +qsmhook.c qbiff.c forward.c preline.c predate.c condredirect.c \ +maildirmake.c maildir2mbox.c maildirwatch.c splogger.c qail.sh elq.sh \ +pinq.sh qlist2.sh qmail-upq.sh datemail.sh mailsubj.sh qlx.h \ +constmap.h constmap.c dnsdoe.h dnsdoe.c fmtqfn.h fmtqfn.c gfrom.h \ +gfrom.c myctime.h myctime.c newfield.h newfield.c qsutil.h qsutil.c \ +readsubdir.h readsubdir.c received.h received.c tcpto.h tcpto.c \ +tcpto_clean.c trigger.h trigger.c triggerpull.h triggerpull.c \ +trynpbg1.c trysyslog.c conf-cc conf-ld find-systype.sh \ +make-compile.sh make-load.sh make-makelib.sh trycpp.c warn-auto.sh \ +auto-str.c auto-int.c auto-int8.c auto-gid.c auto-uid.c install.c \ +instcheck.c alloc.3 alloc.h alloc.c alloc_re.c case.3 case.h \ +case_diffb.c case_diffs.c case_lowerb.c case_lowers.c case_starts.c \ +cdb.3 cdb.h cdb_hash.c cdb_seek.c cdb_unpack.c cdbmake.h \ +cdbmake_add.c cdbmake_hash.c cdbmake_pack.c cdbmss.h cdbmss.c coe.3 \ +coe.h coe.c fd.h fd_copy.3 fd_copy.c fd_move.3 fd_move.c fifo_make.3 \ +fifo.h fifo.c trymkffo.c fork.h1 fork.h2 tryvfork.c now.3 now.h now.c \ +open.h open_append.c open_excl.c open_read.c open_trunc.c \ +open_write.c seek.h seek_cur.c seek_end.c seek_set.c seek_trunc.c \ +conf-qmail auto_qmail.h qmail.h qmail.c gen_alloc.h gen_allocdefs.h \ +stralloc.3 stralloc.h stralloc_eady.c stralloc_pend.c stralloc_copy.c \ +stralloc_opyb.c stralloc_opys.c stralloc_cat.c stralloc_catb.c \ +stralloc_cats.c stralloc_arts.c strerr.h strerr_sys.c strerr_die.c \ +substdio.h substdio.c substdi.c substdo.c substdio_copy.c subfd.h \ +subfderr.c subfdouts.c subfdout.c subfdins.c subfdin.c readwrite.h \ +exit.h timeoutconn.h timeoutconn.c timeoutread.h timeoutread.c \ +timeoutwrite.h timeoutwrite.c remoteinfo.h remoteinfo.c uint32.h1 \ +uint32.h2 tryulong32.c wait.3 wait.h wait_pid.c wait_nohang.c \ +trywaitp.c sig.h sig_alarm.c sig_block.c sig_catch.c sig_pause.c \ +sig_pipe.c sig_child.c sig_term.c sig_hup.c sig_misc.c sig_bug.c \ +trysgact.c trysgprm.c env.3 env.h env.c envread.c byte.h byte_chr.c \ +byte_copy.c byte_cr.c byte_diff.c byte_rchr.c byte_zero.c str.h \ +str_chr.c str_cpy.c str_diff.c str_diffn.c str_len.c str_rchr.c \ +str_start.c lock.h lock_ex.c lock_exnb.c lock_un.c tryflock.c getln.3 \ +getln.h getln.c getln2.3 getln2.c sgetopt.3 sgetopt.h sgetopt.c \ +subgetopt.3 subgetopt.h subgetopt.c error.3 error_str.3 error_temp.3 \ +error.h error.c error_str.c error_temp.c fmt.h fmt_str.c fmt_strn.c \ +fmt_uint.c fmt_uint0.c fmt_ulong.c scan.h scan_ulong.c scan_8long.c \ +scan_nbblong.c slurpclose.h slurpclose.c quote.h quote.c hfield.h \ +hfield.c headerbody.h headerbody.c token822.h token822.c control.h \ +control.c datetime.3 datetime.h datetime.c datetime_un.c prioq.h \ +prioq.c date822fmt.h date822fmt.c dns.h dns.c trylsock.c tryrsolv.c \ +ip.h ip.c ipalloc.h ipalloc.c select.h1 select.h2 trysysel.c ndelay.h \ +ndelay.c ndelay_off.c direntry.3 direntry.h1 direntry.h2 trydrent.c \ +prot.h prot.c chkshsgr.c warn-shsgr tryshsgr.c ipme.h ipme.c \ +trysalen.c maildir.5 maildir.h maildir.c tcp-environ.5 + shar -m `cat FILES` > shar + chmod 400 shar + +sig.a: \ +makelib sig_alarm.o sig_block.o sig_catch.o sig_pause.o sig_pipe.o \ +sig_child.o sig_hup.o sig_term.o sig_bug.o sig_misc.o + ./makelib sig.a sig_alarm.o sig_block.o sig_catch.o \ + sig_pause.o sig_pipe.o sig_child.o sig_hup.o sig_term.o \ + sig_bug.o sig_misc.o + +sig_alarm.o: \ +compile sig_alarm.c sig_alarm.c sig.h sig_alarm.c + ./compile sig_alarm.c + +sig_block.o: \ +compile sig_block.c sig_block.c sig.h sig_block.c hassgprm.h \ +sig_block.c + ./compile sig_block.c + +sig_bug.o: \ +compile sig_bug.c sig_bug.c sig.h sig_bug.c + ./compile sig_bug.c + +sig_catch.o: \ +compile sig_catch.c sig_catch.c sig.h sig_catch.c hassgact.h \ +sig_catch.c + ./compile sig_catch.c + +sig_child.o: \ +compile sig_child.c sig_child.c sig.h sig_child.c + ./compile sig_child.c + +sig_hup.o: \ +compile sig_hup.c sig_hup.c sig.h sig_hup.c + ./compile sig_hup.c + +sig_misc.o: \ +compile sig_misc.c sig_misc.c sig.h sig_misc.c + ./compile sig_misc.c + +sig_pause.o: \ +compile sig_pause.c sig_pause.c sig.h sig_pause.c hassgprm.h \ +sig_pause.c + ./compile sig_pause.c + +sig_pipe.o: \ +compile sig_pipe.c sig_pipe.c sig.h sig_pipe.c + ./compile sig_pipe.c + +sig_term.o: \ +compile sig_term.c sig_term.c sig.h sig_term.c + ./compile sig_term.c + +slurpclose.o: \ +compile slurpclose.c stralloc.h gen_alloc.h stralloc.h slurpclose.c \ +readwrite.h slurpclose.c slurpclose.h slurpclose.c + ./compile slurpclose.c + +socket.lib: \ +trylsock.c compile load + ( ( ./compile trylsock.c && \ + ./load trylsock -lsocket -lnsl ) >/dev/null 2>&1 \ + && echo -lsocket -lnsl || exit 0 ) > socket.lib + rm -f trylsock.o trylsock + +spawn.o: \ +compile chkspawn spawn.c spawn.c spawn.c sig.h spawn.c wait.h spawn.c \ +substdio.h spawn.c byte.h spawn.c str.h spawn.c stralloc.h \ +gen_alloc.h stralloc.h spawn.c select.h select.h select.h select.h \ +spawn.c exit.h spawn.c coe.h spawn.c open.h spawn.c error.h spawn.c \ +auto_qmail.h spawn.c auto_uids.h spawn.c auto_spawn.h spawn.c + ./chkspawn + ./compile spawn.c + +splogger: \ +load splogger.o substdio.a error.a str.a fs.a syslog.lib + ./load splogger substdio.a error.a str.a fs.a `cat \ + syslog.lib` + +splogger.0: \ +splogger.8 + nroff -man splogger.8 > splogger.0 + +splogger.o: \ +compile splogger.c splogger.c splogger.c splogger.c error.h \ +splogger.c substdio.h splogger.c subfd.h substdio.h substdio.h \ +subfd.h splogger.c exit.h splogger.c str.h splogger.c scan.h \ +splogger.c fmt.h splogger.c + ./compile splogger.c + +str.a: \ +makelib str_len.o str_diff.o str_diffn.o str_cpy.o str_chr.o \ +str_rchr.o str_start.o byte_chr.o byte_rchr.o byte_diff.o byte_copy.o \ +byte_cr.o byte_zero.o + ./makelib str.a str_len.o str_diff.o str_diffn.o str_cpy.o \ + str_chr.o str_rchr.o str_start.o byte_chr.o byte_rchr.o \ + byte_diff.o byte_copy.o byte_cr.o byte_zero.o + +str_chr.o: \ +compile str_chr.c str.h str_chr.c + ./compile str_chr.c + +str_cpy.o: \ +compile str_cpy.c str.h str_cpy.c + ./compile str_cpy.c + +str_diff.o: \ +compile str_diff.c str.h str_diff.c + ./compile str_diff.c + +str_diffn.o: \ +compile str_diffn.c str.h str_diffn.c + ./compile str_diffn.c + +str_len.o: \ +compile str_len.c str.h str_len.c + ./compile str_len.c + +str_rchr.o: \ +compile str_rchr.c str.h str_rchr.c + ./compile str_rchr.c + +str_start.o: \ +compile str_start.c str.h str_start.c + ./compile str_start.c + +stralloc.a: \ +makelib stralloc_eady.o stralloc_pend.o stralloc_copy.o \ +stralloc_opys.o stralloc_opyb.o stralloc_cat.o stralloc_cats.o \ +stralloc_catb.o stralloc_arts.o + ./makelib stralloc.a stralloc_eady.o stralloc_pend.o \ + stralloc_copy.o stralloc_opys.o stralloc_opyb.o \ + stralloc_cat.o stralloc_cats.o stralloc_catb.o \ + stralloc_arts.o + +stralloc_arts.o: \ +compile stralloc_arts.c byte.h stralloc_arts.c str.h stralloc_arts.c \ +stralloc.h gen_alloc.h stralloc.h stralloc_arts.c + ./compile stralloc_arts.c + +stralloc_cat.o: \ +compile stralloc_cat.c byte.h stralloc_cat.c stralloc.h gen_alloc.h \ +stralloc.h stralloc_cat.c + ./compile stralloc_cat.c + +stralloc_catb.o: \ +compile stralloc_catb.c stralloc.h gen_alloc.h stralloc.h \ +stralloc_catb.c byte.h stralloc_catb.c + ./compile stralloc_catb.c + +stralloc_cats.o: \ +compile stralloc_cats.c byte.h stralloc_cats.c str.h stralloc_cats.c \ +stralloc.h gen_alloc.h stralloc.h stralloc_cats.c + ./compile stralloc_cats.c + +stralloc_copy.o: \ +compile stralloc_copy.c byte.h stralloc_copy.c stralloc.h gen_alloc.h \ +stralloc.h stralloc_copy.c + ./compile stralloc_copy.c + +stralloc_eady.o: \ +compile stralloc_eady.c alloc.h stralloc_eady.c stralloc.h \ +gen_alloc.h stralloc.h stralloc_eady.c gen_allocdefs.h \ +gen_allocdefs.h gen_allocdefs.h stralloc_eady.c + ./compile stralloc_eady.c + +stralloc_opyb.o: \ +compile stralloc_opyb.c stralloc.h gen_alloc.h stralloc.h \ +stralloc_opyb.c byte.h stralloc_opyb.c + ./compile stralloc_opyb.c + +stralloc_opys.o: \ +compile stralloc_opys.c byte.h stralloc_opys.c str.h stralloc_opys.c \ +stralloc.h gen_alloc.h stralloc.h stralloc_opys.c + ./compile stralloc_opys.c + +stralloc_pend.o: \ +compile stralloc_pend.c alloc.h stralloc_pend.c stralloc.h \ +gen_alloc.h stralloc.h stralloc_pend.c gen_allocdefs.h \ +gen_allocdefs.h gen_allocdefs.h stralloc_pend.c + ./compile stralloc_pend.c + +strerr.a: \ +makelib strerr_sys.o strerr_die.o + ./makelib strerr.a strerr_sys.o strerr_die.o + +strerr_die.o: \ +compile strerr_die.c substdio.h strerr_die.c subfd.h substdio.h \ +substdio.h subfd.h strerr_die.c exit.h strerr_die.c strerr.h \ +strerr_die.c + ./compile strerr_die.c + +strerr_sys.o: \ +compile strerr_sys.c error.h strerr_sys.c strerr.h strerr_sys.c + ./compile strerr_sys.c + +subfderr.o: \ +compile subfderr.c readwrite.h subfderr.c substdio.h subfderr.c \ +subfd.h substdio.h substdio.h subfd.h subfderr.c + ./compile subfderr.c + +subfdin.o: \ +compile subfdin.c readwrite.h subfdin.c substdio.h subfdin.c subfd.h \ +substdio.h substdio.h subfd.h subfdin.c + ./compile subfdin.c + +subfdins.o: \ +compile subfdins.c readwrite.h subfdins.c substdio.h subfdins.c \ +subfd.h substdio.h substdio.h subfd.h subfdins.c + ./compile subfdins.c + +subfdout.o: \ +compile subfdout.c readwrite.h subfdout.c substdio.h subfdout.c \ +subfd.h substdio.h substdio.h subfd.h subfdout.c + ./compile subfdout.c + +subfdouts.o: \ +compile subfdouts.c readwrite.h subfdouts.c substdio.h subfdouts.c \ +subfd.h substdio.h substdio.h subfd.h subfdouts.c + ./compile subfdouts.c + +subgetopt.o: \ +compile subgetopt.c subgetopt.h subgetopt.h subgetopt.c + ./compile subgetopt.c + +substdi.o: \ +compile substdi.c substdio.h substdi.c byte.h substdi.c error.h \ +substdi.c + ./compile substdi.c + +substdio.a: \ +makelib substdio.o substdi.o substdo.o subfderr.o subfdout.o \ +subfdouts.o subfdin.o subfdins.o substdio_copy.o + ./makelib substdio.a substdio.o substdi.o substdo.o \ + subfderr.o subfdout.o subfdouts.o subfdin.o subfdins.o \ + substdio_copy.o + +substdio.o: \ +compile substdio.c substdio.h substdio.c + ./compile substdio.c + +substdio_copy.o: \ +compile substdio_copy.c substdio.h substdio_copy.c + ./compile substdio_copy.c + +substdo.o: \ +compile substdo.c substdio.h substdo.c str.h substdo.c byte.h \ +substdo.c error.h substdo.c + ./compile substdo.c + +syslog.lib: \ +trysyslog.c compile load + ( ( ./compile trysyslog.c && \ + ./load trysyslog -lgen ) >/dev/null 2>&1 \ + && echo -lgen || exit 0 ) > syslog.lib + rm -f trysyslog.o trysyslog + +systype: \ +find-systype trycpp.c + ./find-systype > systype + +tcp-env: \ +load tcp-env.o dns.o remoteinfo.o timeoutread.o timeoutwrite.o \ +timeoutconn.o ip.o ipalloc.o case.a ndelay.a sig.a env.a getopt.a \ +stralloc.a alloc.a substdio.a error.a str.a fs.a dns.lib socket.lib + ./load tcp-env dns.o remoteinfo.o timeoutread.o \ + timeoutwrite.o timeoutconn.o ip.o ipalloc.o case.a ndelay.a \ + sig.a env.a getopt.a stralloc.a alloc.a substdio.a error.a \ + str.a fs.a `cat dns.lib` `cat socket.lib` + +tcp-env.0: \ +tcp-env.1 + nroff -man tcp-env.1 > tcp-env.0 + +tcp-env.o: \ +compile tcp-env.c tcp-env.c tcp-env.c tcp-env.c tcp-env.c sig.h \ +tcp-env.c stralloc.h gen_alloc.h stralloc.h tcp-env.c str.h tcp-env.c \ +env.h tcp-env.c fmt.h tcp-env.c scan.h tcp-env.c subgetopt.h \ +tcp-env.c ip.h tcp-env.c dns.h tcp-env.c byte.h tcp-env.c \ +remoteinfo.h tcp-env.c exit.h tcp-env.c case.h tcp-env.c + ./compile tcp-env.c + +tcp-environ.0: \ +tcp-environ.5 + nroff -man tcp-environ.5 > tcp-environ.0 + +tcpto.o: \ +compile tcpto.c tcpto.h tcpto.c open.h tcpto.c lock.h tcpto.c seek.h \ +tcpto.c now.h datetime.h now.h tcpto.c ip.h tcpto.c byte.h tcpto.c \ +datetime.h datetime.h tcpto.c readwrite.h tcpto.c + ./compile tcpto.c + +tcpto_clean.o: \ +compile tcpto_clean.c tcpto.h tcpto_clean.c open.h tcpto_clean.c \ +substdio.h tcpto_clean.c readwrite.h tcpto_clean.c + ./compile tcpto_clean.c + +timeoutconn.o: \ +compile timeoutconn.c timeoutconn.c timeoutconn.c timeoutconn.c \ +timeoutconn.c ndelay.h timeoutconn.c select.h select.h select.h \ +select.h timeoutconn.c error.h timeoutconn.c readwrite.h \ +timeoutconn.c ip.h timeoutconn.c byte.h timeoutconn.c timeoutconn.h \ +timeoutconn.c + ./compile timeoutconn.c + +timeoutread.o: \ +compile timeoutread.c timeoutread.h timeoutread.c select.h select.h \ +select.h select.h timeoutread.c error.h timeoutread.c readwrite.h \ +timeoutread.c + ./compile timeoutread.c + +timeoutwrite.o: \ +compile timeoutwrite.c timeoutwrite.h timeoutwrite.c select.h \ +select.h select.h select.h timeoutwrite.c error.h timeoutwrite.c \ +readwrite.h timeoutwrite.c + ./compile timeoutwrite.c + +token822.o: \ +compile token822.c stralloc.h gen_alloc.h stralloc.h token822.c \ +alloc.h token822.c str.h token822.c token822.h gen_alloc.h token822.h \ +token822.c gen_allocdefs.h gen_allocdefs.h gen_allocdefs.h token822.c + ./compile token822.c + +trigger.o: \ +compile trigger.c select.h select.h select.h select.h trigger.c \ +open.h trigger.c trigger.h trigger.c hasnpbg1.h trigger.c + ./compile trigger.c + +triggerpull.o: \ +compile triggerpull.c ndelay.h triggerpull.c open.h triggerpull.c \ +triggerpull.h triggerpull.c + ./compile triggerpull.c + +uint32.h: \ +tryulong32.c compile load uint32.h1 uint32.h2 + ( ( ./compile tryulong32.c && ./load tryulong32 && \ + ./tryulong32 ) >/dev/null 2>&1 \ + && cat uint32.h2 || cat uint32.h1 ) > uint32.h + rm -f tryulong32.o tryulong32 + +wait.a: \ +makelib wait_pid.o wait_nohang.o + ./makelib wait.a wait_pid.o wait_nohang.o + +wait_nohang.o: \ +compile wait_nohang.c wait_nohang.c wait_nohang.c haswaitp.h \ +wait_nohang.c + ./compile wait_nohang.c + +wait_pid.o: \ +compile wait_pid.c wait_pid.c wait_pid.c error.h wait_pid.c + ./compile wait_pid.c diff --git a/README b/README @@ -0,0 +1,199 @@ +qmail 1.01 +19970413 +Copyright 1997 +D. J. Bernstein, qmail@pobox.com + +qmail is a secure, reliable, efficient, simple message transfer agent. +It is meant as a replacement for the entire sendmail-binmail system on +typical Internet-connected UNIX hosts. See BLURB, BLURB2, BLURB3, and +BLURB4 for more detailed advertisements. + +INSTALL says how to set up and test qmail. If you're upgrading from +1.00, read UPGRADE instead. + +See http://pobox.com/~djb/qmail.html for other qmail-related software +and a pointer to the qmail mailing list. + +Other documentation here: RFC* explain solutions to several Internet +mail problems; all of these except RFCMXPS are implemented in qmail. +CHANGES and THANKS show how qmail has changed since it was first +released. SECURITY, INTERNALS, THOUGHTS, and TODO record many of the +qmail design decisions. + +The rest of this file is a list of systypes where various versions of +qmail have been reported to work. 0.90 was the first gamma version. 0.96 +was the final gamma version. 1.00 had exactly the same code as 0.96. To +see your systype, make systype; cat systype. + +1.00: a.ux-3.0-svr2-:-:-:mc68030-:- (tnx RF) +0.96: aix-3-2-:-:-:000011216700-:- (tnx JLB) +0.91: aix-3-2-:-:-:000109257500-:- +0.96: aix-4-1-:-:-:000088581000-:- (tnx HJB) +0.95: aix-4-1-:-:-:00061176a600-:- (tnx JS) +1.00: aix-4-1-:-:-:00910033a000-:- (tnx K2J) +0.91: aix-4-2-:-:-:006030934c00-:- +1.00: bsd.os-2.0.1-:i386-:-:i486-:- (tnx KR) +0.93: bsd.os-2.0.1-:i386-:-:pentium-:- +0.96: bsd.os-2.1-:i386-:-:-:- (tnx DAR) +1.00: bsd.os-2.1-:i386-:-:i486-:- (tnx RJC) +0.96: bsd.os-2.1-:i386-:-:pentium-:- (tnx DAR) +0.96: dgux-5.4r2.01-generic-:-:-:aviion-:- (tnx HWM) +0.93: dgux-5.4r3.10-generic-:-:-:aviion-:- (tnx HWM) +0.91: freebsd-2.0.5-release-:i386-:-:-:- (tnx TG) +0.92: freebsd-2.1-stable-:i386-:-:pentium.815\100-:- +0.90: freebsd-2.1.0-release-:i386-:-:cy486dlc-:- (tnx G2A) +1.00: freebsd-2.1.0-release-:i386-:-:i486-dx2-:- (tnx JLB) +1.00: freebsd-2.1.0-release-:i386-:-:i486-dx-:- (tnx chrisj=???) +0.96: freebsd-2.1.0-release-:i386-:-:pentium.735\90.or.815\100-:- (tnx MBS) +0.95: freebsd-2.1.5-release-:i386-:-:-:- (tnx NAA) +0.96: freebsd-2.1.5-release-:i386-:-:i486-dx-:- (tnx FN) +0.95: freebsd-2.1.5-release-:i386-:-:pentium.510\60.or.567\66-:- (tnx M2L) +0.95: freebsd-2.1.5-release-:i386-:-:pentium.735\90-:- (tnx AG) +0.92: freebsd-2.1.5-stable-:i386-:-:pentium.735\90.or.815\100-:- (tnx FE) +1.00: freebsd-2.1.6-release-:i386-:-:-:- (tnx TM) +0.96: freebsd-2.1.6-release-:i386-:-:Pentium-Pro.150-:- (tnx CH) +0.96: freebsd-2.1.6-release-:i386-:-:i486-dx-:- (tnx HCJ) +0.95: freebsd-2.1.6-release-:i386-:-:pentium.735\90.or.815\100-:- (tnx M2L) +0.96: freebsd-2.1.6.1-release-:i386-:-:pentium.735\90.or.815\100-:- (tnx MF) +1.00: freebsd-2.1.7-release-:i386-:-:pentium.735\90.or.815\100-:- (tnx JBB) +0.95: freebsd-2.2-beta_a-:i386-:-:i486-dx2-:- (tnx DA) +0.91: freebsd-2.2-current-:i386-:-:i486-dx-:- (tnx DC) +1.00: freebsd-2.2-release-:i386-:-:-:- (tnx MT) +1.00: freebsd-2.2.1-release-:i386-:-:-:- (tnx TM) +1.00: freebsd-2.2.1-release-:i386-:-:i486-dx2-:- (tnx BR) +1.00: freebsd-2.2.1-release-:i386-:-:pentium-:- (tnx IW) +0.92: freebsd-3.0-current-:i386-:-:pentium-:- (tnx J2M) +1.00: hp-ux-b.09.00-a-:-:-:9000.360-:- (tnx VV) +0.91: hp-ux-b.10.01-a-:-:-:9000.712-:- (tnx S2R) +0.94: hp-ux-b.10.01-a-:-:-:9000.715-:- (tnx BG) +0.91: hp-ux-b.10.01-a-:-:-:9000.801-:- (tnx S2T) +0.93: irix-5.3-02091401-:-:-:ip22-:- (tnx PW) +0.91: irix-5.3-11091811-:sgi-:-:ip19-:- (tnx BS) +0.96: irix-5.3-11091812-:-:-:ip22-:- (tnx JL) +1.00: irix-6.2-03131015-:-:-:ip22-:- (tnx SAS) +0.92: linux-1.2.13-:i386-:-:i386-:- (tnx RN) +1.00: linux-1.2.13-:i386-:-:i486-:- (tnx RF) +0.96: linux-1.2.13-:i386-:-:pentium-:- (tnx MEE) +0.91: linux-1.3.90-:alpha-:-:alpha-:- (tnx ES) +0.93: linux-1.99.6-:i386-:-:pentium-:- (tnx TG) +0.94: linux-2.0.0-:i386-:-:i486-:- (tnx PCO) +1.00: linux-2.0.0-:i386-:-:pentium-:- (tnx JJR) +0.91: linux-2.0.1-:alpha-:-:alpha-:- (tnx BET) +0.90: linux-2.0.1-:i386-:-:i486-:- (tnx DF) +0.93: linux-2.0.1-:i386-:-:pentium-:- (tnx FW) +0.95: linux-2.0.6-:i386-:-:pentium-:- +1.00: linux-2.0.6-:i386-:-:ppro-:- (tnx MR) +0.96: linux-2.0.7-:i386-:-:i486-:- (tnx TLM) +0.90: linux-2.0.7-:i386-:-:pentium-:- (tnx K2J) +0.94: linux-2.0.8-:i386-:-:i486-:- (tnx EP) +0.90: linux-2.0.9-:i386-:-:pentium-:- (tnx JM) +0.90: linux-2.0.10-:i386-:-:i486-:- (tnx JL) +0.90: linux-2.0.10-:i386-:-:pentium-:- (tnx S2R) +0.90: linux-2.0.11-:i386-:-:i486-:- (tnx ET) +0.92: linux-2.0.12-:i386-:-:i486-:- (tnx TRR) +0.90: linux-2.0.12-:i386-:-:ppro-:- (tnx SS) +0.96: linux-2.0.13-:i386-:-:pentium-:- (tnx BW) +0.90: linux-2.0.14-:i386-:-:pentium-:- (tnx PS) +0.91: linux-2.0.15-:i386-:-:pentium-:- (tnx CL) +0.90: linux-2.0.16-:i386-:-:i486-:- (tnx AP) +0.91: linux-2.0.17-:i386-:-:i486-:- (tnx JL) +0.91: linux-2.0.17-:i386-:-:pentium-:- (tnx SS) +0.95: linux-2.0.18-:i386-:-:i386-:- (tnx RN) +1.00: linux-2.0.18-:i386-:-:i486-:- (tnx JMS) +1.00: linux-2.0.18-:i386-:-:pentium-:- (tnx D2S) +0.92: linux-2.0.19-:alpha-:-:alpha-:- (tnx BET) +0.92: linux-2.0.20-:i386-:-:pentium-:- (tnx RJH) +0.92: linux-2.0.21-:i386-:-:i486-:- (tnx DCC) +0.91: linux-2.0.21-:i386-:-:pentium-:- (tnx root@contact=???) +0.92: linux-2.0.22-:i386-:-:i486-:- (tnx root@attila=???) +1.00: linux-2.0.22-:i386-:-:pentium-:- (tnx MDI) +1.00: linux-2.0.23-:i386-:-:i486-:- (tnx B2L) +0.93: linux-2.0.23-:i386-:-:pentium-:- (tnx IW) +0.93: linux-2.0.24-:i386-:-:i486-:- (tnx root@cerberus=???) +1.00: linux-2.0.24-:i386-:-:pentium-:- (tnx VV) +0.96: linux-2.0.25-:i386-:-:i486-:- (tnx BDB) +1.00: linux-2.0.25-:i386-:-:pentium-:- (tnx AP) +0.93: linux-2.0.25-:i386-:-:ppro-:- (tnx CS) +0.93: linux-2.0.26-:i386-:-:i486-:- (tnx blynch=???) +0.96: linux-2.0.26-:i386-:-:pentium-:- (tnx ESM) +0.93: linux-2.0.26-:i386-:-:ppro-:- (tnx modus@enews=???) +1.00: linux-2.0.27-:-:-:sparc-:- (tnx SVD) +1.00: linux-2.0.27-:i386-:-:i486-:- (tnx FPL) +1.00: linux-2.0.27-:i386-:-:pentium-:- (tnx root@bullwinkle=???) +1.00: linux-2.0.27-:i386-:-:ppro-:- (tnx DE) +1.00: linux-2.0.28-:i386-:-:i486-:- (tnx H2S) +1.00: linux-2.0.28-:i386-:-:pentium-:- (tnx root@duggy=???) +1.00: linux-2.0.28-:i386-:-:ppro-:- (tnx KJS) +1.00: linux-2.0.29-:i386-:-:i486-:- (tnx PK) +1.00: linux-2.0.29-:i386-:-:pentium-:- (tnx CL) +1.00: linux-2.0.29-:i386-:-:ppro-:- (tnx MMM) +0.92: linux-2.1.1-:i386-:-:i486-:- (tnx nick@sga2=???) +0.92: linux-2.1.7-:i386-:-:ppro-:- (tnx JL) +0.92: linux-2.1.9-:i386-:-:pentium-:- (tnx AC) +0.92: linux-2.1.10-:i386-:-:ppro-:- (tnx S3T) +0.96: linux-2.1.13-:i386-:-:i486-:- (tnx ML) +0.93: linux-2.1.13-:i386-:-:ppro-:- (tnx JL) +0.96: linux-2.1.14-:i386-:-:pentium-:- (tnx SCW) +0.96: linux-2.1.23-:i386-:-:pentium-:- (tnx JF) +0.96: linux-2.1.25-:i386-:-:i486-:- (tnx JBF) +0.96: linux-2.1.25-:i386-:-:pentium-:- (tnx UO) +1.00: linux-2.1.26-:i386-:-:i486-:- (tnx DK) +1.00: linux-2.1.27-:i386-:-:pentium-:- (tnx JF) +1.00: linux-2.1.28-:i386-:-:pentium-:- (tnx RGS) +1.00: linux-2.1.29-:i386-:-:i486-:- (tnx SJW) +0.93: netbsd-1.2-:amiga-:-:amiga.4000.(m68040.cpu/mmu/fpu)-:- (tnx OS) +0.96: netbsd-1.2-:i386-:-:pentium.(genuineintel.586-class.cpu)-:- (tnx GH) +0.91: netbsd-1.2-:mac68k-:-:apple.macintosh.se/30..(68030)-:- (tnx hauke=???) +0.92: netbsd-1.2b-:i386-:-:-:- (tnx MG) +0.93: netbsd-1.2b-:i386-:-:pentium.(genuineintel.586-class.cpu)-:- (tnx MG) +0.96: netbsd-1.2c-:pmax-:-:-:- (tnx JLW) +0.92: nextstep-3.3-:hppa-:-:7100lc-:- +0.92: nextstep-3.3-:i386-:-:pentium-:- +0.91: openbsd-1.2-:openbsd.m68k-:-:mac68k-:- (tnx AKB) +0.95: openbsd-2.0-:openbsd.i386-:-:i386-:- (tnx JPH) +0.96: openbsd-2.0-:openbsd.m68k-:-:mac68k-:- (tnx AKB) +1.00: openbsd-2.0-hoth#0-:openbsd.i386-:-:i386-:- (tnx MBS) +1.00: openbsd-2.0-mr_potatoe_head#2-:openbsd.i386-:-:i386-:- (JJMK) +0.96: openbsd-2.0-puma#1-:openbsd.m68k-:-:mac68k-:- (tnx AB) +0.92: osf1-v3.0-347-:-:-:alpha-:- (tnx B2H) +0.92: osf1-v4.0-386-:-:-:alpha-:- (tnx WW) +0.96: sco_sv-3.2-2-:-:-:i386-:- (tnx DEH) +0.96: sunos-4.1.3_u1-1-:sparc-:sun4-:sun4c-:sun4c- (tnx MBS) +0.91: sunos-4.1.3_u1-1-:sparc-:sun4-:sun4m-:sun4m- (tnx IS) +0.92: sunos-4.1.3_u1-2-:sparc-:sun4-:sun4c-:sun4c- (tnx ANR) +0.93: sunos-4.1.3_u1-4-:sparc-:sun4-:sun4m-:sun4m- (tnx J2B) +1.00: sunos-4.1.3_u1-4-:unknown-:sun4-:sun4m-:sun4m- (tnx J2B) +0.91: sunos-4.1.3_u1-9-:sparc-:sun4-:sun4m-:sun4m- (tnx TA) +0.91: sunos-4.1.4-2-:sparc-:sun4-:sun4c-:sun4c- (tnx ST) +0.95: sunos-4.1.4-2-:sparc-:sun4-:sun4m-:sun4m- (tnx TG) +0.95: sunos-4.1c-4.1.3-:sparc-:sun4-:sun4-:s4000- (tnx DBK) +0.91: sunos-5.4-generic-:sparc-:sun4-:sun4m-:sun4m- +0.96: sunos-5.4-generic_101945-10-:sparc-:sun4-:sun4m-:sun4m- (tnx W2K) +0.93: sunos-5.4-generic_101945-23-:-:sun4-:sun4m-:sun4m- (tnx JP) +0.92: sunos-5.4-generic_101945-23-:sparc-:-:sun4c-:- (tnx BH) +1.00: sunos-5.4-generic_101945-34-:sparc-:sun4-:sun4m-:sun4m- (tnx ACB) +0.92: sunos-5.4-generic_101945-36-:-:sun4-:sun4c-:sun4c- (tnx JP) +0.95: sunos-5.4-generic_101945-43-:-:sun4-:sun4m-:sun4m- (tnx JP) +0.90: sunos-5.4-generic_101946-07-:-:i86pc-:i86pc-:i86pc- (tnx CR) +0.96: sunos-5.4-generic_101946-35-:i386-:i86pc-:i86pc-:i86pc- (tnx CK) +0.96: sunos-5.5-generic-:i386-:i86pc-:i86pc-:i86pc- (tnx M2G) +0.93: sunos-5.5-generic-:sparc-:sun4-:sun4c-:sun4c- (tnx CG) +1.00: sunos-5.5-generic-:sparc-:sun4-:sun4m-:sun4m- (tnx SG) +1.00: sunos-5.5-generic_103093-02-:sparc-:sun4-:sun4m-:sun4m- (tnx RF) +0.91: sunos-5.5-generic_103093-03-:sparc-:sun4-:sun4d-:sun4d- (tnx M3S) +0.96: sunos-5.5-generic_103093-03-:sparc-:sun4-:sun4m-:sun4m- (tnx RDM) +0.91: sunos-5.5-generic_103093-03-:sparc-:sun4-:sun4u-:sun4u- (tnx olav=???) +0.92: sunos-5.5-generic_103093-05-:sparc-:sun4-:sun4m-:sun4m- (tnx KE) +0.93: sunos-5.5-generic_103093-06-:sparc-:sun4-:sun4d-:sun4d- (tnx KT) +1.00: sunos-5.5-generic_103093-06-:sparc-:sun4-:sun4m-:sun4m- (tnx TEE) +0.94: sunos-5.5-generic_103094-01-:i386-:i86pc-:i86pc-:i86pc- (tnx MD) +0.91: sunos-5.5-generic_103094-03-:i386-:i86pc-:i86pc-:i86pc- (tnx ding=???) +1.00: sunos-5.5.1-generic-:i386-:i86pc-:i86pc-:i86pc- (tnx DML) +1.00: sunos-5.5.1-generic-:sparc-:sun4-:sun4m-:sun4m- (tnx JMT) +1.00: sunos-5.5.1-generic-:sparc-:sun4-:sun4u-:sun4u- (tnx TH) +0.96: sunos-5.5.1-generic_103640-02-:sparc-:sun4-:sun4m-:sun4m- (tnx SGC) +0.95: sunos-5.5.1-generic_103640-03-:sparc-:sun4-:sun4m-:sun4m- (tnx MRG) +1.00: sunos-5.5.1-generic_103640-03-:sparc-:sun4-:sun4u-:sun4u- (tnx EG) +1.00: sunos-5.5.1-generic_103640-05-:sparc-:sun4-:sun4m-:sun4m- (tnx L2L) +0.96: sunos-5.5.1-generic_patch-:i386-:i86pc-:i86pc-:i86pc- (tnx DK) +0.96: ultrix-4.3-1-:pmax-:-:risc-:- (tnx YF) diff --git a/RFCHCSC b/RFCHCSC @@ -0,0 +1,37 @@ +The Hash Convention For Mail System Status Codes (HCMSSC) +D. J. Bernstein, djb@pobox.com +19970201 + + +1. Introduction + + RFC 1893 defines codes for mail delivery failures. For example, + code 5.1.1 means that the specified mailbox does not exist. + + The qmail package sprays these codes all over the place, by adding a + code to the text of every error message, preceded by a hash mark and + surrounded by parentheses. It avoids using hash marks elsewhere. + + +2. Examples + + Here is a typical HCMSSC SMTP error message: + + 421 load average too high, please come back later (#4.3.2) + + Here is part of a typical HCMSSC bounce message: + + <mail-loop@silverton.berkeley.edu>: + This is looping; it already has my Delivered-To line. (#5.7.1) + + But qmail doesn't use HCMSSC when it repeats another MTA's error + message: + + <foo@heaven.af.mil>: + 127.3.4.5 does not like recipient. + Remote host said: 550 <foo>... User unknown (#5.1.1) + + +3. Security considerations + + Don't take drastic action upon seeing "(#"; it might not be HCMSSC. diff --git a/RFCLOOPS b/RFCLOOPS @@ -0,0 +1,338 @@ +Tools in the war on mail loops +D. J. Bernstein, djb@pobox.com +19970201 + + +1. Introduction + + An automailer means any program that receives a mail message and + automatically sends one or more mail messages. This term is meant to + include not only a mail-based server, such as a mailing list exploder + or a vacation program, but also an SMTP server, which receives a + message from the network and relays it to a local or remote user. + + In a network full of automailers, any mistake can cause a mail loop. + Since some automailers generate several outputs in response to a + single input, a loop can produce an exponential explosion of mail. + + All the automailers in the qmail package follow a general philosophy + designed to prevent mail loops and limit the damage from any loops + that do occur. These automailers have been repeatedly observed to + fail safe: they stop loops in the face of typical failures by other + hosts. This document explains the philosophy and describes the + automailers. + + To some extent the philosophy here simply repeats and amplifies + standard practice as codified in RFC 974 and RFC 1123. Unfortunately, + the standards do not adequately control bounce loops, since they do + not recognize that postmasters want to see double bounces; they do + not adequately control relaying loops; and they do not prevent + cross-host forwarding loops. + + Terminology: The mail message received by an automailer is called + input. The mail messages sent by an automailer are called outputs. + For simplicity, this document focuses on the case that the input has + just one envelope recipient. + + REMINDER: This document describes the automailers in the qmail + package. Other packages include automailers that do not fit the + descriptions given here. + + Beware that the war on mail loops can never be won: any method of + preventing mail loops can be subverted by other hosts. I welcome + further development of techniques that work well in practice. + + +2. Basics + + The output from an automailer is always further down the following + list than the input. + + 0 hops, <sender> is neither <> nor <#@[]> normal messages + 1 hop, <sender> is neither <> nor <#@[]> + 2 hops, <sender> is neither <> nor <#@[]> + etc. + 0 hops, <sender> is <> bounces + 1 hop, <sender> is <> + 2 hops, <sender> is <> + etc. + 0 hops, <sender> is <#@[]> double bounces + 1 hop, <sender> is <#@[]> + 2 hops, <sender> is <#@[]> + etc. + + Here sender means the envelope sender address. Hops means the number + of Received and Delivered-To fields in the header. See sections 3.3 + and 3.4 for an explanation of <> and <#@[]>. + + Consequently, no automailer ever generates an entirely new normal + message in response to a normal message. If the output is a normal + message, it always has more hops than the input. + + When input and output are both normal messages, both bounces, or both + double bounces, the output header is essentially the same as the + input header. However, when an automailer moves from a normal message + to a bounce, or from a bounce to a double bounce, it generates an + entirely new header. + + An automailer may refuse to operate if the input has too many hops. + The definition of too many hops depends on the automailer. This + practice is called hop counting. Note that some existing messages + legitimately take as many as 20 hops. One automailer uses a limit of + 100 hops; this will be adequate for all messages in the foreseeable + future. + + Hop counting is a weapon of last resort. It will, if correctly + implemented, prevent all infinite loops; however, even a finite loop + can do practically infinite damage, as illustrated in section 4.3. + + +3. Pre-delivery automailers + + Conceptually: The input is a message that has not yet reached its + envelope recipient address. It is fed to a relay, which attempts to + deliver the message directly to, or at least closer to, that address; + if the relay fails permanently, the message is fed to a bouncer or a + double-bouncer. Relays, bouncers, and double-bouncers are examples of + pre-delivery automailers. + + A pre-delivery automailer produces at most one output. + + The basic weapon against pre-delivery mail loops is gravity. A normal + message always moves closer to its envelope recipient, according to a + notion of distance defined in section 3.1. If it bounces before + reaching the recipient, it turns into a bounce message, which always + moves closer to the original envelope sender. If that in turn + bounces, it turns into a double bounce, which always moves closer to + a local postmaster. (Triple bounces do not exist.) + + +3.1. Distance + + The distance from a DNS domain D to a recipient U@R is defined as + follows, when R has an MX list: the minimum preference of D in the + MX list, or 100000 if D does not appear in the list. + + When R has no MX records, the distance from R to U@R is defined as 0, + and the distance from any other domain to U@R is defined as 100000. + + Exception: If R is an alias, i.e., if R has a CNAME record, the + distance from any domain to U@R is defined as 500000. + + The distance from a host H to U@R is defined as the minimum distance + to U@R from any domain that touches H. (``D touches H'' means ``D has + an A record listing one of H's IP addresses.'') + + Exception: If H does not accept mail from the network, its distance + to any recipient is defined as 999999. + + +3.2. Relays + + A relay is a pre-delivery automailer that sends the output towards + the envelope recipient. What this means for intra-host relays is not + discussed here. What this means for cross-host relays is the + following: if the relay is at host H, and it sends its output to host + T, then the distance from T to the output envelope recipient is + always smaller than the distance from H to the input envelope + recipient. + + The following facts guarantee that certain cross-host relay behavior + is safe. For proofs of these facts, see Appendix A. + + Fact 1: If R is an alias for X, X is not an alias, D touches T, + and T accepts mail from the network, then the distance from T to + U@X is smaller than the distance from H to U@R. + + Fact 2: If R is not an alias, R has no MX records, H is not + touched by R, T is touched by R, and T accepts mail from the + network, then T is closer to U@R than H is. + + Fact 3: If R is not an alias, R has an MX record with domain X and + preference p, H is not touched by any of the domains in the MX + list for R with preference <= p, T is touched by X, and T accepts + mail from the network, then T is closer to U@R than H is. + + Also, a host that does not accept mail from the network can relay + messages to a nearby hub. + + A relay adds a new Received header field to the top of the output. + Other than this, the output header, body, and envelope are exactly + the same as the input header, body, and envelope. Exception: If the + input envelope recipient is U@R, R is an alias for X, and X is not + an alias, the output envelope recipient is U@X. + + +3.3. Bouncers + + A bouncer is a pre-delivery automailer that lets the envelope sender + know what happened to a message. Most bouncers send failure notices. + Some bouncers, such as vacation servers and echo servers, send + success notices. + + In a bouncer's output, the envelope sender is <>, and the envelope + recipient is the input envelope sender. A bouncer refuses to operate + if the input envelope sender is <> or <#@[]>. + + Some mailers on the Internet do not understand the <> convention. In + fact, some mailers will rewrite <> as <@host>. So any message with an + envelope recipient of <> or <@host> is discarded upon local delivery. + + Unlike a relay, a bouncer produces output with a new header, not + simply a copy of the input header. For example: + + (envelope) from <> to <djb@silverton.berkeley.edu> + Date: 2 Jan 1996 03:38:25 GMT + From: DELIVERY NOTICE SYSTEM <MAILER-DAEMON@heaven.af.mil> + To: djb@silverton.berkeley.edu + Subject: failure notice + + However, the body of the bounce indicates the relevant input envelope + recipient, as well as the Message-ID of the input, if the input had a + Message-ID. The body of a failure notice includes a copy of the + entire input message. + + +3.4. Double-bouncers + + A double-bouncer is a pre-delivery automailer that informs a local + postmaster of permanent failures to deliver bounce messages. Such + failures are generally caused by poorly configured hosts that produce + normal messages with faulty envelope sender addresses. + + A double-bouncer refuses to operate unless the input envelope sender + is <>. The output envelope sender from a double-bouncer is <#@[]>; + note that <#@[]> cannot be used as an SMTP envelope sender under + RFC 821. The output envelope recipient is predetermined. + + Note that double bounces are not suggested by RFC 1123. However, + faulty envelope sender addresses are usually configuration errors + that can and should be fixed. Some postmasters, faced with mail + software that throws away double bounces, resort to keeping copies of + all bounces; but single bounces are rarely the postmaster's problem. + + +4. Post-delivery automailers + + Conceptually: The input is a message that has reached its envelope + recipient address. It is fed to a post-delivery automailer at that + address. + + The basic weapon against post-delivery loops is a new header field, + Delivered-To, tracing all the forwarders and mailing lists that a + message has been through. This field has the side benefit of making + it much easier for a user (or for a postmaster seeing a bounce) to + figure out the path that the message took. Delivered-To is similar to + RFC 1327's DL-Expansion-History, but (1) it omits the time stamp, + removing any need for parsing, and (2) it has a much better name. + + +4.1. Exploders and repliers + + There are two basic types of post-delivery automailers: exploders, + where the output envelope recipients are predetermined; and repliers, + where there is just one output, with envelope recipient determined + from the input. + + Repliers normally determine the output envelope recipient as either + the input Reply-To header field, if it exists; or else the input + From header field, if it exists; or else the envelope sender. A + replier never produces an output to <> or <#@[]>. + + Exploders are classified into mailing lists, where the output + envelope senders are predetermined, and forwarders, where every + output has envelope sender equal to the original envelope sender. + + Exception: if the input envelope sender is <> or <#@[]>, then the + output envelope senders are equal to the input envelope sender, even + for a mailing list. + + Note that, if the envelope sender of a mailing list with M bad + addresses is another exploder with E bad addresses, the local + postmaster will receive EM double bounces for each message to the + mailing list. + + +4.2. Delivered-To + + Every post-delivery automailer adds a new Delivered-To header field + to the top of each output. + + The contents of the Delivered-To field are typically the address of + the automailer, i.e., the input envelope recipient, conventionally + without any quoting. The contents of the Delivered-To field are in + any case entirely predetermined. The automailer checks if exactly the + same Delivered-To field already appears in the header; if so, it + refuses to operate. + + A post-delivery automailer preserves existing Delivered-To and + Received fields. In fact, a post-delivery automailer generally + preserves all header fields. The exceptions are limited to known + fields that are not used for loop detection and that must be removed + for correct operation. For example, a replier generally changes the + body of a message and thus should not preserve the SVR4 + Content-Length field. + + +4.3. An example + + Aliases and mailing lists are highly dangerous, because they can + generate several outputs for each input. + + Here is an extreme example. A user has three accounts, and wants any + message to any of the accounts to be delivered to all three. So he + forwards luser@host1 to luser@host2 and luser@host3, forwards + luser@host2 to luser@host1 and luser@host3, and forwards luser@host3 + to luser@host1 and luser@host2. + + Without Delivered-To, someone who sends a message to luser@host1 will + receive a practically infinite series of bounces. For example, with a + hop count limit of 50, the sender will receive 1125899906842624 + bounces. + + If all the hosts, or two out of the three, support Delivered-To, the + message will bounce just a few times. If just one of the hosts + supports Delivered-To, it will be the unfortunate victim of a loop + between the other two hosts---although the total number of bounces + will drop from practically infinite down to a few hundred, with + typical hop count limits. + + +Appendix A. Proofs of correctness for MX handling + + Section 3.2 states three facts about the notion of distance defined + in section 3.1. Here are mathematical proofs of those facts. + + Symbols: D, E, R, and X are domains; H and T are hosts; p and q are + nonnegative integers. {} is the empty set. + + Hypotheses: M(R), the ``MX list for R,'' is a set of pairs (p,D) + where p <= 65535. There is a set A of domains, called ``aliases.'' + There is a relation D->H, called ``D touches H.'' There is a set N of + hosts, called ``hosts that accept mail from the network.'' + + Definitions: m(D,R) = min { p: p = 100000 or (p,D) in M(R) } when + M(R) is nonempty. When M(R) is empty, m(D,R) is 0 if D = R, 100000 + otherwise. f(D,R) is defined as 500000 if R is in A, m(D,R) + otherwise; this is the ``distance from D to U@R,'' for any U. g(H,R) + is defined as min { f(D,R): D->H } if H is in N, 999999 otherwise; + this is the ``distance from H to U@R,'' for any U. + + Fact 1 (generalized): If R is in A, X is not in A, D->T, and T is in + N, then g(T,X) < g(H,R). Proof: R is in A, so f(E,R) = 500000 for any + E; thus g(H,R) >= 500000. X is not in A, so f(D,X) = m(D,X) <= + 100000; hence g(T,X) <= f(D,X) <= 100000 < g(H,R). + + Fact 2: If R is not in A, M(R) = {}, R->T, T is in N, and not R->H, + then g(T,R) < g(H,R). Proof: f(R,R) = m(R,R) = 0 since R is not in A + and M(R) = {}. T is in N so g(T,R) <= f(R,R) = 0 so g(T,R) = 0. + Suppose that g(H,R) <= g(T,R). Then g(H,R) = 0, so f(D,R) = 0 for + some D with D->H, so m(D,R) = 0. But then D = R by definition of m, + so R->H. Contradiction. Thus g(T,R) < g(H,R). + + Fact 3: If R is not in A, (p,X) is in M(R), X->T, T is in N, and + (q,D) is not in M(R) whenever D->H and q <= p, then g(T,R) < g(H,R). + Proof: First m(X,R) <= p. R is not in A, so f(X,R) = m(X,R). T is in + N, so g(T,R) <= f(X,R). Thus g(T,R) <= p. Suppose that g(H,R) <= p. + Then f(D,R) <= p for some D with D->H, so m(D,R) <= p. But then + (m(D,R),D) is in M(R). Contradiction. Thus g(T,R) <= p < g(H,R). diff --git a/RFCMXPS b/RFCMXPS @@ -0,0 +1,122 @@ +The Mail Exchanger Protocol Switch (MXPS) +D. J. Bernstein, djb@pobox.com +19970201 + + +1. Introduction + + Mail messages today are transferred through the Simple Mail Transfer + Protocol (SMTP). One can imagine other protocols that achieve the + same results as SMTP but that, for example, use the network more + efficiently. + + The Mail Exchanger Protocol Switch (MXPS) lets other protocols + compete with SMTP. A receiver can announce its support for another + protocol while operating properly with MXPS-ignorant senders. A + sender can check for support, with no overhead, while operating + properly with MXPS-ignorant receivers. + + All receivers must support SMTP, i.e., must be able to receive + messages via SMTP. Similarly, all senders must be able to send + messages via SMTP. + + +2. The protocol switch + + MXPS abuses the preference field of MX records. A protocol is + assigned to each possible preference. + + SMTP is assigned to preferences 0 through 10000. + + The initial MXPS experiment will involve preferences between 12800 + and 13055 inclusive. These preferences are sliced into 16 portions: + + 12800, 12816, 12832, 12848, 12864, ..., 13040: slice #0 (SMTP) + 12801, 12817, 12833, 12849, 12865, ..., 13041: slice #1 (QMTP) + 12802, 12818, 12834, 12850, 12866, ..., 13042: slice #2 + ... + 12815, 12831, 12847, 12863, 12879, ..., 13055: slice #15 + + Preferences in slice #0 are assigned SMTP. Preferences in slice #1 + are assigned the Quick Mail Transfer Protocol (QMTP). Preferences in + the remaining slices may be assigned protocols in the future. + + A receiver must support the protocol assigned to its preference. More + precisely, if an MX record points to domain D, and the MX preference + is assigned protocol P, then every host listed as an A record for D + must support protocol P. + + When a sender, following the procedure outlined in RFC 974 (and + modified by RFC 1123), attempts to deliver a mail message as + specified by that MX record, it may use protocol P instead of SMTP. + If it does not support protocol P, it may treat the attempt as a + temporary failure and go on to the next MX record. However, the + sender must not skip every MX record. + + MX records must never use unassigned preferences. A sender may treat + an unassigned preference as referring to SMTP. + + Example: + + A.EXAMPLE.ORG IN MX 12801 A.EXAMPLE.ORG + B.EXAMPLE.ORG IN MX 12801 A.EXAMPLE.ORG + IN MX 12816 C.EXAMPLE.ORG + + A sender with a message for A.EXAMPLE.ORG will try A.EXAMPLE.ORG by + QMTP. If it does not support QMTP, it will try SMTP instead. Note + that A.EXAMPLE.ORG must support both QMTP and SMTP. + + A sender with a message for B.EXAMPLE.ORG will try A.EXAMPLE.ORG by + QMTP, then C.EXAMPLE.ORG by SMTP. If it does not support QMTP, it may + try SMTP instead of QMTP, or it may skip A.EXAMPLE.ORG. + + Some of the above requirements might be violated if current + MXPS-ignorant domains use any preferences above 10000. Mail could be + unnecessarily rejected if any existing MXPS-ignorant domains have a + best-preference MX above 10000. I do not know any examples of such + domains. + + +3. Protocol requirements + + MXPS operates purely at the link level. It does not change the + fundamental nature of Internet mail. + + The function of a mail transfer protocol is to transmit a message, as + described below, together with an envelope sender address and one or + more envelope recipient addresses. + + A recipient address is a sequence of characters---i.e., nonnegative + integers---including an ASCII @ (64). It is parsed as box@dom, where + dom does not contain an @. The interpretation of box is up to the + hosts listed as MX records for dom. A sender address may contain an + @, in which case it is also of the form box@dom; or it may be a + special address, such as the empty string. + + A mail message is structured as a sequence of lines. A line is a + sequence of characters. Every mail transfer protocol must be able to + transmit all sufficiently short boring mail messages. A boring mail + message is one where (1) no line has more than 80 characters and (2) + each character is either 9 or between 32 and 127 inclusive. Note that + RFC 1341 defines a mechanism for encoding a message with characters + between 0 and 255 inclusive as a boring mail message of similar + length. + + The receiver must indicate, for each recipient address, either + acceptance, permanent rejection, or temporary rejection of the + message. Acceptance means that the receiver has taken responsibility, + in the sense of RFC 1123, section 5.3.3, for delivering the message + to that recipient. Rejection means that the receiver will not deliver + the message to that recipient. + + Mail transfer protocols may vary in many details, such as line + encodings, the means of expressing acceptance or rejection, the + maximum number of allowable recipients per envelope, the encoding of + envelope addresses, the nature of optional protocol extensions, etc. + + +4. Security considerations + + MXPS does not change the following facts: An attacker who can subvert + the Domain Name System can steal or forge mail. An attacker who can + subvert TCP/IP can also steal or forge mail. diff --git a/RFCNETSTR b/RFCNETSTR @@ -0,0 +1,88 @@ +Netstrings +D. J. Bernstein, djb@pobox.com +19970201 + + +1. Introduction + + A netstring is a self-delimiting encoding of a string. Netstrings are + very easy to generate and to parse. Any string may be encoded as a + netstring; there are no restrictions on length or on allowed bytes. + Another virtue of a netstring is that it declares the string size up + front. Thus an application can check in advance whether it has enough + space to store the entire string. + + Netstrings may be used as a basic building block for reliable network + protocols. Most high-level protocols, in effect, transmit a sequence + of strings; those strings may be encoded as netstrings and then + concatenated into a sequence of characters, which in turn may be + transmitted over a reliable stream protocol such as TCP. + + Note that netstrings can be used recursively. The result of encoding + a sequence of strings is a single string. A series of those encoded + strings may in turn be encoded into a single string. And so on. + + In this document, a string of 8-bit bytes may be written in two + different forms: as a series of hexadecimal numbers between angle + brackets, or as a sequence of ASCII characters between double quotes. + For example, <68 65 6c 6c 6f 20 77 6f 72 6c 64 21> is a string of + length 12; it is the same as the string "hello world!". + + Although this document restricts attention to strings of 8-bit bytes, + netstrings could be used with any 6-bit-or-larger character set. + + +2. Definition + + Any string of 8-bit bytes may be encoded as [len]":"[string]",". + Here [string] is the string and [len] is a nonempty sequence of ASCII + digits giving the length of [string] in decimal. The ASCII digits are + <30> for 0, <31> for 1, and so on up through <39> for 9. Extra zeros + at the front of [len] are prohibited: [len] begins with <30> exactly + when [string] is empty. + + For example, the string "hello world!" is encoded as <31 32 3a 68 + 65 6c 6c 6f 20 77 6f 72 6c 64 21 2c>, i.e., "12:hello world!,". The + empty string is encoded as "0:,". + + [len]":"[string]"," is called a netstring. [string] is called the + interpretation of the netstring. + + +3. Sample code + + The following C code starts with a buffer buf of length len and + prints it as a netstring. + + if (printf("%lu:",len) < 0) barf(); + if (fwrite(buf,1,len,stdout) < len) barf(); + if (putchar(',') < 0) barf(); + + The following C code reads a netstring and decodes it into a + dynamically allocated buffer buf of length len. + + if (scanf("%9lu",&len) < 1) barf(); /* >999999999 bytes is bad */ + if (getchar() != ':') barf(); + buf = malloc(len + 1); /* malloc(0) is not portable */ + if (!buf) barf(); + if (fread(buf,1,len,stdin) < len) barf(); + if (getchar() != ',') barf(); + + Both of these code fragments assume that the local character set is + ASCII, and that the relevant stdio streams are in binary mode. + + +4. Security considerations + + The famous Finger security hole may be blamed on Finger's use of the + CRLF encoding. In that encoding, each string is simply terminated by + CRLF. This encoding has several problems. Most importantly, it does + not declare the string size in advance. This means that a correct + CRLF parser must be prepared to ask for more and more memory as it is + reading the string. In the case of Finger, a lazy implementor found + this to be too much trouble; instead he simply declared a fixed-size + buffer and used C's gets() function. The rest is history. + + In contrast, as the above sample code shows, it is very easy to + handle netstrings without risking buffer overflow. Thus widespread + use of netstrings may improve network security. diff --git a/RFCNRUDT b/RFCNRUDT @@ -0,0 +1,89 @@ +Notice-Requested-Upon-Delivery-To (NRUDT) +D. J. Bernstein, djb@pobox.com +19970201 + + +1. Introduction + + The UNIX sendmail program has for many years supported a + Return-Receipt-To (RRT) header field that requests a notice of + successful final delivery. + + Notice-Requested-Upon-Delivery-To (NRUDT) has the same basic + function. The big difference is that RRT lists the sender's address, + while NRUDT lists the recipient's address. + + This change is critical. RRT works poorly for messages to multiple + recipients, because it requests a notice from every recipient. RRT in + a message to a large mailing list produces a giant, usually + unintentional, flood of mail. This problem is so severe that RRT has + been disabled in recent versions of sendmail. + + NRUDT is designed to be adopted immediately, with minimal disruption, + as a solution to the problems of RRT. Note that NRUDT is merely a + request for notification; unlike the link-level Delivery Status + Notification SMTP extension, NRUDT does not provide a guarantee of + notification. + + NRUDT is supported by the qreceipt program in the qmail package. + + +2. Syntax + + NRUDT is a field in the header of an RFC 822 mail message. It has the + following syntax: + + "Notice-Requested-Upon-Delivery-To" ":" 1#address + + See RFC 822 for more information about header fields and addresses. + + NRUDT requests that, upon final delivery of the message to any of the + specified addresses, the sender be notified. Note that more than one + address can appear in a single NRUDT header field. Multiple NRUDT + header fields should not appear in a single message. + + +3. Response + + Upon successful final delivery of a message to any address listed in + an NRUDT header field, the host performing delivery may, if desired, + generate a success notice. + + The success notice is similar to a failure notice as described in RFC + 1123. Its envelope sender is <>. Its envelope recipient is the + envelope sender of the original message; however, if the envelope + sender of the original message is <>, a success notice is not sent. + + The body of the success notice does not contain a copy of the + original message, but it does indicate the Message-ID of the original + message, as well as the relevant recipient address. + + A success notice may indicate delivery to several addresses. For + example, given the following message: + + (envelope) from djb@silverton.berkeley.edu + (envelope) to god@heaven.af.mil, angels@heaven.af.mil + Date: 1 Jan 1996 21:43:34 GMT + From: "D. J. Bernstein" <djb@silverton.berkeley.edu> + Message-Id: <19960101214334.8529.qmail@silverton.berkeley.edu> + Notice-Requested-Upon-Delivery-To: God <god@heaven.af.mil>, + angels@heaven.af.mil (You Know Who You Are) + ... + + a host may respond as follows: + + (envelope) from <> to djb@silverton.berkeley.edu + Date: 1 Jan 1996 21:43:37 GMT + From: DELIVERY NOTICE SYSTEM <MAILER-DAEMON@heaven.af.mil> + To: djb@silverton.berkeley.edu + Subject: success notice + + I delivered <19960101214334.8529.qmail@silverton.berkeley.edu> + to the following local mailboxes: + + god@heaven.af.mil + angels@heaven.af.mil + + Thanks for asking. + + However, a success notice is never merged with a failure notice. diff --git a/RFCQMTP b/RFCQMTP @@ -0,0 +1,229 @@ +Quick Mail Transfer Protocol (QMTP) +D. J. Bernstein, djb@pobox.com +19970201 + + +1. Introduction + + The Quick Mail Transfer Protocol (QMTP) is a replacement for the + Simple Mail Transfer Protocol (SMTP). QMTP eliminates any need for + end-of-line scanning between hosts with the same end-of-line + convention. It features automatic pipelining and chunking, 8-bit + transmission, prior declaration of the message size, and efficient + batching. It is designed to be very easy to implement. + + QMTP is supported by the qmail-qmtpd and maildir2qmtp programs in the + qmail package. + + In this document, a string of 8-bit bytes may be written in two + different forms: as a series of hexadecimal numbers between angle + brackets, or as a sequence of ASCII characters between double quotes. + For example, <68 65 6c 6c 6f 20 77 6f 72 6c 64 21> is a string of + length 12; it is the same as the string "hello world!". Note that + these notations are part of this document, not part of the protocol. + + +2. Protocol + + A QMTP client connects to a QMTP server, as discussed in section 7, + over a reliable stream protocol allowing transmission of 8-bit bytes. + + Protocol outline: the client sends one or more packages; after each + package, the server sends back some responses. + + The client begins by sending a package. A package contains a mail + message, an envelope sender address, and one or more envelope + recipient addresses. See section 4 for the format of a package. + + When the server sees the end of the package, it sends back a series + of responses, one response for each envelope recipient address, in + the same order as given by the client. The server is not permitted to + change the order under any circumstances, even if two addresses are + the same. See section 5 for the format of a response. + + The server is not permitted to send any portion of its responses to a + package until the client has sent the final byte of the package. The + client is permitted to close the connection before sending the final + byte of the package; in this case, the server must throw away the + package without attempting to deliver the message. However, the + server must not throw away previously accepted messages. + + The client does NOT need to wait for a server response before sending + another package. The server must NOT throw away incoming data when it + sends a response. It is the client's responsibility to avoid + deadlock: if it sends a package before receiving all expected server + responses, it must continuously watch for those responses. The server + is permitted to delay its responses if further data has already shown + up from the client; while it is delaying responses, it must not pause + to wait for further data for the client. + + The server is permitted to close the connection at any time, although + high-quality servers will try to avoid doing so. Any response not + received by the client indicates a temporary failure. + + A QMTP session should take at most 1 hour. Both sides are expected + to close the connection after this time. + + +3. Messages + + In this document, an ``8-bit mail message'' means a sequence of + lines. Each line is a string of zero or more 8-bit bytes. + + A message is called ``safe'' if none of its bytes are <0a>. + + Implementation note: Here is the intended interpretation of text + files as messages under some current operating systems. Under DOS, a + message is stored on disk as + + first line, <0d 0a>, second line, <0d 0a> ... <0d 0a>, last line. + + Under UNIX, a message is stored on disk as + + first line, <0a>, second line, <0a> ... <0a>, last line. + + Notice that both of these encodings are reversible for safe messages. + + In practice, it is very common for the last line to be empty. Many + existing utilities refer to the last line as a ``partial line'' and + ignore it whether or not it is empty. + + +4. Packages + + A package is the concatenation of three strings: + + first, an encoded 8-bit mail message; + second, an encoded envelope sender address; + third, an encoded series of encoded envelope recipient addresses. + + Each envelope address is a string of 8-bit bytes. The interpretation + of addresses depends on the environment in which QMTP is used and is + outside the scope of this document. Each address is encoded as a + netstring, as discussed in section 6. The series of encoded recipient + addresses is in turn encoded as a netstring. + + A message is encoded as a string of 8-bit bytes in one of two ways: + + Encoding #1 is <0d>, the first line, <0d 0a>, the second line, + <0d 0a>, the third line, ..., <0d 0a>, the last line. + + Encoding #2 is <0a>, the first line, <0a>, the second line, <0a>, + the third line, ..., <0a>, the last line. + + This string of 8-bit bytes is in turn encoded as a netstring, as + discussed in section 6. + + Every server must be prepared to handle encoding #1 and encoding #2. + A server must not reject a message merely because of its encoding. + + Implementation note: The intent of encoding #1 and encoding #2 is to + allow very straightforward handling of text files under DOS and UNIX + respectively. The programmer can print <0d> or <0a> and then simply + copy the file. + + +5. Responses + + Each response is a nonempty string of 8-bit bytes, encoded as a + netstring. The first byte of the string is one of the following: + + "K" The message has been accepted for delivery to this envelope + recipient. This is morally equivalent to the 250 response to + DATA in SMTP; it is subject to the reliability requirements + of RFC 1123, section 5.3.3. + + "Z" Temporary failure. The client should try again later. + + "D" Permanent failure. + + The remaining bytes are a description of what happened. It is + expected that the description, when interpreted as UTF-2 characters, + (1) will be human-readable, (2) will not repeat the envelope + recipient address, and (3) will not include formatting characters + other than <20>. However, these expectations are not requirements, + and the client should be ready for arbitrary bytes from the server. + + Descriptions beginning with <20> are reserved for future extensions. + In descriptions not beginning with <20>, the character "#" must not + appear except in HCMSSC codes. + + A server must NOT accept a safe message unless it can store the + message without corruption. More precisely: if the encoded message + sent by the client matches the encoding of some safe message M, then + acceptance means that the server is accepting responsibility to + deliver M to the envelope recipient. (There is at most one + possibility for M, since encodings are reversible on safe messages.) + Deletion of nulls is NOT permissible; a server that deletes nulls + must reject any message containing nulls. Folding of long lines and + high-bit stripping are also NOT permissible. + + Servers are permitted to change unsafe messages. + + +6. Netstrings + + Any string of 8-bit bytes may be encoded as [len]":"[string]",". + Here [string] is the string and [len] is a nonempty sequence of ASCII + digits giving the length of [string] in decimal. The ASCII digits are + <30> for 0, <31> for 1, and so on up through <39> for 9. Extra zeros + at the front of [len] are prohibited: [len] begins with <30> exactly + when [string] is empty. + + For example, the string "hello world!" is encoded as <31 32 3a 68 + 65 6c 6c 6f 20 77 6f 72 6c 64 21 2c>, i.e., "12:hello world!,". The + empty string is encoded as "0:,". + + [len]":"[string]"," is called a netstring. [string] is called the + interpretation of the netstring. + + +7. Encapsulation + + QMTP may be used on top of TCP. A QMTP-over-TCP server listens for + TCP connections on port 209. + + +8. Examples + + A client opens a connection and sends the concatenation of the + following strings: + + "246:" <0a> + "Received: (qmail-queue invoked by uid 0);" + " 29 Jul 1996 09:36:40 -0000" <0a> + "Date: 29 Jul 1996 11:35:35 -0000" <0a> + "Message-ID: <19960729113535.375.qmail@heaven.af.mil>" <0a> + "From: God@heaven.af.mil" <0a> + "To: djb@silverton.berkeley.edu (D. J. Bernstein)" <0a> + <0a> + "This is a test." <0a> "," + "24:" "God-DSN-37@heaven.af.mil" "," + "30:" "26:djb@silverton.berkeley.edu," "," + + "356:" <0d> + "From: MAILER-DAEMON@heaven.af.mil" <0d 0a> + "To:" <0d 0a> + " Hate." <22> "The Quoting" <22> + "@SILVERTON.berkeley.edu," <0d 0a> + " " <22> "\\Backslashes!" <22> + "@silverton.BERKELEY.edu" <0d 0a> + <0d 0a> + "The recipient addresses here could" + " have been encoded in SMTP as" <0d 0a> + "" <0d 0a> + " RCPT TO:<Hate.The\ Quoting@silverton.berkeley.EDU>" <0d 0a> + " RCPT TO:<\\Backslashes!@silverton.berkeley.edu>" <0d 0a> + <0d 0a> + "This ends with a partial last line, right here" "," + "0:" "," + "83:" "39:Hate.The Quoting@silverton.berkeley.edu," + "36:\Backslashes!@silverton.berkeley.EDU," "," + + The server sends the following response, indicating acceptance: + + "21:Kok 838640135 qp 1390," + "21:Kok 838640135 qp 1391," + "21:Kok 838640135 qp 1391," + + The client closes the connection. diff --git a/RFCQSBMF b/RFCQSBMF @@ -0,0 +1,155 @@ +The qmail-send Bounce Message Format (QSBMF) +D. J. Bernstein, djb@pobox.com +19970201 + + +1. Introduction + + When a message transport agent (MTA) finds itself permanently unable + to deliver a mail message, it generates a new message, generally + known as a bounce message, back to the envelope sender. + + Bounce messages produced by the qmail-send program display the list + of failed recipient addresses, an explanation for each address, and a + copy of the original message, in a format that is easy for both + humans and programs to read. For example: + + Date: 17 Mar 1996 03:54:40 -0000 + From: MAILER-DAEMON@silverton.berkeley.edu + To: djb@silverton.berkeley.edu + Subject: failure notice + + Hi. This is the qmail-send program at silverton.berkeley.edu. + I'm afraid I wasn't able to deliver your message to the + following addresses. This is a permanent error; I've given up. + Sorry it didn't work out. + + <god@heaven.af.mil>: + Sorry, I couldn't find any host by that name. + + --- Below this line is a copy of the message. + + Return-Path: <djb@silverton.berkeley.edu> + Received: (qmail 317 invoked by uid 7); 17 Mar 1996 03:54:38 -0000 + Date: 17 Mar 1996 03:54:38 -0000 + Message-ID: <19960317035438.316.qmail@silverton.berkeley.edu> + From: djb@silverton.berkeley.edu (D. J. Bernstein) + To: god@heaven.af.mil + Subject: are you there? + + Just checking. + + This document defines qmail-send's format for bounce messages. + + In this document, a string of 8-bit bytes may be written in two + different forms: as a series of hexadecimal numbers between angle + brackets, or as a sequence of ASCII characters between double quotes. + For example, <68 65 6c 6c 6f 20 77 6f 72 6c 64 21> is a string of + length 12; it is the same as the string "hello world!". + + +2. Format + + A bounce message may be recognized as QSBMF as follows: its body + begins with the characters "Hi. This is the" exactly as shown. + + The body of the message has four pieces: an introductory paragraph, + zero or more recipient paragraphs, a break paragraph, and the + original message. + + Each paragraph is a series of non-blank lines followed by a single + blank line. The break paragraph begins with the character "-". All + other paragraphs begin with characters other than "-". The break + paragraph is human-readable but provides no interesting information. + + The introductory paragraph is human-readable. It gives the name and + human-comprehensible location of the MTA, but parsers should not + attempt to use this information. + + The only type of recipient paragraph described here is a failure + paragraph, which begins with the character "<". Paragraphs beginning + with other characters are reserved for future extensions. + + The first line of a failure paragraph ends with the characters ">:". + Everything between the leading "<" and the trailing ">:" is an + (unquoted) Internet mail address. + + A failure paragraph asserts that the MTA was permanently unable to + deliver the message to the mail address shown on the first line; the + MTA will not attempt further deliveries to that address. The + remaining lines of the paragraph give a human-readable description of + the reason for failure. Descriptions beginning with <20>, and + descriptions containing "#", are reserved for future extensions. + + The envelope sender might not have sent his message to the address + shown. There are two reasons for this. First, the MTA may freely + replace unprintable characters with "_". Second, the original + recipient address may have been an alias for the address shown. + + The original message is an exact copy of the message received by the + MTA, including both header and body, preceded by a Return-Path field + showing the envelope sender. + + +3. Comparison with 1892/1894 + + RFC 1892 and RFC 1894 together describe a format for delivery status + notifications. I have decided not to use that format, because I + believe that its complexity will prevent wide implementation and + increase the burden on people who manage mailing lists. + + QSBMF is dedicated to failure reports, whereas RFC 1894 allows + success reports and deferral reports. Although it would be possible + to add deferral paragraphs and success paragraphs to QSBMF, it would + be even easier to design separate formats for such notices. I have + trouble reading mixed failure/deferral reports. + + QSBMF always returns the entire original message. RFC 1892 allows + the MTA to return nothing or to return just the headers; it states + ``Return of content may be wasteful of network bandwidth.'' However, + failure notices are very rare, so the overall loss of bandwidth in + this case is insignificant. A much more important issue is storage + space: someone who manages a big mailing list does not want to have + to store several copies of each message in the form of bounces. The + best solution is to have each bounce automatically fed through a + program that stores only the critical information. I expect such + programs to spring up quickly for QSBMF. + + RFC 1894 provides language-independent error messages, as described + by RFC 1893. One can achieve the same results more easily by adding + structure to the human-readable failure descriptions, for example + with HCMSSC. + + RFC 1894 is able to communicate an ``envelope ID'' and the original + envelope recipient address specified by the sender. Unfortunately, + this information will almost never be available, since it requires + support by every intermediate MTA. All of the applications of this + information can be handled reliably, right now, with VERPs; this + requires support from the sender's MTA but not from other hosts. + + RFC 1894 includes several pieces of information that might be of + human interest but can be seen just as easily from Received lines: + the name of the MTA where delivery failed, the name of the previous + MTA, timestamps, etc. + + All of these RFC 1894 features have a cost: complexity. A program + cannot parse an 1894 report without parsing RFC 822 header fields + and understanding quite a bit of MIME. This will limit the + availability of parsing software. In the meantime, such reports are + annoying to mailing list maintainers, since they are full of + uninteresting information and are difficult to parse visually. + + +4. Security considerations + + Bounce messages may be forged. Never remove someone from a mailing + list without sending him a message stating that you are doing so, + even if the reason for removal is a series of apparent bounce + messages from his address. + + If you send a message along a secret path, you should change the + envelope sender address of the message to yourself, so that a bounce + will not reveal anything to the original sender. In other words: for + secret forwarding, use a mailing list, not a forwarder. + + See RFC 1894 for further discussion of these points. diff --git a/RFCVERP b/RFCVERP @@ -0,0 +1,88 @@ +Variable Envelope Return Paths +D. J. Bernstein, djb@pobox.com +19970201 + + +1. Introduction + + The fundamental problem in managing a large mailing list is matching + bounce messages to subscription addresses. + + Often a bounce message refers to a failing address that does not + appear on the mailing list. One of the mailing list subscribers is + forwarding messages to that address. Which subscriber? As the list + grows, this question becomes more and more difficult to answer. + + Sometimes a bounce message doesn't identify the address that failed. + On occasion it doesn't even include a copy of the original message. + See RFC 1211 for an extensive collection of horror stories. + + In theory, one could solve this problem with the DSN option and DSN + format described in RFC 1891, RFC 1892, and RFC 1894. Unfortunately, + the DSN option is useless unless it is supported by every + intermediate MTA. The complexity of RFC 1891 means that it will be + many years, perhaps infinitely many, before DSNs are universally + supported. Furthermore, the complexity of RFC 1894 means that parsing + the subscriber address is difficult even on the occasions that the + address is available. + + Variable envelope return paths (VERPs) completely eliminate this + problem _right now_. They automatically and reliably identify the + subscription address relevant to each bounce message. They provide + the address in a form that is trivial for automated bounce handlers + to parse. They require support from the local mailer, but they do not + require support from any other hosts. + + +2. Variable envelope return paths + + Here is how VERPs work: each recipient of the message sees a + different envelope sender address. When a message to the + djb-sos@silverton.berkeley.edu mailing list is sent to + God@heaven.af.mil, for example, it has the following envelope sender: + + djb-sos-owner-God=heaven.af.mil@silverton.berkeley.edu + + If the message bounces, the bounce message will be sent back to + djb-sos-owner-God=heaven.af.mil@silverton.berkeley.edu. + + If God is forwarding His mail, the bounce message will still go to + djb-sos-owner-God=heaven.af.mil@silverton.berkeley.edu. No matter how + uninformative the bounce message is, it will display God's + subscription address in its envelope. + + Another benefit of VERPs is that God Himself can see what address He + used to subscribe. + + Making VERPs work requires two pieces of local software support. + First: it must be easy to modify the outgoing sender address + separately for each envelope recipient. For example, with one mailer, + qmail, a user can simply touch ~/.qmail-list-owner and + ~/.qmail-list-owner-default to apply VERPs to user-list. + + Second, and more important: it must be easy to identify a collection + of addresses, such as djb-sos-owner-*, and send all mail for those + addresses to one place, while preserving the * information. Under + qmail, all user-list-owner-* mail will be sent to the user once he + touches ~/.qmail-list-owner-default. Sending the mail through an + automated bounce-handling program is just as easy. + + With older mailers, applying VERPs would require setting up a new + user-list-owner-recipient alias for each new recipient. This + inconvenience has prevented VERPs from being widely exploited, even + though the idea is not new. + + +3. Per-message VERPs + + VERPs are not restricted to distinguishing mailing list subscribers; + they can also be used to distinguish messages. + + For example, a user can send one message with an envelope sender + address of user-dsn-1, the next message with user-dsn-2, and so on. + As long as the local mailer gives all user-dsn-* back to that user, + he can reliably match up incoming bounces with outgoing messages. + + Per-message VERPs can be combined with per-recipient VERPs. Every + application of RFC 1891's ORCPT and ENVID can be handled with + VERPs---easily, reliably, and right now. diff --git a/SECURITY b/SECURITY @@ -0,0 +1,131 @@ +Background: Every few months CERT announces Yet Another Security Hole In +Sendmail---something that lets local or even remote users take complete +control of the machine. I'm sure there are many more holes waiting to be +discovered; sendmail's design means that any minor bug in 46000 lines of +code is a major security risk. Other popular mailers, such as Smail, and +even mailing-list managers, such as Majordomo, seem nearly as bad. + +I started working on qmail because I was sick of this cycle of doom. +Here are some of the things I did to make sure that qmail will never let +an intruder into your machine. + + +1. Programs and files are not addresses. Don't treat them as addresses. + +sendmail treats programs and files as addresses. Obviously random people +can't be allowed to execute arbitrary programs or write to arbitrary +files, so sendmail goes through horrendous contortions trying to keep +track of whether a local user was ``responsible'' for an address. This +has proven to be an unmitigated disaster. + +In qmail, programs and files are not addresses. The local delivery +agent, qmail-local, can run programs or write to files as directed by +~user/.qmail, but it's always running as that user. (The notion of +``user'' is configurable, but root is never a user. To prevent silly +mistakes, qmail-local makes sure that neither ~user nor ~user/.qmail is +group-writable or world-writable.) + +Security impact: .qmail, like .cshrc and .exrc and various other files, +means that anyone who can write arbitrary files as a user can execute +arbitrary programs as that user. That's it. + + +2. Do as little as possible in setuid programs. + +A setuid program must operate in a very dangerous environment: a user is +under complete control of its fds, args, environ, cwd, tty, rlimits, +timers, signals, and more. Even worse, the list of controlled items +varies from one vendor's UNIX to the next, so it is very difficult to +write portable code that cleans up everything. + +Of the twelve most recent sendmail security holes, six worked only +because the entire sendmail system is setuid. + +Only one qmail program is setuid: qmail-queue. Its only purpose is to +add a new mail message to the outgoing queue. + + +3. Do as little as possible as root. + +The entire sendmail system runs as root, so there's no way that its +mistakes can be caught by the operating system's built-in protections. +In contrast, only two qmail programs, qmail-start and qmail-lspawn, +run as root. + + +4. Move separate functions into mutually untrusting programs. + +Five of the qmail programs---qmail-smtpd, qmail-send, qmail-rspawn, +qmail-remote, and tcp-env---are not security-critical. Even if all of +these programs are completely compromised, so that an intruder has +control over the qmaild, qmails, and qmailr accounts and the mail queue, +he still can't take over your system. None of the other programs trust +the results from these five. + +In fact, these programs don't even trust each other. They are in three +groups: tcp-env and qmail-smtpd, which run as qmaild; qmail-rspawn and +qmail-remote, which run as qmailr; and qmail-send, the queue manager, +which runs as qmails. Each group is immune from attacks by the others. + +(From root's point of view, as long as root doesn't send any mail, only +qmail-start and qmail-lspawn are security-critical. They don't write any +files or start any other programs as root.) + + +5. Don't parse. + +I have discovered that there are two types of command interfaces in the +world of computing: good interfaces and user interfaces. + +The essence of user interfaces is _parsing_---converting an unstructured +sequence of commands, in a format usually determined more by psychology +than by solid engineering, into structured data. + +When another programmer wants to talk to a user interface, he has to +_quote_: convert his structured data into an unstructured sequence of +commands that the parser will, he hopes, convert back into the original +structured data. + +This situation is a recipe for disaster. The parser often has bugs: it +fails to handle some inputs according to the documented interface. The +quoter often has bugs: it produces outputs that do not have the right +meaning. Only on rare joyous occasions does it happen that the parser +and the quoter both misinterpret the interface in the same way. + +When the original data is controlled by a malicious user, many of these +bugs translate into security holes. Some examples: the Linux login +-froot security hole; the classic find | xargs rm security hole; the +recent Majordomo security hole. Even a simple parser like getopt is +complicated enough for people to screw up the quoting. + +In qmail, all the internal file structures are incredibly simple: text0 +lines beginning with single-character commands. (text0 format means that +lines are separated by a 0 byte instead of line feed.) The program-level +interfaces don't take options. + +All the complexity of parsing RFC 822 address lists and rewriting +headers is in the qmail-inject program, which runs without privileges +and is essentially part of the UA. + +The only nasty case is .qmail, qmail's answer to .forward. I tried to +make this as simple as possible, but unfortunately it still has to be +edited by users. As a result, the qlist mailing-list-management program +has to be careful to exclude subscriber addresses that contain newlines. + + +6. Keep it simple, stupid. + +See BLURB for some of the reasons that qmail is so much smaller than +sendmail. There's nothing inherently complicated about writing a mailer. +(Except RFC 822 support; but that's only in qmail-inject.) Security +holes can't show up in features that don't exist. + + +7. Write bug-free code. + +I've mostly given up on the standard C library. Many of its facilities, +particularly stdio, seem designed to encourage bugs. A big chunk of +qmail is stolen from a basic C library that I've been developing for +several years for a variety of applications. The stralloc concept and +getline2() make it very easy to avoid buffer overruns, memory leaks, +and artificial line length limits. diff --git a/SYSDEPS b/SYSDEPS @@ -0,0 +1,17 @@ +VERSION +systype +hasshsgr.h +hasnpbg1.h +select.h +hasflock.h +hassalen.h +fork.h +hassgact.h +direntry.h +hassgprm.h +haswaitp.h +hasmkffo.h +uint32.h +dns.lib +socket.lib +syslog.lib diff --git a/TARGETS b/TARGETS @@ -0,0 +1,353 @@ +auto-ccld.sh +make-load +find-systype +systype +load +make-compile +compile +fork.h +qmail-local.o +qmail.o +auto-str.o +make-makelib +makelib +substdio.o +substdi.o +substdo.o +subfderr.o +subfdout.o +subfdouts.o +subfdin.o +subfdins.o +substdio_copy.o +substdio.a +error.o +error_str.o +error_temp.o +error.a +str_len.o +str_diff.o +str_diffn.o +str_cpy.o +str_chr.o +str_rchr.o +str_start.o +byte_chr.o +byte_rchr.o +byte_diff.o +byte_copy.o +byte_cr.o +byte_zero.o +str.a +auto-str +auto_qmail.c +auto_qmail.o +auto-int8.o +fmt_str.o +fmt_strn.o +fmt_uint.o +fmt_uint0.o +fmt_ulong.o +scan_ulong.o +scan_8long.o +scan_nbblong.o +fs.a +auto-int8 +auto_patrn.c +auto_patrn.o +quote.o +now.o +gfrom.o +myctime.o +slurpclose.o +case_diffb.o +case_diffs.o +case_lowerb.o +case_lowers.o +case_starts.o +case.a +getln.o +getln2.o +getln.a +subgetopt.o +sgetopt.o +getopt.a +sig_alarm.o +hassgprm.h +sig_block.o +hassgact.h +sig_catch.o +sig_pause.o +sig_pipe.o +sig_child.o +sig_hup.o +sig_term.o +sig_bug.o +sig_misc.o +sig.a +open_append.o +open_excl.o +open_read.o +open_trunc.o +open_write.o +open.a +seek_cur.o +seek_end.o +seek_set.o +seek_trunc.o +seek.a +hasflock.h +lock_ex.o +lock_exnb.o +lock_un.o +lock.a +fd_copy.o +fd_move.o +fd.a +wait_pid.o +haswaitp.h +wait_nohang.o +wait.a +env.o +envread.o +env.a +stralloc_eady.o +stralloc_pend.o +stralloc_copy.o +stralloc_opys.o +stralloc_opyb.o +stralloc_cat.o +stralloc_cats.o +stralloc_catb.o +stralloc_arts.o +stralloc.a +alloc.o +alloc_re.o +alloc.a +datetime.o +datetime_un.o +datetime.a +qmail-local +uint32.h +qmail-lspawn.o +auto-uid.o +auto-uid +auto-gid.o +auto-gid +auto_uids.c +auto_uids.o +auto-int.o +auto-int +auto_spawn.c +auto_spawn.o +select.h +chkspawn.o +chkspawn +spawn.o +chkshsgr.o +chkshsgr +hasshsgr.h +prot.o +coe.o +cdb_hash.o +cdb_unpack.o +cdb_seek.o +cdb.a +qmail-lspawn +qmail-getpw.o +auto_break.c +auto_break.o +auto_usera.c +auto_usera.o +qmail-getpw +qmail-remote.o +control.o +constmap.o +timeoutread.o +timeoutwrite.o +timeoutconn.o +tcpto.o +dns.o +ip.o +ipalloc.o +hassalen.h +ipme.o +ndelay.o +ndelay_off.o +ndelay.a +socket.lib +dns.lib +qmail-remote +qmail-rspawn.o +tcpto_clean.o +qmail-rspawn +direntry.h +qmail-clean.o +auto_split.c +auto_split.o +fmtqfn.o +qmail-clean +qmail-send.o +qsutil.o +newfield.o +prioq.o +hasmkffo.h +fifo.o +hasnpbg1.h +trigger.o +readsubdir.o +date822fmt.o +qmail-send +qmail-start.o +qmail-start +splogger.o +syslog.lib +splogger +qmail-queue.o +triggerpull.o +qmail-queue +qmail-inject.o +headerbody.o +hfield.o +token822.o +qmail-inject +predate.o +predate +datemail +mailsubj +qmail-upq +qmail-config +qmail-showctl.o +qmail-showctl +qmail-newu.o +cdbmss.o +cdbmake_pack.o +cdbmake_hash.o +cdbmake_add.o +cdbmake.a +qmail-newu +qmail-pw2u.o +qmail-pw2u +qmail-qread.o +qmail-qread +qmail-qstat +qmail-tcpto.o +qmail-tcpto +qmail-pop3d.o +qmail-pop3d +qmail-popup.o +qmail-popup +qmail-qmtpd.o +received.o +qmail-qmtpd +qmail-smtpd.o +qmail-smtpd +sendmail.o +sendmail +tcp-env.o +remoteinfo.o +tcp-env +dnscname.o +dnsdoe.o +dnscname +dnsptr.o +dnsptr +dnsip.o +dnsip +dnsmxip.o +dnsmxip +dnsfq.o +dnsfq +hostname.o +hostname +ipmeprint.o +ipmeprint +qlist.o +qlist +qlist2 +qreceipt.o +qreceipt +qsmhook.o +qsmhook +qbiff.o +qbiff +forward.o +forward +preline.o +preline +condredirect.o +condredirect +maildirmake.o +maildirmake +maildir2mbox.o +maildir.o +strerr_sys.o +strerr_die.o +strerr.a +maildir2mbox +maildirwatch.o +maildirwatch +qail +elq +pinq +qmail-hier.o +qmail-hier +install.o +install +instcheck.o +instcheck +it +qmail-local.0 +qmail-lspawn.0 +qmail-getpw.8 +qmail-getpw.0 +qmail-remote.0 +qmail-rspawn.0 +qmail-clean.0 +qmail-send.8 +qmail-send.0 +qmail-start.0 +splogger.0 +qmail-queue.0 +qmail-inject.0 +mailsubj.0 +qmail-showctl.0 +qmail-newu.0 +qmail-pw2u.8 +qmail-pw2u.0 +qmail-qread.0 +qmail-qstat.0 +qmail-tcpto.0 +qmail-pop3d.0 +qmail-popup.0 +qmail-qmtpd.0 +qmail-smtpd.0 +tcp-env.0 +qlist.0 +qreceipt.0 +qbiff.0 +forward.0 +preline.0 +condredirect.0 +maildirmake.0 +maildir2mbox.0 +maildirwatch.0 +qmail.0 +qmail-upgrade.7 +qmail-upgrade.0 +qmail-limits.7 +qmail-limits.0 +qmail-log.0 +qmail-control.0 +qmail-header.0 +qmail-users.0 +dot-qmail.5 +dot-qmail.0 +qmail-command.0 +tcp-environ.0 +maildir.0 +mbox.0 +addresses.0 +envelopes.0 +forgeries.0 +man diff --git a/THANKS b/THANKS @@ -0,0 +1,217 @@ +Thanks to lots of people for success and failure reports, code, ideas, +and documentation. See CHANGES for details of specific contributions. +Sorry if I left anyone out. + +SA = Satoshi Adachi +G1A = Graham Adams +NA = Norm Aleks +NAA = Nicholas A. Amato +G2A = Greg Andrews +TA = Tetsuo Aoki +DA = Dave Arcuri +JJB = J. J. Bailey +J2B = Jos Backus +SSB = Stik Bakken +J1B = John Banghart +GB = Glenn Barry +JLB = Julie L. Baumler +SLB = Steven L. Baur +ALB = Allan L. Bazinet +JDHB = Johannes D. H. Beekhuizen +BDB = Boris D. Beletsky +HJB = Herbert J. Bernstein +REB = Ronald E. Bickers +JPB = Joe Block +BB = Bruce Bodger +SB = Stephane Bortzmeyer +PB = Peter Bowyer +ACB = Andy C. Brandt +AB = Alan Briggs +AKB = Allen K. Briggs +JBB = Jason B. Brown +JAB = Jeremy A. Bussard +RJC = Robert J. Carter +EC = Evan Champion +JC = Jim Clausing +HCJ = Helio Coelho Jr. +BC = Bob Collie +SGC = Stephen G. Comings +MC = Michael Cooley +DCC = Daniel C. Cotey +AC = Arne Coucheron +M2C = Mark Crimmins +DC = Dan Cross +MD = Mark Delany +SVD = Stef Van Dessel +RD = Rahul Dhesi +MJD = Mark-Jason Dominus +JD = Joe Doupnik +FE = Frank Ederveen +DE = Daniel Egnor +MWE = Mark W. Eichin +MEE = Mads E. Eilertsen +KE = Kenny Elliott +TEE = Thomas E. Erskine +ME = Marc Ewing +JF = Janos Farkas +DF = Dale Farnsworth +YF = Yaroslav Faybishenko +CF = C. Ferree +JBF = John B. Fleming +C2F = Chuck Foster +RF = Rainer Fraedrich +MF = Massimo Fusaro +CG = Chris Garrigues +BG = Bert Gijsbers +M2G = Michael R. Gile +EG = Eivind Gjelseth +HG = Howard Goldstein +TG = Tim Goodwin +HDG = Hans de Graaff +MG = Michael Graff +PJG = Paul Graham +MRG = Matthew R. Green +SG = Steven Grimm +AG = Armin Gruner +CSH = Clayton S. Haapala +JLH = Jason L. Haar +MLH = May Liss Haarstad +CH = Chael Hall +JPH = Justin P. Hannah +RJH = Randy Harmon +PH = Paul Harrington +DEH = Daniel E. Harris +RFH = Robert F. Harrison +D1H = Dieter Heidner +GH = Gene Hightower +MH = Markus Hofmann +D2H = Dan Hollis +NH = Nick Holloway +TH = Ton Hospel +BH = Brad Howes +TJH = Timothy J. Hunt +B2H = Buck Huppmann +MDI = Miguel de Icaza +BJ = Brian Jackson +AJ = Alan Jaffray +CEJ = Colin Eric Johnson +K2J = Kevin Johnson +K1J = Kyle Jones +SJ = Sudish Joseph +W2K = Wolfram Kahl +JJMK = Jonathan J. M. Katz +CK = Christoph Kaesling +PK = Petri Kaukasoina +D2K = Dax Kelson +TK = Terry Kennedy +DBK = Douglas B. Kerry +WK = Werner Koch +DK = Dave Kopper +J1K = Jost Krieger +J2K = Johannes Kroeger +EK = Eric Krohn +B2L = Brent Laminack +AL = Andreas Lamprecht +L2L = Louis Larry +M3L = Michael Lazarou +CL = Carsten Leonhardt +JRL = John R. Levine +DML = David M. Lew +LL = lilo +FPL = Frederik P. Lindberg +JL = Jim Littlefield +BL = Brian Litzinger +RL = Robert Luce +ML = Martin Lucina +M2L = M. Lyons +TM = Toshinori Maeno +ESM = Edward S. Marshall +J2M = Joel Maslak +TLM = Timothy L. Mayo +DM = David Mazieres +RM = Rich McClellan +SM = Shawn McHorse +JM = Jim Meehan +HWM = Henry W. Miller +RDM = Raul Miller +MMM = Momchil M. Momchev +JGM = John G. Myers +FN = Faried Nawaz +RN = Russell Nelson +TN = Thomas Neumann +UO = Uwe Ohse +PCO = Peter C. Olsen +HHO = Harald Hanche-Olsen +BEO = Bruce E. O'Neel +JP = John Palkovic +AP = Andrew Pam +SP = Stephen Parker +TVP = Tom van Peer +BP = Bruce Perens +CMP = Chase M. Phillips +DP = Dave Platt +EP = Emanuele Pucciarelli +JPR = Jean-Pierre Radley +S1R = Satish Ramachandran +MR = Mosfeq Rashid +TRR = Tracy R. Reed +BR = Brian Reichert +S2R = Sean Reifschneider +DAR = Daniel A. Reish +CR = Christian Riede +KR = Kenji Rikitake +OR = Ollivier Robert +ANR = Adriano Nagelschmidt Rodrigues +JJR = Jaron J. Rubenstein +RS = Robert Sanders +KJS = Kevin J. Sawyer +MBS = Mike Scher +SAS = Steven A. Schrader +CLS = Christopher L. Seawood +OS = Oliver Seiler +D2S = Dan Senie +SS = Simon Shapiro +RGS = Richard G. Sharman +DS = Dave Sill +H2S = Harley Silver +M3S = Morten Skjelland +JS = Jesper Skriver +ES = Eric Smith +IS = Icarus Sparry +CS = Cloyce Spradling +BS = Bjoern Stabell +HS = Harlan Stenn +JMS = Jason M. Stokes +DWS = David Wayne Summers +M2S = Mikael Suokas +PS = Paul Svensson +ET = Eivind Tagseth +PT = Paul Taylor +S2T = Steve Taylor +BT = Brad Templeton +DST = Daniel S. Thibadeau +MT = Mark Thompson +S3T = Steffen Thorsen +KT = Karsten Thygesen +BET = Bennett E. Todd +JMT = John M. Twilley +ST = Steve Tylock +TU = Tetsu Ushijima +VV = Vince Vielhaber +TV = Tommi Virtanen +FW = Frank Wagner +BW = Bill Weinman +IW = Ian Westcott +SJW = Stephen J. White +JW = John Whittaker +LW = Lionel Widdifield +MW = Mate Wierdl +BTW = Brian T. Wightman +PW = Peter Wilkinson +HW = Hal Wine +GAW = Greg A. Woods +SCW = Steven C. Work +JLW = Jason L. Wright +WW = Wei Wu +IKW = Ian Keith Wynne +BZ = Blaz Zupan diff --git a/THOUGHTS b/THOUGHTS @@ -0,0 +1,437 @@ +Please note that this file is not called ``Internet Mail For Dummies.'' +It _records_ my thoughts on various issues. It does not _explain_ them. +Paragraphs are not organized except by section. The required background +varies wildly from one paragraph to the next. + +In this file, ``sendmail'' means Allman's creation; ``sendmail-clone'' +means the program in this package. + + +1. Security + +There are lots of interesting remote denial-of-service attacks on any +mail system. A long-term solution is to insist on prepayment for +unauthorized resource use. The tricky technical problem is to make the +prepayment enforcement mechanism cheaper than the expected cost of the +attacks. (For local denial-of-service attacks it's enough to be able to +figure out which user is responsible.) + +qmail-send's log was originally designed for profiling. It subsequently +sprouted some tracing features. However, there's no way to verify +securely that a particular message came from a particular local user; +how do you know the recipient is telling you the truth about the +contents of the message? With QUEUE_EXTRA it'd be possible to record a +one-way hash of each outgoing message, but a user who wants to send +``bad'' mail can avoid qmail entirely. + +I originally decided on security grounds not to put qmail advertisements +into SMTP responses: advertisements often act as version identifiers. +But this problem went away when I found a stable qmail URL. + +As qmail grows in popularity, the mere knowledge that rcpthosts is so +easily available will deter people from setting up unauthorized MXs. +(I've never seen an unauthorized MX, but I can imagine that it would be +rather annoying.) Note that, unlike the bat book checkcompat() kludge, +rcpthosts doesn't interfere with mailing lists. + +qmail-start doesn't bother with tty dissociation. On some old machines +this means that random people can send tty signals to the qmail daemons. +That's a security flaw in the job control subsystem, not in qmail. + +The resolver library isn't too bloated (before 4.9.4, at least), but it +uses stdio, which _is_ bloated. Reading /etc/resolv.conf costs lots of +memory in each qmail-remote process. So it's tempting to incorporate a +smaller resolver library into qmail. (Bonus: I'd avoid system-specific +problems with old resolvers.) The problem is that I'd then be writing a +fundamentally insecure library. I'd no longer be able to blame the BIND +authors and vendors for the fact that attackers can easily use DNS to +steal mail. Possible solution: replace dns.c with something that passes +requests (reliably!) to a local daemon; call the original resolver +library from that daemon. + +NFS is the primary enemy of security partitioning under UNIX. Here's the +story. Sun knew from the start that NFS was completely insecure. It +tried to hide that fact by disallowing root access over NFS. Intruders +nevertheless broke into system after system, first obtaining bin access +and then obtaining root access. Various people thus decided to compound +Sun's error and build a wall between root and all other users: if all +system files are owned by root, and if there are no security holes other +than NFS, someone who breaks in via NFS won't be able to wipe out the +operating system---he'll merely be able to wipe out all user files. This +clueless policy means that, for example, all the qmail users have to be +replaced by root. See what I mean by ``enemy''? ... Basic NFS comments: +Aside from the cryptographic problem of having hosts communicate +securely, it's obvious that there's an administrative problem of mapping +client uids to server uids. If a host is secure and under your control, +you shouldn't have to map anything. If a host is under someone else's +control, you'll want to map his uids to one local account; it's his +client's job to decide which of his users get to talk NFS in the first +place. Sun's original map---root to nobody, everyone else left alone--- +is, as far as I can tell, always wrong. + + +2. Injecting mail locally (qmail-inject, sendmail-clone) + +RFC 822 section 3.4.9 prohibits certain visual effects in headers. +qmail-inject doesn't waste the time to enforce this absurd restriction. +If you will suffer from someone sending you ``flash mail,'' go find a +better mail reader. + +qmail-inject's ``Cc: recipient list not shown: ;'' successfully stops +sendmail from adding Apparently-To. Unfortunately, old versions of +sendmail will append a host name. This wasn't fixed until sendmail 8.7. +How many years has it been since RFC 822 came out? + +sendmail discards duplicate addresses. This has probably resulted in +more lost and stolen mail over the years than the entire Chicago branch +of the United States Postal Service. The qmail system delivers messages +exactly as it's told to do. Along the same lines: qmail-inject is both +unable and unwilling to support anything like sendmail's (default) +nometoo option. Of course, a list manager could support nometoo. + +There should be a mechanism in qmail-inject that does for envelope +recipients what Return-Path does for the envelope sender. Then +qmail-inject -n could print the recipients. + +Should qmail-inject bounce messages with no recipients? Should there be +an option for this? If it stays as is (accept the message), qmail-inject +could at least avoid invoking qmail-queue. + +It is possible to extract non-unique Message-IDs out of qmail-inject. +Here's how: stop qmail-inject before it gets to the third line of +main(), then wait until the pids wrap around, then restart qmail-inject +and blast the message through, then start another qmail-inject with the +same pid in the same second. I'm not sure how to fix this. (Of course, +the user could just type in his own non-unique Message-IDs.) + +The bat book says: ``Rules that hide hosts in a domain should be applied +only to sender addresses.'' Recipient masquerading works fine with +qmail. None of sendmail's pitfalls apply, basically because qmail has a +straight paper path. + +I expect to receive some pressure to make up for the failings of MUA +writers who don't understand the concept of reliability. (``Like, duh, +you mean I was supposed to check the sendmail exit code?'') + + +3. Receiving mail from the network (tcp-env, qmail-smtpd) + +RFC 1123 requires VRFY support, but says that it's okay if an +implementation can be configured to not allow VRFY. qmail-smtpd doesn't +allow VRFY. If you desperately want your SMTP server (i.e., inetd) to +provide useful information for VRFY, just compile and install sendmail. +Were the RFC 1123 writers aware of the as-if principle of interface +specification? ... They say that VRFY and EXPN are important for +tracking down cross-host mailing list loops. Catch up to the 1990s, +guys: with Delivered-To, mailing list loops do absolutely no damage, +_and_ one of the list administrators gets a bounce that shows exactly +how the loop occurred. Solve the problem, not the symptom. ... There's a +vastly superior alternative to EXPN. Hint: finger postmaster@ai.mit.edu. + +Should dns.c make special allowances for 127.0.0.1/localhost? + +badmailfrom (like 8BITMIME) is a waste of code space. + + +4. Adding messages to the queue (qmail-queue) + +Should qmail-queue try to make sure enough disk space is free in +advance? When qmail-queue is invoked by qmail-local or (with ESMTP) +qmail-smtpd or qmail-qmtpd, it could be told a size in advance. I wish +UNIX had an atomic allocate-disk-space routine... + +The qmail.h interface (reflecting the qmail-queue interface, which in +turn reflects the current queue file structure) is constitutionally +incapable of handling an address that contains a 0 byte. I can't imagine +that this will be a problem. + +Should qmail-queue not bother queueing a message with no recipients? + + +5. Handling queued mail (qmail-send, qmail-clean) + +The queue directory must be local. Mounting it over NFS is extremely +dangerous---not that this stops people from running sendmail that way! +Perhaps it is worth putting together a diskless-host qmail package with +just qmail-inject and an SMTP client in place of qmail-queue. Sending +mail to the server via SMTP is of course vastly better than trying to do +anything over NFS. If the NFS server is up but the mail server is down, +users will just have to wait. + +Queue reliability demands that single-byte writes be atomic. This is +true for a fixed-block filesystem such as UFS, and for a logging +filesystem such as LFS. + +qmail-send uses 8 bytes of memory per queued message. Double that for +reallocation. (Fix: use a small forest of heaps; i.e., keep several +prioqs.) Double again for buddy malloc()s. (Fix: be clever about the +heap sizes.) 32 bytes is worrisome, but not devastating. Even on my +disk-heavy memory-light machine, I'd run out of inodes long before +running out of memory. + +Some mail systems organize the queue by host. This is pointless as a +means of splitting up the queue directory. The real issue is what to do +when you suddenly find out that a host is up. For local SLIP/PPP links +you know in advance which hosts need this treatment, so you can handle +them with virtualdomains and serialmail. + +For the old queue structure I implemented recipient list compression: +if mail goes out to a giant mailing list, and most of the recipients are +delivered, make a new, compressed, todo list. But this really isn't +worth the effort: it saves only a tiny bit of CPU time. + +qmail-send doesn't have any notions of precedence, priority, fairness, +importance, etc. It handles the queue in first-seen-first-served order. +One could put a lot of work into doing something different, but that +work would be a waste: given the triggering mechanism and qmail's +deferral strategy, it is exceedingly rare for the queue to contain more +than one deliverable message at any given moment. + +Exception: Even with all the concurrency tricks, qmail-send can end up +spending a few minutes on a mailing list with thousands of remote +entries. A user might send a new message to a remote address in the +meantime. Perhaps qmail-send should limit its time per message to, +say, thirty recipients. This will require some way to mark recipients +who were already done on this pass. Possible approach: Maintain two todo +lists (for both L and R). Always work on the earlier todo list. Move +deferrals to the other todo list. + +qmail-send will never start a pass for a job that it already has. This +means that, if one delivery takes longer than the retry interval, the +next pass will be delayed. I implemented the opposite strategy for the +old queue structure. Some hassles: mark() had to understand how job +input was buffered; every new delivery had to check whether the same +mpos in the same message was already being done. + +Some things that qmail-send does synchronously: queueing a bounce +message; doing a cleanup via qmail-clean; classifying and rewriting all +the addresses in a new message. As usual, making these asynchronous +would require some housekeeping, but could speed things up a bit. +(Making bounces asynchronous, without POSIX waitpid(), means that +wait_pid() has to keep a buffer of previous wait()s. Ugh.) + +fsync() is a bottleneck. To make this asynchronous would require gobs of +dedicated output processes whose only purpose in life is to watch data +get written to the disk. Inconceivable! (``You keep using that word. I +do not think that word means what you think it means.'') + +On the other hand, I could survive without fsync()ing the local and +remote and info files as long as I don't unlink todo. This would require +redefining the queue states. I need to see how much speed can be gained. + +Currently qmail-send sends at most one bounce message for each incoming +message. This means that the sender doesn't get flooded with copies of +his own message. On the other hand, a single slow address can hold up +bounces for a bunch of fast addresses. It would be easy to call +injectbounce() more often. What is the best strategy? This feels like +the TCP-buffering issue... don't want to pepper the other guy with +little packets, but do want to get the data across. + +qmail-stop implementation: setuid to UID_SEND; kill -TERM -1. Given how +simple this is, I'm not inclined to set up some tricky locking solution +where qmail-send records its pid etc. But I just know that, if I provide +this qmail-stop program, someone will screw himself by making another +uid the same as UID_SEND, or making UID_SEND be root, or whatever. +Aargh. Maybe use another named pipe... New solution: Run qmail-start +under an external service controller---it runs in the foreground now. + +Bounce messages could include more statistical information in the first +paragraph: when I received the message, how many recipients I was +supposed to handle, how many I successfully dealt with, how many I +already told you about, how many are still in the queue. Have to +emphasize that the number of recipients _here_ is perhaps less than the +number of recipients on the original message. + +The readdir() interface hides I/O errors. Lower-level interfaces would +lead me into a thicket of portability problems. I'm really not sure what +to do about this. Of course, a hard I/O error means that mail is toast, +but a soft I/O error shouldn't cause any trouble. + +job_open() or pass_dochan() could be paranoid about the same id,channel +already being open; but, since messdone() is so paranoid, the worst +possible effect of a bug along these lines would be double delivery. + +Mathematical amusement: The optimal retry schedule is essentially, +though not exactly, independent of the actual distribution of message +delay times. What really matters is how much cost you assign to retries +and to particular increases in latency. qmail's current quadratic retry +schedule says that an hour-long delay in a day-old message is worth the +same as a ten-minute delay in an hour-old message; this doesn't seem so +unreasonable. + +Insider information: AOL retries their messages every five minutes for +three days straight. Hmmm. + + +6. Sending mail through the network (qmail-rspawn, qmail-remote) + +Are there any hosts, anywhere, whose mailers are bogged down by huge +messages to multiple recipients at a single host? For typical hosts, +multiple RCPTs per SMTP aren't an ``efficiency feature''; they're a +_slowness_ feature. Separate SMTP transactions have much lower latency. + +The multiple-RCPT bandwidth gain _might_ be noticeable for a machine +that sends most messages to a smarthost. It would be easy to have +qmail-rspawn supply qmail-remote with all the addresses at once, as long +as qmail-send says when it's about to block... Putting recipients into +the right order is clearly the UA's job. One multiple-RCPT pitfall is +that a remote host might not be able to deal with (say) 10 recipients, +even though RFC 821 says everyone has to be able to handle 100; +qmail-rspawn would have to notice this and back off. (Not that other +mailers do. Sometimes I'm amazed Internet mail works at all.) + +In the opposite direction: It's tempting to remove the @host part of the +qmail-remote recip argument. Or at least avoid double-dns_cname. + +There are lots of reasons that qmail-rspawn should take a more active +role in qmail-remote's activities. It should call separate programs to +do (1) MX lookups, (2) SMTP connections, (3) QMTP connections. + +I bounce ambiguous MXs. (An ``ambiguous MX'' is a best-preference MX +record sending me mail for a host that I don't recognize as local.) +Automatically treating ambiguous MXs as local is incompatible with my +design decision to keep local delivery working when the network goes +down. It puts more faith in DNS than DNS deserves. Much better: Have +your MX records generated automatically from control/locals. + +If I successfully connect to an MX host but it temporarily refuses to +accept the message, I give up and put the message back into the queue. +But several documents seem to suggest that I should try further MX +records. What are they thinking? My approach deals properly with downed +hosts, hosts that are unreachable through a firewall, and load +balancing; what else do people use multiple MX records for? + +Currently qmail-remote sends data in 1024-byte buffers. Perhaps it +should try to take account of the MTU. + +Perhaps qmail-remote should allocate a fixed amount of DNS/connect() +time across any number of MXs; this idea is due to Mark Delany. + +RFC 821 doesn't say what it means by ``text.'' qmail-remote assumes that +the server's reply text doesn't contain bare LFs. + + +7. Delivering mail locally (qmail-lspawn, qmail-local) + +qmail-local doesn't support comsat. comsat is a pointless abomination. +Use qbiff if you want that kind of notification. + +The getpwnam() interface hides I/O errors. Solution: qmail-pw2u. + + +8. sendmail V8's new features + +sendmail-8.8.0/doc/op/op.me includes a list of big improvements of +sendmail 8.8.0 over sendmail 5.67. Here's how qmail stacks up against +each of those improvements. (Of course, qmail has its own improvements, +but that's not the point of this list.) + +Connection caching, MX piggybacking: Nope. (Profile. Don't speculate.) + +Response to RCPT command is fast: Yup. + +IP addresses show up in Received lines: Yup. + +Self domain literal is properly handled: Yup. + +Different timeouts for QUIT, RCPT, etc.: No, just a single timeout. + +Proper <> handling, route-address pruning: Yes, but not configurable. + +ESMTP support: Yup. (Server-side, including PIPELINING.) + +8-bit clean: Yup. (Including server-side 8BITMIME support; same as +sendmail with the 8 option.) + +Configurable user database: Yup. + +BIND support: Yup. + +Keyed files: Yes, in qmsmac. + +931/1413/Ident/TAP: Yup. + +Correct 822 address list parsing: Yup. (Note that sendmail still has +some major problems with quoting.) + +List-owner handling: Yup. + +Dynamic header allocation: Yup. + +Minimum number of disk blocks: Yes, via tunefs -m. + +Checkpointing: Yes, but not configurable---qmail always checkpoints. + +Error message configuration: Nope. + +GECOS matching: Not directly, but easy to hook in. + +Hop limit configuration: No. (qmail's limit is 100 hops. qmail offers +automatic loop protection much more advanced than hop counting.) + +MIME error messages: No. (qmail uses QSBMF error messages, which are +much easier to parse.) + +Forward file path: Yes, via /etc/passwd. + +Incoming SMTP configuration: Yes, via inetd or tcpserver. + +Privacy options: Yes, but they're not options. + +Best-MX mangling: Nope. See section 6 for further discussion. + +7-bit mangling: Nope. qmail always uses 8 bits. + +Support for up to 20 MX records: Yes, and more. qmail has no limits +other than memory. + +Correct quoting of name-and-address headers: Yup. + +VRFY and EXPN now different: Nope. qmail always hides this information. + +Multi-word classes, deferred macro expansion, separate envelope/header +$g processing, separate per-mailer envelope and header processing, new +command line flags, new configuration lines, new mailer flags, new +macros: These are sendmail-specific; they wouldn't even make sense for +qmail. For example, _of course_ qmail handles envelopes and headers +separately; they're almost entirely different objects! + + +9. Miscellany + +sendmail-clone and qsmhook are too bletcherous to be documented. (The +official replacement for qsmhook is preline, together with the +qmail-command environment variables.) + +I've considered making install atomic, but this is very difficult to do +right, and pointless if it isn't done right. + +RN suggests automatically putting together a reasonable set of lines for +/etc/passwd. I perceive this as getting into the adduser business, which +is worrisome: I'll be lynched the first time I screw up somebody's +passwd file. This should be left to OS-specific installation scripts. + +The BSD 4.2 inetd didn't allow a username. I think I can safely forget +about this. (DS notes that the username works under Ultrix even though +it's undocumented.) + +I should clean up the bput/put choices. + +Some of the stralloc_0()s indicate that certain lower-level routines +should grok stralloc. + +RN suggests having qlist smash the case of the incoming host name. + +K1J suggests that mailing list subscription managers should have +a three-way handshake, to prevent person A from subscribing person B to +a mailing list. qlist doesn't do this, but ezmlm does. + +qmail assumes that all times are positive; that pid_t, time_t and ino_t +fit into unsigned long; that gid_t fits into int; that the character set +is ASCII; and that all pointers are interchangeable. Do I care? + +The bat book justifies sendmail's insane line-splitting mechanism by +pointing out that it might be useful for ``a 40-character braille +print-driving program.'' C'mon, guys, is that your best excuse? + +qmail's mascot is a dolphin. diff --git a/TODO b/TODO @@ -0,0 +1,9 @@ +do some serious coverage testing, preferably under Purify, without alloc slop +replace INTERNALS and THOUGHTS with a real paper describing qmail +reorganize qmail-inject to do most rw on characters, not tokens +allow concurrency over 255 +handle IPv6 +turn qmail-upq into a more serious queue-moving utility +expand strerr coverage +rewrite everything from scratch +maybe allow root to clear tcpto on the fly diff --git a/UPGRADE b/UPGRADE @@ -0,0 +1,145 @@ +SAVE COPIES OF YOUR OUTGOING MAIL! Like any other piece of software (and +information generally), the qmail system comes with NO WARRANTY. It's +much more secure and reliable than sendmail, but that's not saying much. + + +Here's how to upgrade from qmail 1.00 to qmail 1.01. This procedure will +overwrite the old qmail binaries. Furthermore, it may begin delivering +messages from the queue before you have had a chance to test it. + + +WARNING: The qmail-start command line has changed. + + +Before starting, compare conf* to your old conf*, and make any necessary +changes. Do not copy your old conf*; the baseline has changed. + + +How to install: + + 1. Compile the programs: + # make + 2. Create the formatted man pages, *.0: + # make man + 3. Inform your users that mail will not be accepted for a few minutes. + 4. Disable deliveries by killing your old qmail-send. Wait for it to + print ``exiting'' in the log. + 5. Disable SMTP service by commenting out the smtp line in inetd.conf; + kill -HUP your inetd. (If you are using tcpserver, simply kill -STOP + your tcpserver.) Wait for current qmail-smtpd processes to die. (If + you are running a QMTP server, disable that too.) + 6. Install the new binaries and man pages: + # rm /var/qmail/bin/* /var/qmail/man/*/* + # make setup + 7. Run instcheck to make sure it doesn't print any warnings: + # make check + 8. Reenable deliveries: + # env - PATH="/var/qmail/bin:$PATH" \ + qmail-start ./Mailbox splogger qmail & + Make sure to include the ./ in ./Mailbox. + 9. Insert ./Mailbox into the qmail-start line in your boot scripts. +10. Reenable SMTP service by restoring the smtp line in inetd.conf; kill + -HUP your inetd. (If you are using tcpserver, simply kill -CONT your + tcpserver. If you are running a QMTP server, reenable that too.) + + +How to test (steps 11-17 can be done before step 10): + +11. Look for a + qmail: running + line in syslog. (The big number is a splogger timestamp.) +12. Local-local test: Send yourself an empty message. (Replace ``me'' + with your username. Make sure to include the ``to:'' colon.) + % echo to: me | /var/qmail/bin/qmail-inject + The message will show up immediately in ~/Mailbox, and syslog will + show something like this: + qmail: new msg 53 + qmail: info msg 53: bytes 246 from <me@domain> qp 20345 uid 666 + qmail: starting delivery 1: msg 53 to local me@domain + qmail: delivery 1: success: did_1+0+0/ + qmail: end msg 53 + (53 is an inode number; 20345 is a process ID; your numbers will + probably be different.) +13. Local-error test: Send a message to a nonexistent local address. + % echo to: nonexistent | /var/qmail/bin/qmail-inject + qmail: new msg 53 + qmail: info msg 53: bytes 246 from <me@domain> qp 20351 uid 666 + qmail: starting delivery 2: msg 53 to local nonexistent@domain + qmail: delivery 2: failure: No_such_address.__#5.1.1_/ + qmail: bounce msg 53 qp 20357 + qmail: end msg 53 + qmail: new msg 54 + qmail: info msg 54: bytes 743 from <> qp 20357 uid 666 + qmail: starting delivery 3: msg 54 to local me@domain + qmail: delivery 3: success: did_1+0+0/ + qmail: end msg 54 + You will now have a bounce message in ~/Mailbox. +14. Local-remote test: Send an empty message to your account on another + machine. + % echo to: me@wherever | /var/qmail/bin/qmail-inject + qmail: new msg 53 + qmail: info msg 53: bytes 246 from <me@domain> qp 20372 uid 666 + qmail: starting delivery 4: msg 53 to remote me@wherever + qmail: delivery 4: success: 1.2.3.4_accepted_message./... + qmail: end msg 53 + There will be a pause between ``starting delivery'' and ``success''; + SMTP is slow. Check that the message is in your mailbox on the other + machine. +15. Local-postmaster test: Send mail to postmaster, any capitalization. + % echo to: POSTmaster | /var/qmail/bin/qmail-inject + Look for the message in ~alias/Mailbox. +16. Double-bounce test: Send a message with a completely bad envelope. + % /var/qmail/bin/qmail-inject -f nonexistent + To: unknownuser + Subject: testing + + This is a test. This is only a test. + % + (Use end-of-file, not dot, to end the message.) Look for the double + bounce in ~alias/Mailbox. +17. Group membership test: + % cat > ~me/.qmail-groups + |groups >> MYGROUPS; exit 0 + % /var/qmail/bin/qmail-inject me-groups < /dev/null + % cat ~me/MYGROUPS + MYGROUPS will show your normal gid and nothing else. (Under Solaris, + make sure to use /usr/ucb/groups; /usr/bin/groups is broken.) +18. SMTP server test: Forge some mail locally via SMTP. + % telnet 127.0.0.1 25 + Trying 127.0.0.1... + Connected to 127.0.0.1. + Escape character is '^]'. + 220 domain ESMTP + helo dude + 250-domain + 250-PIPELINING + 250 8BITMIME + mail <me@domain> + 250 ok + rcpt <me@domain> + 250 ok + data + 354 go ahead + Subject: testing + + This is a test. + . + 250 ok 812345679 qp 12345 + quit + 221 domain + Connection closed by foreign host. + % + Look for the message in your mailbox. +19. Remote-local test: Send yourself some mail from another machine. +20. Remote-error test: I think you can figure this one out. +21. UA test: Try sending mail, first to a local account, then to a + remote account, with your normal user agent. +22. Remote-postmaster test: Send mail from another machine to + PoStMaStEr@domain. Look for the message in ~alias/Mailbox. + + +That's it! To report success: + % ( echo 'First M. Last'; cat `cat SYSDEPS` ) \ + | mail djb-qst@koobera.math.uic.edu +Replace First M. Last with your name. If you have questions about qmail, +contact qmail@pobox.com. diff --git a/VERSION b/VERSION @@ -0,0 +1 @@ +qmail 1.01 diff --git a/addresses.5 b/addresses.5 @@ -0,0 +1,260 @@ +.TH addresses 5 +.SH "NAME" +addresses \- formats for Internet mail addresses +.SH "INTRODUCTION" +A +.B mail address +is a string of characters containing @. + +Every mail address has a +.B local part +and a +.B domain part\fR. +The domain part is everything after the final @. +The local part is everything before. + +For example, the mail addresses + +.EX + God@heaven.af.mil + @heaven.af.mil + @at@@heaven.af.mil +.EE + +all have domain part +.BR heaven.af.mil . +The local parts are +.BR God , +empty, +and +.BR @at@ . + +Some domains have owners. +It is up to the owner of +.B heaven.af.mil +to say how mail messages will be delivered to addresses with domain part +.BR heaven.af.mil . + +The domain part of an address is interpreted without regard to case, so + +.EX + God@heaven.af.mil +.br + God@HEAVEN.AF.MIL +.br + God@Heaven.AF.Mil +.EE + +all refer to the same domain. + +There is one exceptional address that does not contain an @: +namely, the empty string. +The empty string cannot be used as a recipient address. +It can be used as a sender address so that +the real sender doesn't receive bounces. +.SH "QMAIL EXTENSIONS" +The +.B qmail +system allows several further types of addresses in mail envelopes. + +First, an envelope recipient address without an @ is interpreted as being at +.IR envnoathost . +For example, if +.I envnoathost +is +.BR heaven.af.mil , +the address +.B God +will be rewritten as +.BR God@heaven.af.mil . + +Second, the address +.B #@[] +is used as an envelope sender address for double bounces. + +Third, envelope sender addresses of the form +.I pre\fB@\fIhost\fB-@[] +are used to support variable envelope return paths (VERPs). +.B qmail-send +will rewrite +.I pre\fB@\fIhost\fB-@[] +as +.I prerecip\fB=\fIdomain\fB@\fIhost +for deliveries to +.IR recip\fB@\fIdomain . +Bounces directly from +.B qmail-send +will come back to +.IR pre\fB@\fIhost . +.SH "CHOOSING MAIL ADDRESSES" +Here are some suggestions on choosing mail addresses for the Internet. + +Do not use non-ASCII characters. +Under RFC 822 and RFC 821, +these characters cannot be used in mail headers or in SMTP commands. +In practice, they are regularly corrupted. + +Do not use ASCII control characters. +NUL is regularly corrupted. +CR and LF cannot be used in some combinations +and are corrupted in all. +None of these characters are usable on business cards. + +Avoid spaces and the characters + +.EX + \\"<>()[],;: +.EE + +These all require quoting in mail headers and in SMTP. +Many existing mail programs do not handle quoting properly. + +Do not use @ in a local part. +@ requires quoting in mail headers and in SMTP. +Many programs incorrectly look for the first @, +rather than the last @, +to find the domain part of an address. + +In a local part, +do not use two consecutive dots, a dot at the beginning, or a dot at the end. +Any of these would require quoting in mail headers. + +Do not use an empty local part; it cannot appear in SMTP commands. + +Avoid local parts longer than 64 characters. + +Be wary of uppercase letters in local parts. +Some mail programs (and users!) will incorrectly convert +.B God@heaven.af.mil +to +.BR god@heaven.af.mil . + +Be wary of the following characters: + +.EX + $&!#~`'^*|{} +.EE + +Some users will not know +how to feed these characters safely to their mail programs. + +In domain names, stick to letters, digits, dash, and dot. +One popular DNS resolver has, +under the banner of security, +recently begun destroying domain names +that contain certain other characters, +including underscore. +Exception: A dotted-decimal IP address in brackets, +such as +.BR [127.0.0.1] , +identifies a domain owned by whoever owns the host at that IP address, +and can be used safely. + +In a domain name, +do not use two consecutive dots, +a dot at the beginning, +or a dot at the end. +This means that, +when a domain name is broken down into components separated by dots, +there are no empty components. + +Always use at least one dot in a domain name. +If you own the +.B mil +domain, +don't bother using the address +.BR root@mil ; +most users will be unable to send messages to that address. +Same for the root domain. + +Avoid domain names longer than 64 characters. +.SH "ENCODED ADDRESSES IN SMTP COMMANDS" +RFC 821 defines an encoding of mail addresses in SMTP. +For example, the addresses + +.EX + God@heaven.af.mil +.br + a"quote@heaven.af.mil +.br + The Almighty.One@heaven.af.mil +.EE + +could be encoded in RCPT commands as + +.EX + RCPT TO:<God@heaven.af.mil> +.br + RCPT TO:<a\\"quote@heaven.af.mil> +.br + RCPT TO:<The\\ Almighty.One@heaven.af.mil> +.EE + +There are several restrictions in RFC 821 +on the mail addresses that can be used over SMTP. +Non-ASCII characters are prohibited. +The local part must not be empty. +The domain part must be a sequence of elements separated by dots, +where each element is either a component, +a sequence of digits preceded by #, +or a dotted-decimal IP address surrounded by brackets. +The only allowable characters in components are +letters, digits, and dashes. +Every component must (believe it or not) +have at least three characters; +the first character must be a letter; +the last character must not be a hyphen. +.SH "ENCODED ADDRESSES IN MAIL HEADERS" +RFC 822 defines an encoding of mail addresses +in certain header fields in a mail message. +For example, the addresses + +.EX + God@heaven.af.mil +.br + a"quote@heaven.af.mil +.br + The Almighty.One@heaven.af.mil +.EE + +could be encoded in a +.B To +field as + +.EX + To: God@heaven.af.mil, +.br + <@brl.mil:"a\\"quote"@heaven.af.mil>, +.br + "The Almighty".One@heaven.af.mil +.EE + +or perhaps + +.EX + To: < "God"@heaven .af.mil>, +.br + "a\\"quote" (Who?) @ heaven . af. mil +.br + , God<"The Almighty.One"@heaven.af.mil> +.EE + +There are several restrictions on the mail addresses that can +be used in these header fields. +Non-ASCII characters are prohibited. +The domain part must be a sequence of elements separated by dots, +where each element either (1) begins with [ and ends with ] +or (2) is a nonempty string of printable ASCII characters +not including any of + +.EX + \\".<>()[],;: +.EE + +and not including space. +.SH "SEE ALSO" +envelopes(5), +qmail-header(5), +qmail-inject(8), +qmail-remote(8), +qmail-smtpd(8) diff --git a/alloc.3 b/alloc.3 @@ -0,0 +1,62 @@ +.TH alloc 3 +.SH NAME +alloc \- allocate memory +.SH SYNTAX +.B #include <alloc.h> + +char *\fBalloc\fP(\fInew\fR); + +void \fBalloc_free\fP(\fIx\fR); + +void \fBalloc_re\fP(&\fIx\fR,\fIold\fR,\fInew\fR); + +char *\fIx\fR; +.br +unsigned int \fIold\fR; +.br +unsigned int \fInew\fR; +.SH DESCRIPTION +.B alloc +allocates enough space from the heap for +.I new +bytes of data, +adequately aligned for any data type. +.I new +may be 0. +.B alloc +returns a pointer to the space. +If space is not available, +.B alloc +returns 0, +setting +.B errno +appropriately. + +.B alloc_free +returns space to the heap. + +.B alloc_re +expands the space allocated to +.I x +from +.I old +bytes to +.I new +bytes. +It allocates new space, +copies +.I old +bytes from the old space to the new space, +returns the old space to the heap, +and changes +.I x +to point to the new space. +It then returns 1. +If space is not available, +.B alloc_re +returns 0, +leaving the old space alone. +.SH "SEE ALSO" +sbrk(2), +malloc(3), +error(3) diff --git a/alloc.c b/alloc.c @@ -0,0 +1,32 @@ +#include "alloc.h" +#include "error.h" +extern char *malloc(); +extern void free(); + +#define ALIGNMENT 16 /* XXX: assuming that this alignment is enough */ +#define SPACE 4096 /* must be multiple of ALIGNMENT */ + +typedef union { char irrelevant[ALIGNMENT]; double d; } aligned; +static aligned realspace[SPACE / ALIGNMENT]; +#define space ((char *) realspace) +static unsigned int avail = SPACE; /* multiple of ALIGNMENT; 0<=avail<=SPACE */ + +/*@null@*//*@out@*/char *alloc(n) +unsigned int n; +{ + char *x; + n = ALIGNMENT + n - (n & (ALIGNMENT - 1)); /* XXX: could overflow */ + if (n <= avail) { avail -= n; return space + avail; } + x = malloc(n); + if (!x) errno = error_nomem; + return x; +} + +void alloc_free(x) +char *x; +{ + if (x >= space) + if (x < space + SPACE) + return; /* XXX: assuming that pointers are flat */ + free(x); +} diff --git a/alloc.h b/alloc.h @@ -0,0 +1,8 @@ +#ifndef ALLOC_H +#define ALLOC_H + +extern /*@null@*//*@out@*/char *alloc(); +extern void alloc_free(); +extern int alloc_re(); + +#endif diff --git a/alloc_re.c b/alloc_re.c @@ -0,0 +1,17 @@ +#include "alloc.h" +#include "byte.h" + +int alloc_re(x,m,n) +char **x; +unsigned int m; +unsigned int n; +{ + char *y; + + y = alloc(n); + if (!y) return 0; + byte_copy(y,m,*x); + alloc_free(*x); + *x = y; + return 1; +} diff --git a/auto-gid.c b/auto-gid.c @@ -0,0 +1,51 @@ +#include <sys/types.h> +#include <grp.h> +#include "subfd.h" +#include "substdio.h" +#include "readwrite.h" +#include "exit.h" +#include "scan.h" +#include "fmt.h" + +char buf1[256]; +substdio ss1 = SUBSTDIO_FDBUF(write,1,buf1,sizeof(buf1)); + +void outs(s) +char *s; +{ + if (substdio_puts(&ss1,s) == -1) _exit(111); +} + +void main(argc,argv) +int argc; +char **argv; +{ + char *name; + char *value; + struct group *gr; + char strnum[FMT_ULONG]; + + name = argv[1]; + if (!name) _exit(100); + value = argv[2]; + if (!value) _exit(100); + + gr = getgrnam(value); + if (!gr) { + substdio_puts(subfderr,"fatal: unable to find group "); + substdio_puts(subfderr,value); + substdio_puts(subfderr,"\n"); + substdio_flush(subfderr); + _exit(111); + } + + strnum[fmt_ulong(strnum,(unsigned long) gr->gr_gid)] = 0; + + outs("int "); + outs(name); + outs(" = "); + outs(strnum); + outs(";\n"); + if (substdio_flush(&ss1) == -1) _exit(111); + _exit(0); +} diff --git a/auto-int.c b/auto-int.c @@ -0,0 +1,40 @@ +#include "substdio.h" +#include "readwrite.h" +#include "exit.h" +#include "scan.h" +#include "fmt.h" + +char buf1[256]; +substdio ss1 = SUBSTDIO_FDBUF(write,1,buf1,sizeof(buf1)); + +void puts(s) +char *s; +{ + if (substdio_puts(&ss1,s) == -1) _exit(111); +} + +void main(argc,argv) +int argc; +char **argv; +{ + char *name; + char *value; + unsigned long num; + char strnum[FMT_ULONG]; + + name = argv[1]; + if (!name) _exit(100); + value = argv[2]; + if (!value) _exit(100); + + scan_ulong(value,&num); + strnum[fmt_ulong(strnum,num)] = 0; + + puts("int "); + puts(name); + puts(" = "); + puts(strnum); + puts(";\n"); + if (substdio_flush(&ss1) == -1) _exit(111); + _exit(0); +} diff --git a/auto-int8.c b/auto-int8.c @@ -0,0 +1,40 @@ +#include "substdio.h" +#include "readwrite.h" +#include "exit.h" +#include "scan.h" +#include "fmt.h" + +char buf1[256]; +substdio ss1 = SUBSTDIO_FDBUF(write,1,buf1,sizeof(buf1)); + +void puts(s) +char *s; +{ + if (substdio_puts(&ss1,s) == -1) _exit(111); +} + +void main(argc,argv) +int argc; +char **argv; +{ + char *name; + char *value; + unsigned long num; + char strnum[FMT_ULONG]; + + name = argv[1]; + if (!name) _exit(100); + value = argv[2]; + if (!value) _exit(100); + + scan_8long(value,&num); + strnum[fmt_ulong(strnum,num)] = 0; + + puts("int "); + puts(name); + puts(" = "); + puts(strnum); + puts(";\n"); + if (substdio_flush(&ss1) == -1) _exit(111); + _exit(0); +} diff --git a/auto-str.c b/auto-str.c @@ -0,0 +1,44 @@ +#include "substdio.h" +#include "readwrite.h" +#include "exit.h" + +char buf1[256]; +substdio ss1 = SUBSTDIO_FDBUF(write,1,buf1,sizeof(buf1)); + +void puts(s) +char *s; +{ + if (substdio_puts(&ss1,s) == -1) _exit(111); +} + +void main(argc,argv) +int argc; +char **argv; +{ + char *name; + char *value; + unsigned char ch; + char octal[4]; + + name = argv[1]; + if (!name) _exit(100); + value = argv[2]; + if (!value) _exit(100); + + puts("char "); + puts(name); + puts("[] = \"\\\n"); + + while (ch = *value++) { + puts("\\"); + octal[3] = 0; + octal[2] = '0' + (ch & 7); ch >>= 3; + octal[1] = '0' + (ch & 7); ch >>= 3; + octal[0] = '0' + (ch & 7); + puts(octal); + } + + puts("\\\n\";\n"); + if (substdio_flush(&ss1) == -1) _exit(111); + _exit(0); +} diff --git a/auto-uid.c b/auto-uid.c @@ -0,0 +1,51 @@ +#include <sys/types.h> +#include <pwd.h> +#include "subfd.h" +#include "substdio.h" +#include "readwrite.h" +#include "exit.h" +#include "scan.h" +#include "fmt.h" + +char buf1[256]; +substdio ss1 = SUBSTDIO_FDBUF(write,1,buf1,sizeof(buf1)); + +void outs(s) /* was named puts, but Solaris pwd.h includes stdio.h. dorks. */ +char *s; +{ + if (substdio_puts(&ss1,s) == -1) _exit(111); +} + +void main(argc,argv) +int argc; +char **argv; +{ + char *name; + char *value; + struct passwd *pw; + char strnum[FMT_ULONG]; + + name = argv[1]; + if (!name) _exit(100); + value = argv[2]; + if (!value) _exit(100); + + pw = getpwnam(value); + if (!pw) { + substdio_puts(subfderr,"fatal: unable to find user "); + substdio_puts(subfderr,value); + substdio_puts(subfderr,"\n"); + substdio_flush(subfderr); + _exit(111); + } + + strnum[fmt_ulong(strnum,(unsigned long) pw->pw_uid)] = 0; + + outs("int "); + outs(name); + outs(" = "); + outs(strnum); + outs(";\n"); + if (substdio_flush(&ss1) == -1) _exit(111); + _exit(0); +} diff --git a/auto_break.h b/auto_break.h @@ -0,0 +1,6 @@ +#ifndef AUTO_BREAK_H +#define AUTO_BREAK_H + +extern char auto_break[]; + +#endif diff --git a/auto_patrn.h b/auto_patrn.h @@ -0,0 +1,6 @@ +#ifndef AUTO_PATRN_H +#define AUTO_PATRN_H + +extern int auto_patrn; + +#endif diff --git a/auto_qmail.h b/auto_qmail.h @@ -0,0 +1,6 @@ +#ifndef AUTO_QMAIL_H +#define AUTO_QMAIL_H + +extern char auto_qmail[]; + +#endif diff --git a/auto_spawn.h b/auto_spawn.h @@ -0,0 +1,6 @@ +#ifndef AUTO_SPAWN_H +#define AUTO_SPAWN_H + +extern int auto_spawn; + +#endif diff --git a/auto_split.h b/auto_split.h @@ -0,0 +1,6 @@ +#ifndef AUTO_SPLIT_H +#define AUTO_SPLIT_H + +extern int auto_split; + +#endif diff --git a/auto_uids.h b/auto_uids.h @@ -0,0 +1,16 @@ +#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; + +extern int auto_gidn; +extern int auto_gidq; + +#endif diff --git a/auto_usera.h b/auto_usera.h @@ -0,0 +1,6 @@ +#ifndef AUTO_USERA_H +#define AUTO_USERA_H + +extern char auto_usera[]; + +#endif diff --git a/byte.h b/byte.h @@ -0,0 +1,13 @@ +#ifndef BYTE_H +#define BYTE_H + +extern unsigned int byte_chr(); +extern unsigned int byte_rchr(); +extern void byte_copy(); +extern void byte_copyr(); +extern int byte_diff(); +extern void byte_zero(); + +#define byte_equal(s,n,t) (!byte_diff((s),(n),(t))) + +#endif diff --git a/byte_chr.c b/byte_chr.c @@ -0,0 +1,20 @@ +#include "byte.h" + +unsigned int byte_chr(s,n,c) +char *s; +register unsigned int n; +int c; +{ + register char ch; + register char *t; + + ch = c; + t = s; + for (;;) { + if (!n) break; if (*t == ch) break; ++t; --n; + if (!n) break; if (*t == ch) break; ++t; --n; + if (!n) break; if (*t == ch) break; ++t; --n; + if (!n) break; if (*t == ch) break; ++t; --n; + } + return t - s; +} diff --git a/byte_copy.c b/byte_copy.c @@ -0,0 +1,14 @@ +#include "byte.h" + +void byte_copy(to,n,from) +register char *to; +register unsigned int n; +register char *from; +{ + for (;;) { + if (!n) return; *to++ = *from++; --n; + if (!n) return; *to++ = *from++; --n; + if (!n) return; *to++ = *from++; --n; + if (!n) return; *to++ = *from++; --n; + } +} diff --git a/byte_cr.c b/byte_cr.c @@ -0,0 +1,16 @@ +#include "byte.h" + +void byte_copyr(to,n,from) +register char *to; +register unsigned int n; +register char *from; +{ + to += n; + from += n; + for (;;) { + if (!n) return; *--to = *--from; --n; + if (!n) return; *--to = *--from; --n; + if (!n) return; *--to = *--from; --n; + if (!n) return; *--to = *--from; --n; + } +} diff --git a/byte_diff.c b/byte_diff.c @@ -0,0 +1,16 @@ +#include "byte.h" + +int byte_diff(s,n,t) +register char *s; +register unsigned int n; +register char *t; +{ + for (;;) { + if (!n) return 0; if (*s != *t) break; ++s; ++t; --n; + if (!n) return 0; if (*s != *t) break; ++s; ++t; --n; + if (!n) return 0; if (*s != *t) break; ++s; ++t; --n; + if (!n) return 0; if (*s != *t) break; ++s; ++t; --n; + } + return ((int)(unsigned int)(unsigned char) *s) + - ((int)(unsigned int)(unsigned char) *t); +} diff --git a/byte_rchr.c b/byte_rchr.c @@ -0,0 +1,23 @@ +#include "byte.h" + +unsigned int byte_rchr(s,n,c) +char *s; +register unsigned int n; +int c; +{ + register char ch; + register char *t; + register char *u; + + ch = c; + t = s; + u = 0; + for (;;) { + if (!n) break; if (*t == ch) u = t; ++t; --n; + if (!n) break; if (*t == ch) u = t; ++t; --n; + if (!n) break; if (*t == ch) u = t; ++t; --n; + if (!n) break; if (*t == ch) u = t; ++t; --n; + } + if (!u) u = t; + return u - s; +} diff --git a/byte_zero.c b/byte_zero.c @@ -0,0 +1,13 @@ +#include "byte.h" + +void byte_zero(s,n) +char *s; +register unsigned int n; +{ + for (;;) { + if (!n) break; *s++ = 0; --n; + if (!n) break; *s++ = 0; --n; + if (!n) break; *s++ = 0; --n; + if (!n) break; *s++ = 0; --n; + } +} diff --git a/case.3 b/case.3 @@ -0,0 +1,100 @@ +.TH case 3 +.SH NAME +case \- convert ASCII uppercase bytes to lowercase +.SH SYNTAX +.B #include <case.h> + +void \fBcase_lowers\fP(\fIs\fR); +.br +void \fBcase_lowerb\fP(\fIs\fR,\fIlen\fR); + +int \fBcase_diffs\fP(\fIs\fR,\fIt\fR); +.br +int \fBcase_equals\fP(\fIs\fR,\fIt\fR); +.br +int \fBcase_starts\fP(\fIs\fR,\fIt\fR); + +int \fBcase_diffb\fP(\fIs\fR,\fIlen\fR,\fIt\fR); +.br +int \fBcase_startb\fP(\fIs\fR,\fIlen\fR,\fIt\fR); + +char *\fIs\fR; +.br +char *\fIt\fR; +.br +unsigned int \fIlen\fR; +.SH DESCRIPTION +.B case_lowers +converts each uppercase byte in the string +.I s +to lowercase. +.I s +must be 0-terminated. + +.B case_lowerb +converts each uppercase byte in the buffer +.IR s , +of length +.IR len , +to lowercase. + +.B case_diffs +lexicographically compares lowercase versions of the strings +.I s +and +.IR t . +It returns something positive, negative, or zero +when the first is larger than, smaller than, or equal to the second. +.I s +and +.I t +must be 0-terminated. + +.B case_equals +means +.BR !case_diffs . + +.B case_starts +returns 1 if a lowercase version of +.I s +starts with a lowercase version of +.IR t . +.I s +and +.I t +must be 0-terminated. + +.B case_diffb +lexicographically compares lowercase versions of the buffers +.I s +and +.IR t , +each of length +.IR len . +It returns something positive, negative, or zero +when the first is larger than, smaller than, or equal to the second. + +.B case_startb +returns 1 if a lowercase version of the buffer +.IR s , +of length +.IR len , +starts with a lowercase version of the string +.IR t . +.I t +must be 0-terminated. + +The +.B case +routines +are ASCII-specific. +They are suitable for programs that handle +case-independent networking protocols. + +All comparisons are performed on unsigned bytes. +.SH "SEE ALSO" +byte_diff(3), +byte_equal(3), +str_diff(3), +str_equal(3), +str_start(3) diff --git a/case.h b/case.h @@ -0,0 +1,13 @@ +#ifndef CASE_H +#define CASE_H + +extern void case_lowers(); +extern void case_lowerb(); +extern int case_diffs(); +extern int case_diffb(); +extern int case_starts(); +extern int case_startb(); + +#define case_equals(s,t) (!case_diffs((s),(t))) + +#endif diff --git a/case_diffb.c b/case_diffb.c @@ -0,0 +1,21 @@ +#include "case.h" + +int case_diffb(s,len,t) +register char *s; +unsigned int len; +register char *t; +{ + register unsigned char x; + register unsigned char y; + + while (len > 0) { + --len; + x = *s++ - 'A'; + if (x <= 'Z' - 'A') x += 'a'; else x += 'A'; + y = *t++ - 'A'; + if (y <= 'Z' - 'A') y += 'a'; else y += 'A'; + if (x != y) + return ((int)(unsigned int) x) - ((int)(unsigned int) y); + } + return 0; +} diff --git a/case_diffs.c b/case_diffs.c @@ -0,0 +1,19 @@ +#include "case.h" + +int case_diffs(s,t) +register char *s; +register char *t; +{ + register unsigned char x; + register unsigned char y; + + for (;;) { + x = *s++ - 'A'; + if (x <= 'Z' - 'A') x += 'a'; else x += 'A'; + y = *t++ - 'A'; + if (y <= 'Z' - 'A') y += 'a'; else y += 'A'; + if (x != y) break; + if (!x) break; + } + return ((int)(unsigned int) x) - ((int)(unsigned int) y); +} diff --git a/case_lowerb.c b/case_lowerb.c @@ -0,0 +1,14 @@ +#include "case.h" + +void case_lowerb(s,len) +char *s; +unsigned int len; +{ + unsigned char x; + while (len > 0) { + --len; + x = *s - 'A'; + if (x <= 'Z' - 'A') *s = x + 'a'; + ++s; + } +} diff --git a/case_lowers.c b/case_lowers.c @@ -0,0 +1,12 @@ +#include "case.h" + +void case_lowers(s) +char *s; +{ + unsigned char x; + while (x = *s) { + x -= 'A'; + if (x <= 'Z' - 'A') *s = x + 'a'; + ++s; + } +} diff --git a/case_starts.c b/case_starts.c @@ -0,0 +1,18 @@ +#include "case.h" + +int case_starts(s,t) +register char *s; +register char *t; +{ + register unsigned char x; + register unsigned char y; + + for (;;) { + x = *s++ - 'A'; + if (x <= 'Z' - 'A') x += 'a'; else x += 'A'; + y = *t++ - 'A'; + if (y <= 'Z' - 'A') y += 'a'; else y += 'A'; + if (!y) return 1; + if (x != y) return 0; + } +} diff --git a/cdb.3 b/cdb.3 @@ -0,0 +1,62 @@ +.TH cdb 3 +.SH NAME +cdb \- read from a constant database +.SH SYNTAX +.B #include <cdb.h> + +int \fBcdb_seek(\fP\fIfd,key,len,dlen\fR\fB)\fP; + +int \fIfd\fR; +.br +char *\fIkey\fR; +.br +unsigned int \fIlen\fR; +.br +uint32 *\fIdlen\fR; +.SH DESCRIPTION +.B cdb_seek +looks up +.I key +in a constant database. +It returns 1 if +.I key +is present, +0 if +.I key +is not present, +or \-1 if there was a read error. +.I key +is an array of +.I len +characters. + +.B cdb_seek +needs an open file descriptor, +.IR fd , +pointing to the database. +If +.B cdb_seek +returns 1, +it points +.I fd +at the beginning of the data portion of the first record +indexed by +.IR key , +and it stores the data length in +.IR dlen. +.B cdb_seek +does not provide a way to read subsequent records with the same key. + +It's fine to do several +.B cdb_seek +lookups with the same open file descriptor. +Beware, however, that two simultaneous +.B cdb_seek +lookups can fail horribly; +separate processes should not share the same database descriptor. +Furthermore, any updates after the database was opened +will be invisible. +It's rarely a good idea for a long-running program +to hold a database open. +.SH "SEE ALSO" +cdbget(1) diff --git a/cdb.h b/cdb.h @@ -0,0 +1,12 @@ +#ifndef CDB_H +#define CDB_H + +#include "uint32.h" + +extern uint32 cdb_hash(); +extern uint32 cdb_unpack(); + +extern int cdb_bread(); +extern int cdb_seek(); + +#endif diff --git a/cdb_hash.c b/cdb_hash.c @@ -0,0 +1,16 @@ +#include "cdb.h" + +uint32 cdb_hash(buf,len) +unsigned char *buf; +unsigned int len; +{ + uint32 h; + + h = 5381; + while (len) { + --len; + h += (h << 5); + h ^= (uint32) *buf++; + } + return h; +} diff --git a/cdb_seek.c b/cdb_seek.c @@ -0,0 +1,95 @@ +#include <sys/types.h> +#include <errno.h> +extern int errno; +#include "cdb.h" + +#ifndef SEEK_SET +#define SEEK_SET 0 +#endif + +int cdb_bread(fd,buf,len) +int fd; +char *buf; +int len; +{ + int r; + while (len > 0) { + do + r = read(fd,buf,len); + while ((r == -1) && (errno == EINTR)); + if (r == -1) return -1; + if (r == 0) { errno = EIO; return -1; } + buf += r; + len -= r; + } + return 0; +} + +static int match(fd,key,len) +int fd; +char *key; +unsigned int len; +{ + char buf[32]; + int n; + int i; + + while (len > 0) { + n = sizeof(buf); + if (n > len) n = len; + if (cdb_bread(fd,buf,n) == -1) return -1; + for (i = 0;i < n;++i) if (buf[i] != key[i]) return 0; + key += n; + len -= n; + } + return 1; +} + +int cdb_seek(fd,key,len,dlen) +int fd; +char *key; +unsigned int len; +uint32 *dlen; +{ + char packbuf[8]; + uint32 pos; + uint32 h; + uint32 lenhash; + uint32 h2; + uint32 loop; + uint32 poskd; + + h = cdb_hash(key,len); + + pos = 8 * (h & 255); + if (lseek(fd,(off_t) pos,SEEK_SET) == -1) return -1; + + if (cdb_bread(fd,packbuf,8) == -1) return -1; + + pos = cdb_unpack(packbuf); + lenhash = cdb_unpack(packbuf + 4); + + if (!lenhash) return 0; + h2 = (h >> 8) % lenhash; + + for (loop = 0;loop < lenhash;++loop) { + if (lseek(fd,(off_t) (pos + 8 * h2),SEEK_SET) == -1) return -1; + if (cdb_bread(fd,packbuf,8) == -1) return -1; + poskd = cdb_unpack(packbuf + 4); + if (!poskd) return 0; + if (cdb_unpack(packbuf) == h) { + if (lseek(fd,(off_t) poskd,SEEK_SET) == -1) return -1; + if (cdb_bread(fd,packbuf,8) == -1) return -1; + if (cdb_unpack(packbuf) == len) + switch(match(fd,key,len)) { + case -1: + return -1; + case 1: + *dlen = cdb_unpack(packbuf + 4); + return 1; + } + } + if (++h2 == lenhash) h2 = 0; + } + return 0; +} diff --git a/cdb_unpack.c b/cdb_unpack.c @@ -0,0 +1,12 @@ +#include "cdb.h" + +uint32 cdb_unpack(buf) +unsigned char *buf; +{ + uint32 num; + num = buf[3]; num <<= 8; + num += buf[2]; num <<= 8; + num += buf[1]; num <<= 8; + num += buf[0]; + return num; +} diff --git a/cdbmake.h b/cdbmake.h @@ -0,0 +1,35 @@ +#ifndef CDBMAKE_H +#define CDBMAKE_H + +#include "uint32.h" + +#define CDBMAKE_HPLIST 1000 + +struct cdbmake_hp { uint32 h; uint32 p; } ; + +struct cdbmake_hplist { + struct cdbmake_hp hp[CDBMAKE_HPLIST]; + struct cdbmake_hplist *next; + int num; +} ; + +struct cdbmake { + char final[2048]; + uint32 count[256]; + uint32 start[256]; + struct cdbmake_hplist *head; + struct cdbmake_hp *split; /* includes space for hash */ + struct cdbmake_hp *hash; + uint32 numentries; +} ; + +extern void cdbmake_pack(); +#define CDBMAKE_HASHSTART ((uint32) 5381) +extern uint32 cdbmake_hashadd(); + +extern void cdbmake_init(); +extern int cdbmake_add(); +extern int cdbmake_split(); +extern uint32 cdbmake_throw(); + +#endif diff --git a/cdbmake_add.c b/cdbmake_add.c @@ -0,0 +1,117 @@ +#include "cdbmake.h" + +void cdbmake_init(cdbm) +struct cdbmake *cdbm; +{ + cdbm->head = 0; + cdbm->split = 0; + cdbm->hash = 0; + cdbm->numentries = 0; +} + +int cdbmake_add(cdbm,h,p,alloc) +struct cdbmake *cdbm; +uint32 h; +uint32 p; +char *(*alloc)(); +{ + struct cdbmake_hplist *head; + + head = cdbm->head; + if (!head || (head->num >= CDBMAKE_HPLIST)) { + head = (struct cdbmake_hplist *) alloc(sizeof(struct cdbmake_hplist)); + if (!head) return 0; + head->num = 0; + head->next = cdbm->head; + cdbm->head = head; + } + head->hp[head->num].h = h; + head->hp[head->num].p = p; + ++head->num; + ++cdbm->numentries; + return 1; +} + +int cdbmake_split(cdbm,alloc) +struct cdbmake *cdbm; +char *(*alloc)(); +{ + int i; + uint32 u; + uint32 memsize; + struct cdbmake_hplist *x; + + for (i = 0;i < 256;++i) + cdbm->count[i] = 0; + + for (x = cdbm->head;x;x = x->next) { + i = x->num; + while (i--) + ++cdbm->count[255 & x->hp[i].h]; + } + + memsize = 1; + for (i = 0;i < 256;++i) { + u = cdbm->count[i] * 2; + if (u > memsize) + memsize = u; + } + + memsize += cdbm->numentries; /* no overflow possible up to now */ + u = (uint32) 0 - (uint32) 1; + u /= sizeof(struct cdbmake_hp); + if (memsize > u) return 0; + + cdbm->split = (struct cdbmake_hp *) alloc(memsize * sizeof(struct cdbmake_hp)); + if (!cdbm->split) return 0; + + cdbm->hash = cdbm->split + cdbm->numentries; + + u = 0; + for (i = 0;i < 256;++i) { + u += cdbm->count[i]; /* bounded by numentries, so no overflow */ + cdbm->start[i] = u; + } + + for (x = cdbm->head;x;x = x->next) { + i = x->num; + while (i--) + cdbm->split[--cdbm->start[255 & x->hp[i].h]] = x->hp[i]; + } + + return 1; +} + +uint32 cdbmake_throw(cdbm,pos,b) +struct cdbmake *cdbm; +uint32 pos; +int b; +{ + uint32 len; + uint32 j; + uint32 count; + struct cdbmake_hp *hp; + uint32 where; + + count = cdbm->count[b]; + + len = count + count; /* no overflow possible */ + cdbmake_pack(cdbm->final + 8 * b,pos); + cdbmake_pack(cdbm->final + 8 * b + 4,len); + + if (len) { + for (j = 0;j < len;++j) + cdbm->hash[j].h = cdbm->hash[j].p = 0; + + hp = cdbm->split + cdbm->start[b]; + for (j = 0;j < count;++j) { + where = (hp->h >> 8) % len; + while (cdbm->hash[where].p) + if (++where == len) + where = 0; + cdbm->hash[where] = *hp++; + } + } + + return len; +} diff --git a/cdbmake_hash.c b/cdbmake_hash.c @@ -0,0 +1,10 @@ +#include "cdbmake.h" + +uint32 cdbmake_hashadd(h,c) +uint32 h; +unsigned int c; +{ + h += (h << 5); + h ^= (uint32) (unsigned char) c; + return h; +} diff --git a/cdbmake_pack.c b/cdbmake_pack.c @@ -0,0 +1,11 @@ +#include "cdbmake.h" + +void cdbmake_pack(buf,num) +unsigned char *buf; +uint32 num; +{ + *buf++ = num; num >>= 8; + *buf++ = num; num >>= 8; + *buf++ = num; num >>= 8; + *buf = num; +} diff --git a/cdbmss.c b/cdbmss.c @@ -0,0 +1,65 @@ +#include "readwrite.h" +#include "seek.h" +#include "alloc.h" +#include "cdbmss.h" + +int cdbmss_start(c,fd) +struct cdbmss *c; +int fd; +{ + cdbmake_init(&c->cdbm); + c->fd = fd; + c->pos = sizeof(c->cdbm.final); + substdio_fdbuf(&c->ss,write,fd,c->ssbuf,sizeof(c->ssbuf)); + return seek_set(fd,(seek_pos) c->pos); +} + +int cdbmss_add(c,key,keylen,data,datalen) +struct cdbmss *c; +unsigned char *key; +unsigned int keylen; +unsigned char *data; +unsigned int datalen; +{ + uint32 h; + int i; + + cdbmake_pack(c->packbuf,(uint32) keylen); + cdbmake_pack(c->packbuf + 4,(uint32) datalen); + if (substdio_put(&c->ss,c->packbuf,8) == -1) return -1; + if (substdio_put(&c->ss,key,keylen) == -1) return -1; + if (substdio_put(&c->ss,data,datalen) == -1) return -1; + + h = CDBMAKE_HASHSTART; + for (i = 0;i < keylen;++i) + h = cdbmake_hashadd(h,(unsigned int) key[i]); + + if (!cdbmake_add(&c->cdbm,h,c->pos,alloc)) return -1; + + c->pos += 8 + keylen + datalen; /* XXX: overflow? */ + return 0; +} + +int cdbmss_finish(c) +struct cdbmss *c; +{ + int i; + uint32 len; + uint32 u; + + if (!cdbmake_split(&c->cdbm,alloc)) return -1; + + for (i = 0;i < 256;++i) { + len = cdbmake_throw(&c->cdbm,c->pos,i); + for (u = 0;u < len;++u) { + cdbmake_pack(c->packbuf,c->cdbm.hash[u].h); + cdbmake_pack(c->packbuf + 4,c->cdbm.hash[u].p); + if (substdio_put(&c->ss,c->packbuf,8) == -1) return -1; + c->pos += 8; /* XXX: overflow? */ + } + } + + if (substdio_flush(&c->ss) == -1) return -1; + if (seek_begin(c->fd) == -1) return -1; + return substdio_putflush(&c->ss,c->cdbm.final,sizeof(c->cdbm.final)); +} diff --git a/cdbmss.h b/cdbmss.h @@ -0,0 +1,16 @@ +#ifndef CDBMSS_H +#define CDBMSS_H + +#include "cdbmake.h" +#include "substdio.h" + +struct cdbmss { + char ssbuf[1024]; + struct cdbmake cdbm; + substdio ss; + char packbuf[8]; + uint32 pos; + int fd; +} ; + +#endif diff --git a/chkshsgr.c b/chkshsgr.c @@ -0,0 +1,9 @@ +#include "exit.h" +void main() +{ + short x[4]; + + x[0] = x[1] = 0; + if (getgroups(1,x) == 0) if (setgroups(1,x) == -1) _exit(1); + _exit(0); +} diff --git a/chkspawn.c b/chkspawn.c @@ -0,0 +1,48 @@ +#include "substdio.h" +#include "subfd.h" +#include "fmt.h" +#include "select.h" +#include "exit.h" +#include "auto_spawn.h" + +char num[FMT_ULONG]; +fd_set fds; + +void main() +{ + unsigned long hiddenlimit; + unsigned long maxnumd; + + hiddenlimit = sizeof(fds) * 8; + maxnumd = (hiddenlimit - 5) / 2; + + if (auto_spawn < 1) { + substdio_puts(subfderr,"Oops. You have set conf-spawn lower than 1.\n"); + substdio_flush(subfderr); + _exit(1); + } + + if (auto_spawn > 255) { + substdio_puts(subfderr,"Oops. You have set conf-spawn higher than 255.\n"); + substdio_flush(subfderr); + _exit(1); + } + + if (auto_spawn > maxnumd) { + substdio_puts(subfderr,"Oops. Your system's FD_SET() has a hidden limit of "); + substdio_put(subfderr,num,fmt_ulong(num,hiddenlimit)); + substdio_puts(subfderr," descriptors.\n\ +This means that the qmail daemons could crash if you set the run-time\n\ +concurrency higher than "); + substdio_put(subfderr,num,fmt_ulong(num,maxnumd)); + substdio_puts(subfderr,". So I'm going to insist that the concurrency\n\ +limit in conf-spawn be at most "); + substdio_put(subfderr,num,fmt_ulong(num,maxnumd)); + substdio_puts(subfderr,". Right now it's "); + substdio_put(subfderr,num,fmt_ulong(num,(unsigned long) auto_spawn)); + substdio_puts(subfderr,".\n"); + substdio_flush(subfderr); + _exit(1); + } + _exit(0); +} diff --git a/coe.3 b/coe.3 @@ -0,0 +1,25 @@ +.TH coe 3 +.SH NAME +coe \- set close-on-exec flag for a descriptor +.SH SYNTAX +.B #include <coe.h> + +int \fBcoe\fP(\fIfd\fR); + +int \fIfd\fR; +.SH DESCRIPTION +.B coe +sets the close-on-exec flag for +file descriptor +.IR fd , +returning 0 if it was successful +or -1 on error. +If +.B coe +is successful, +.I fd +will be closed when the process calls +.BR execve . +.SH "SEE ALSO" +execve(2), +fcntl(2) diff --git a/coe.c b/coe.c @@ -0,0 +1,8 @@ +#include <fcntl.h> +#include "coe.h" + +int coe(fd) +int fd; +{ + return fcntl(fd,F_SETFD,1); +} diff --git a/coe.h b/coe.h @@ -0,0 +1,6 @@ +#ifndef COE_H +#define COE_H + +extern int coe(); + +#endif diff --git a/condredirect.1 b/condredirect.1 @@ -0,0 +1,54 @@ +.TH condredirect 1 +.SH NAME +condredirect \- perhaps redirect mail to another address +.SH SYNOPSIS +in +.BR .qmail : +.B |condredirect +.I newaddress +.I program +[ +.I arg ... +] +.SH DESCRIPTION +.B condredirect +feeds each new mail message to +.I program +with the given arguments. +If +.I program +exits 0, +.B condredirect +forwards the mail message to +.IR newaddress , +and then exits 99, +so further commands in +.B .qmail +are ignored. + +If +.I program +exits 111, +.B condredirect +exits 111, +so delivery will be retried later. + +If +.I program +exits anything else +(or does not exist), +.B condredirect +exits 0, +so the rest of +.B .qmail +will be processed as usual. + +Note that +it is not safe for +.I program +to fork a child that +reads the message in the background. +.SH "SEE ALSO" +dot-qmail(5), +qmail-command(8), +qmail-queue(8) diff --git a/condredirect.c b/condredirect.c @@ -0,0 +1,87 @@ +#include "sig.h" +#include "readwrite.h" +#include "exit.h" +#include "env.h" +#include "error.h" +#include "fork.h" +#include "wait.h" +#include "seek.h" +#include "qmail.h" +#include "stralloc.h" +#include "subfd.h" +#include "substdio.h" + +void die_success() { _exit(0); } +void die_99() { _exit(99); } +void die_perm(s) char *s; { substdio_putsflush(subfderr,s); _exit(100); } +void die_temp(s) char *s; { substdio_putsflush(subfderr,s); _exit(111); } +void die_nomem() { die_temp("condredirect: fatal: out of memory\n"); } + +struct qmail qqt; + +int mywrite(fd,buf,len) int fd; char *buf; int len; +{ + qmail_put(&qqt,buf,len); + return len; +} + +substdio ssin; +substdio ssout; +char inbuf[SUBSTDIO_INSIZE]; +char outbuf[16]; + +void main(argc,argv) +int argc; +char **argv; +{ + char *sender; + char *dtline; + int pid; + int wstat; + + if (!argv[1] || !argv[2]) + die_perm("condredirect: usage: condredirect newaddress program arg ...\n"); + + switch(pid = fork()) + { + case -1: die_temp("condredirect: fatal: unable to fork\n"); + case 0: + execvp(argv[2],argv + 2); + if (error_temp(errno)) _exit(111); + _exit(100); + } + if (wait_pid(&wstat,pid) != pid) + die_perm("condredirect: fatal: internal bug\n"); + if (wait_crashed(wstat)) die_temp("condredirect: fatal: child crashed\n"); + switch(wait_exitcode(wstat)) + { + case 0: break; + case 111: die_temp("condredirect: fatal: temporary child error\n"); + default: die_success(); + } + + if (seek_begin(0) == -1) die_temp("condredirect: fatal: unable to rewind\n"); + sig_pipeignore(); + + sender = env_get("SENDER"); + if (!sender) die_perm("condredirect: fatal: SENDER not set\n"); + dtline = env_get("DTLINE"); + if (!dtline) die_perm("condredirect: fatal: DTLINE not set\n"); + + if (qmail_open(&qqt) == -1) die_temp("condredirect: fatal: unable to fork\n"); + qmail_puts(&qqt,dtline); + substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf)); + substdio_fdbuf(&ssout,mywrite,-1,outbuf,sizeof(outbuf)); + if (substdio_copy(&ssout,&ssin) != 0) + die_temp("condredirect: fatal: error while reading message\n"); + substdio_flush(&ssout); + + qmail_from(&qqt,sender); + qmail_to(&qqt,argv[1]); + switch(qmail_close(&qqt)) + { + case 0: die_99(); + case QMAIL_TOOLONG: die_perm("condredirect: fatal: permanent qmail-queue error\n"); + default: die_temp("condredirect: fatal: temporary qmail-queue error\n"); + } +} diff --git a/conf-break b/conf-break @@ -0,0 +1,9 @@ +- + +This character is the user-ext delimiter. The default delimiter is -, +meaning that user joe controls joe-anything. Some system administrators +prefer + or =. + +You can override this choice at run time with the qmail-users mechanism. + +Multicharacter delimiters are not permitted. diff --git a/conf-cc b/conf-cc @@ -0,0 +1,3 @@ +cc -O2 + +This will be used to compile .c files. diff --git a/conf-groups b/conf-groups @@ -0,0 +1,6 @@ +qmail +nofiles + +These are the qmail groups. The second group should not have access to +any files, but it must be usable for processes; this requirement +excludes the ``nogroup'' and ``nobody'' groups on many systems. diff --git a/conf-ld b/conf-ld @@ -0,0 +1,3 @@ +cc -s + +This will be used to link .o files into an executable. diff --git a/conf-patrn b/conf-patrn @@ -0,0 +1,5 @@ +022 + +These stat bits are not allowed in ~ and ~/.qmail. + +Note that ~ftp, ~www, ~uucp, etc. should be owned by root. diff --git a/conf-qmail b/conf-qmail @@ -0,0 +1,4 @@ +/var/qmail + +This is the qmail home directory. It must be a local directory, not +shared among machines. This is where qmail queues all mail messages. diff --git a/conf-spawn b/conf-spawn @@ -0,0 +1,5 @@ +120 + +This is a silent concurrency limit. You can't set it above 255. On some +systems you can't set it above 125. qmail will refuse to compile if the +limit is too high. diff --git a/conf-split b/conf-split @@ -0,0 +1,3 @@ +23 + +This is the queue subdirectory split. diff --git a/conf-users b/conf-users @@ -0,0 +1,15 @@ +alias +qmaild +qmaill +root +qmailp +qmailq +qmailr +qmails + +The qmail system is heavily partitioned for security; it does almost +nothing as root. + +The first eight lines of this file are the alias user, the daemon user, +the log user, the owner of miscellaneous files such as binaries, the +passwd user, the queue user, the remote user, and the send user. diff --git a/constmap.c b/constmap.c @@ -0,0 +1,123 @@ +#include "constmap.h" +#include "alloc.h" +#include "case.h" + +static constmap_hash hash(s,len) +char *s; +int len; +{ + unsigned char ch; + constmap_hash h; + h = 5381; + while (len > 0) + { + ch = *s++ - 'A'; + if (ch <= 'Z' - 'A') ch += 'a' - 'A'; + h = ((h << 5) + h) ^ ch; + --len; + } + return h; +} + +char *constmap(cm,s,len) +struct constmap *cm; +char *s; +int len; +{ + constmap_hash h; + int pos; + h = hash(s,len); + pos = cm->first[h & cm->mask]; + while (pos != -1) + { + if (h == cm->hash[pos]) + if (len == cm->inputlen[pos]) + if (!case_diffb(cm->input[pos],len,s)) + return cm->input[pos] + cm->inputlen[pos] + 1; + pos = cm->next[pos]; + } + return 0; +} + +int constmap_init(cm,s,len,flagcolon) +struct constmap *cm; +char *s; +int len; +int flagcolon; +{ + int i; + int j; + int k; + int pos; + constmap_hash h; + + cm->num = 0; + for (j = 0;j < len;++j) if (!s[j]) ++cm->num; + + h = 64; + while (h && (h < cm->num)) h += h; + cm->mask = h - 1; + + cm->first = (int *) alloc(sizeof(int) * h); + if (cm->first) + { + cm->input = (char **) alloc(sizeof(char *) * cm->num); + if (cm->input) + { + cm->inputlen = (int *) alloc(sizeof(int) * cm->num); + if (cm->inputlen) + { + cm->hash = (constmap_hash *) alloc(sizeof(constmap_hash) * cm->num); + if (cm->hash) + { + cm->next = (int *) alloc(sizeof(int) * cm->num); + if (cm->next) + { + for (h = 0;h <= cm->mask;++h) + cm->first[h] = -1; + pos = 0; + i = 0; + for (j = 0;j < len;++j) + if (!s[j]) + { + k = j - i; + if (flagcolon) + { + for (k = i;k < j;++k) + if (s[k] == ':') + break; + if (k >= j) { i = j + 1; continue; } + k -= i; + } + cm->input[pos] = s + i; + cm->inputlen[pos] = k; + h = hash(s + i,k); + cm->hash[pos] = h; + h &= cm->mask; + cm->next[pos] = cm->first[h]; + cm->first[h] = pos; + ++pos; + i = j + 1; + } + return 1; + } + alloc_free(cm->hash); + } + alloc_free(cm->inputlen); + } + alloc_free(cm->input); + } + alloc_free(cm->first); + } + return 0; +} + +void constmap_free(cm) +struct constmap *cm; +{ + alloc_free(cm->next); + alloc_free(cm->hash); + alloc_free(cm->inputlen); + alloc_free(cm->input); + alloc_free(cm->first); +} diff --git a/constmap.h b/constmap.h @@ -0,0 +1,22 @@ +#ifndef CONSTMAP_H +#define CONSTMAP_H + +typedef unsigned long constmap_hash; + +struct constmap + { + int num; + constmap_hash mask; + constmap_hash *hash; + int *first; + int *next; + char **input; + int *inputlen; + } +; + +extern int constmap_init(); +extern void constmap_free(); +extern char *constmap(); + +#endif diff --git a/control.c b/control.c @@ -0,0 +1,129 @@ +#include "readwrite.h" +#include "open.h" +#include "getln.h" +#include "stralloc.h" +#include "substdio.h" +#include "error.h" +#include "control.h" +#include "alloc.h" +#include "scan.h" + +static char inbuf[64]; +static stralloc line = {0}; +static stralloc me = {0}; +static int meok = 0; + +static void striptrailingwhitespace(sa) +stralloc *sa; +{ + while (sa->len > 0) + switch(sa->s[sa->len - 1]) + { + case '\n': case ' ': case '\t': + --sa->len; + break; + default: + return; + } +} + +int control_init() +{ + int r; + r = control_readline(&me,"control/me"); + if (r == 1) meok = 1; + return r; +} + +int control_rldef(sa,fn,flagme,def) +stralloc *sa; +char *fn; +int flagme; +char *def; +{ + int r; + r = control_readline(sa,fn); + if (r) return r; + if (flagme) if (meok) return stralloc_copy(sa,&me) ? 1 : -1; + if (def) return stralloc_copys(sa,def) ? 1 : -1; + return r; +} + +int control_readline(sa,fn) +stralloc *sa; +char *fn; +{ + substdio ss; + int fd; + int match; + + fd = open_read(fn); + if (fd == -1) { if (errno == error_noent) return 0; return -1; } + + substdio_fdbuf(&ss,read,fd,inbuf,sizeof(inbuf)); + + if (getln(&ss,sa,&match,'\n') == -1) { close(fd); return -1; } + + striptrailingwhitespace(sa); + close(fd); + return 1; +} + +int control_readint(i,fn) +int *i; +char *fn; +{ + unsigned long u; + switch(control_readline(&line,fn)) + { + case 0: return 0; + case -1: return -1; + } + if (scan_nbblong(line.s,line.len,10,0,&u) == 0) return 0; + *i = u; + return 1; +} + +int control_readfile(sa,fn,flagme) +stralloc *sa; +char *fn; +int flagme; +{ + substdio ss; + int fd; + int match; + + if (!stralloc_copys(sa,"")) return -1; + + fd = open_read(fn); + if (fd == -1) + { + if (errno == error_noent) + { + if (flagme && meok) + { + if (!stralloc_copy(sa,&me)) return -1; + if (!stralloc_0(sa)) return -1; + return 1; + } + return 0; + } + return -1; + } + + substdio_fdbuf(&ss,read,fd,inbuf,sizeof(inbuf)); + + for (;;) + { + if (getln(&ss,&line,&match,'\n') == -1) break; + if (!match && !line.len) { close(fd); return 1; } + striptrailingwhitespace(&line); + if (!stralloc_0(&line)) break; + if (line.s[0]) + if (line.s[0] != '#') + if (!stralloc_cat(sa,&line)) break; + if (!match) { close(fd); return 1; } + } + close(fd); + return -1; +} diff --git a/control.h b/control.h @@ -0,0 +1,10 @@ +#ifndef CONTROL_H +#define CONTROL_H + +extern int control_init(); +extern int control_readline(); +extern int control_rldef(); +extern int control_readint(); +extern int control_readfile(); + +#endif diff --git a/date822fmt.c b/date822fmt.c @@ -0,0 +1,29 @@ +#include "datetime.h" +#include "fmt.h" +#include "date822fmt.h" + +static char *montab[12] = { +"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" +}; + +unsigned int date822fmt(s,dt) +char *s; +struct datetime *dt; +{ + unsigned int i; + unsigned int len; + len = 0; + i = fmt_uint(s,dt->mday); len += i; if (s) s += i; + i = fmt_str(s," "); len += i; if (s) s += i; + i = fmt_str(s,montab[dt->mon]); len += i; if (s) s += i; + i = fmt_str(s," "); len += i; if (s) s += i; + i = fmt_uint(s,dt->year + 1900); len += i; if (s) s += i; + i = fmt_str(s," "); len += i; if (s) s += i; + i = fmt_uint0(s,dt->hour,2); len += i; if (s) s += i; + i = fmt_str(s,":"); len += i; if (s) s += i; + i = fmt_uint0(s,dt->min,2); len += i; if (s) s += i; + i = fmt_str(s,":"); len += i; if (s) s += i; + i = fmt_uint0(s,dt->sec,2); len += i; if (s) s += i; + i = fmt_str(s," -0000\n"); len += i; if (s) s += i; + return len; +} diff --git a/date822fmt.h b/date822fmt.h @@ -0,0 +1,7 @@ +#ifndef DATE822FMT_H +#define DATE822FMT_H + +extern unsigned int date822fmt(); +#define DATE822FMT 60 + +#endif diff --git a/datemail.sh b/datemail.sh @@ -0,0 +1 @@ +exec QMAIL/bin/predate QMAIL/bin/sendmail ${1+"$@"} diff --git a/datetime.3 b/datetime.3 @@ -0,0 +1,73 @@ +.TH datetime 3 +.SH NAME +datetime \- convert between TAI labels and seconds +.SH SYNTAX +.B #include <datetime.h> + +void \fBdatetime_tai\fP(&\fIdt\fR,\fIt\fR); + +datetime_sec \fBdatetime_untai\fP(&\fIdt\fR); + +struct datetime \fIdt\fR; +.br +datetime_sec \fIt\fR; +.SH DESCRIPTION +International Atomic Time, TAI, +is the fundamental unit for time measurements. +TAI has one label for every second of real time, +without complications such as leap seconds. + +A +struct datetime +variable, +such as +.IR dt , +stores a TAI label. +.I dt\fB.year +is the year number minus 1900; +.I dt\fB.mon +is the month number, from 0 (January) through 11 (December); +.I dt\fB.mday +is the day of the month, from 1 through 31; +.I dt\fB.hour +is the hour, from 0 through 23; +.I dt\fB.min +is the minute, from 0 through 59; +.I dt\fB.sec +is the second, from 0 through 59; +.I dt\fB.wday +is the day of the week, from 0 (Sunday) through 6 (Saturday); +.I dt\fB.yday +is the day of the year, from 0 through 365. + +The +.B datetime +library supports more convenient TAI manipulation with +the datetime_sec type. +A datetime_sec value, such as +.IR t , +is an integer referring to the +.IR t th +second after the beginning of 1970 TAI. +The first second of 1970 TAI was 0; +the next second was 1; +the last second of 1969 TAI was -1. +The difference between two datetime_sec values is a number +of real-time seconds. + +.B datetime_tai +converts a datetime_sec to a TAI label. + +.B datetime_untai +reads a TAI label +(specifically +.IR dt\fB.year , +.IR dt\fB.mon , +.IR dt\fB.mday , +.IR dt\fB.hour , +.IR dt\fB.min , +and +.IR dt\fB.sec ) +and returns a datetime_sec. +.SH "SEE ALSO" +now(3) diff --git a/datetime.c b/datetime.c @@ -0,0 +1,55 @@ +/* 19950925 */ +#include "datetime.h" + +void datetime_tai(dt,t) +struct datetime *dt; +datetime_sec t; +{ + int day; + int tod; + int year; + int yday; + int wday; + int mon; + + tod = t % 86400; + day = t / 86400; + if (tod < 0) { tod += 86400; --day; } + + dt->hour = tod / 3600; + tod %= 3600; + dt->min = tod / 60; + dt->sec = tod % 60; + + wday = (day + 4) % 7; if (wday < 0) wday += 7; + dt->wday = wday; + + day -= 11017; + /* day 0 is march 1, 2000 */ + year = 5 + day / 146097; + day = day % 146097; if (day < 0) { day += 146097; --year; } + /* from now on, day is nonnegative */ + year *= 4; + if (day == 146096) { year += 3; day = 36524; } + else { year += day / 36524; day %= 36524; } + year *= 25; + year += day / 1461; + day %= 1461; + year *= 4; + yday = (day < 306); + if (day == 1460) { year += 3; day = 365; } + else { year += day / 365; day %= 365; } + yday += day; + + day *= 10; + mon = (day + 5) / 306; + day = day + 5 - 306 * mon; + day /= 10; + if (mon >= 10) { yday -= 306; ++year; mon -= 10; } + else { yday += 59; mon += 2; } + + dt->yday = yday; + dt->year = year - 1900; + dt->mon = mon; + dt->mday = day + 1; +} diff --git a/datetime.h b/datetime.h @@ -0,0 +1,20 @@ +#ifndef DATETIME_H +#define DATETIME_H + +struct datetime { + int hour; + int min; + int sec; + int wday; + int mday; + int yday; + int mon; + int year; +} ; + +typedef long datetime_sec; + +extern void datetime_tai(); +extern datetime_sec datetime_untai(); + +#endif diff --git a/datetime_un.c b/datetime_un.c @@ -0,0 +1,35 @@ +#include "datetime.h" + +/* roughly 100x faster than mktime() */ +datetime_sec datetime_untai(dt) +struct datetime *dt; +{ + int year; + int day; + int mon; + + year = dt->year + 1900; + + mon = dt->mon; + if (mon >= 2) { mon -= 2; } + else { mon += 10; --year; } + + day = (dt->mday - 1) * 10 + 5 + 306 * mon; + day /= 10; + + if (day == 365) { year -= 3; day = 1460; } + else { day += 365 * (year % 4); } + year /= 4; + + day += 1461 * (year % 25); + year /= 25; + + if (day == 36524) { year -= 3; day = 146096; } + else { day += 36524 * (year % 4); } + year /= 4; + + day += 146097 * (year - 5); + day += 11017; + + return ((day * 24 + dt->hour) * 60 + dt->min) * 60 + dt->sec; +} diff --git a/direntry.3 b/direntry.3 @@ -0,0 +1,36 @@ +.TH direntry 3 +.SH NAME +direntry \- read directory entries +.SH SYNTAX +.B #include <direntry.h> + +DIR *\fBopendir\fP(\fIfn\fR); + +struct direntry *\fBreaddir\fP(\fIdir\fP); + +void \fBclosedir\fP(\fIdir\fP); + +DIR *\fIdir\fR; +.br +char *\fIfn\fR; +.SH DESCRIPTION +The point of +.B direntry.h +is to provide a uniform interface to BSD's +.B sys/dir.h +and POSIX's +.BR dirent.h . + +The +.B readdir +interface is highly unsatisfactory. +It does not distinguish between I/O errors and end-of-directory. +It uses +.BR malloc . +The return type for +.B closedir +varies: some implementations return the +.B close +return value. +.SH "SEE ALSO" +readdir(3) diff --git a/direntry.h1 b/direntry.h1 @@ -0,0 +1,8 @@ +#ifndef DIRENTRY_H +#define DIRENTRY_H + +#include <sys/types.h> +#include <sys/dir.h> +#define direntry struct direct + +#endif diff --git a/direntry.h2 b/direntry.h2 @@ -0,0 +1,8 @@ +#ifndef DIRENTRY_H +#define DIRENTRY_H + +#include <sys/types.h> +#include <dirent.h> +#define direntry struct dirent + +#endif diff --git a/dns.c b/dns.c @@ -0,0 +1,402 @@ +#include <stdio.h> +#include <netdb.h> +#include <sys/types.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <resolv.h> +#include <errno.h> +extern int res_query(); +extern int res_search(); +extern int errno; +extern int h_errno; +#include "ip.h" +#include "ipalloc.h" +#include "fmt.h" +#include "alloc.h" +#include "str.h" +#include "stralloc.h" +#include "dns.h" +#include "case.h" + +static unsigned short getshort(c) unsigned char *c; +{ unsigned short u; u = c[0]; return (u << 8) + c[1]; } + +static union { HEADER hdr; unsigned char buf[PACKETSZ]; } response; +static int responselen; +static unsigned char *responseend; +static unsigned char *responsepos; + +static int numanswers; +static char name[MAXDNAME]; +static struct ip_address ip; +unsigned short pref; + +static stralloc glue = {0}; + +static int (*lookup)() = res_query; + +static int resolve(domain,type) +stralloc *domain; +int type; +{ + int n; + int i; + + errno = 0; + if (!stralloc_copy(&glue,domain)) return DNS_MEM; + if (!stralloc_0(&glue)) return DNS_MEM; + responselen = lookup(glue.s,C_IN,type,response.buf,sizeof(response)); + if (responselen <= 0) + { + if (errno == ECONNREFUSED) return DNS_SOFT; + if (h_errno == TRY_AGAIN) return DNS_SOFT; + return DNS_HARD; + } + if (responselen >= sizeof(response)) + responselen = sizeof(response); + responseend = response.buf + responselen; + responsepos = response.buf + sizeof(HEADER); + n = ntohs(response.hdr.qdcount); + while (n-- > 0) + { + i = dn_expand(response.buf,responseend,responsepos,name,MAXDNAME); + if (i < 0) return DNS_SOFT; + responsepos += i; + i = responseend - responsepos; + if (i < QFIXEDSZ) return DNS_SOFT; + responsepos += QFIXEDSZ; + } + numanswers = ntohs(response.hdr.ancount); + return 0; +} + +static int findname(wanttype) +int wanttype; +{ + unsigned short rrtype; + unsigned short rrdlen; + int i; + + if (numanswers <= 0) return 2; + --numanswers; + if (responsepos == responseend) return DNS_SOFT; + + i = dn_expand(response.buf,responseend,responsepos,name,MAXDNAME); + if (i < 0) return DNS_SOFT; + responsepos += i; + + i = responseend - responsepos; + if (i < 4 + 3 * 2) return DNS_SOFT; + + rrtype = getshort(responsepos); + rrdlen = getshort(responsepos + 8); + responsepos += 10; + + if (rrtype == wanttype) + { + if (dn_expand(response.buf,responseend,responsepos,name,MAXDNAME) < 0) + return DNS_SOFT; + responsepos += rrdlen; + return 1; + } + + responsepos += rrdlen; + return 0; +} + +static int findip(wanttype) +int wanttype; +{ + unsigned short rrtype; + unsigned short rrdlen; + int i; + + if (numanswers <= 0) return 2; + --numanswers; + if (responsepos == responseend) return DNS_SOFT; + + i = dn_expand(response.buf,responseend,responsepos,name,MAXDNAME); + if (i < 0) return DNS_SOFT; + responsepos += i; + + i = responseend - responsepos; + if (i < 4 + 3 * 2) return DNS_SOFT; + + rrtype = getshort(responsepos); + rrdlen = getshort(responsepos + 8); + responsepos += 10; + + if (rrtype == wanttype) + { + if (rrdlen < 4) + return DNS_SOFT; + ip.d[0] = responsepos[0]; + ip.d[1] = responsepos[1]; + ip.d[2] = responsepos[2]; + ip.d[3] = responsepos[3]; + responsepos += rrdlen; + return 1; + } + + responsepos += rrdlen; + return 0; +} + +static int findmx(wanttype) +int wanttype; +{ + unsigned short rrtype; + unsigned short rrdlen; + int i; + + if (numanswers <= 0) return 2; + --numanswers; + if (responsepos == responseend) return DNS_SOFT; + + i = dn_expand(response.buf,responseend,responsepos,name,MAXDNAME); + if (i < 0) return DNS_SOFT; + responsepos += i; + + i = responseend - responsepos; + if (i < 4 + 3 * 2) return DNS_SOFT; + + rrtype = getshort(responsepos); + rrdlen = getshort(responsepos + 8); + responsepos += 10; + + if (rrtype == wanttype) + { + if (rrdlen < 3) + return DNS_SOFT; + pref = (responsepos[0] << 8) + responsepos[1]; + if (dn_expand(response.buf,responseend,responsepos + 2,name,MAXDNAME) < 0) + return DNS_SOFT; + responsepos += rrdlen; + return 1; + } + + responsepos += rrdlen; + return 0; +} + +void dns_init(flagsearch) +int flagsearch; +{ + res_init(); + if (flagsearch) lookup = res_search; +} + +int dns_cname(sa) +stralloc *sa; +{ + int r; + int loop; + for (loop = 0;loop < 10;++loop) + { + if (!sa->len) return loop; + if (sa->s[sa->len - 1] == ']') return loop; + if (sa->s[sa->len - 1] == '.') { --sa->len; continue; } + switch(resolve(sa,T_ANY)) + { + case DNS_MEM: return DNS_MEM; + case DNS_SOFT: return DNS_SOFT; + case DNS_HARD: return loop; + default: + while ((r = findname(T_CNAME)) != 2) + { + if (r == DNS_SOFT) return DNS_SOFT; + if (r == 1) + { + if (!stralloc_copys(sa,name)) return DNS_MEM; + break; + } + } + if (r == 2) return loop; + } + } + return DNS_HARD; /* alias loop */ +} + +#define FMT_IAA 40 + +static int iaafmt(s,ip) +char *s; +struct ip_address *ip; +{ + unsigned int i; + unsigned int len; + len = 0; + i = fmt_ulong(s,(unsigned long) ip->d[3]); len += i; if (s) s += i; + i = fmt_str(s,"."); len += i; if (s) s += i; + i = fmt_ulong(s,(unsigned long) ip->d[2]); len += i; if (s) s += i; + i = fmt_str(s,"."); len += i; if (s) s += i; + i = fmt_ulong(s,(unsigned long) ip->d[1]); len += i; if (s) s += i; + i = fmt_str(s,"."); len += i; if (s) s += i; + i = fmt_ulong(s,(unsigned long) ip->d[0]); len += i; if (s) s += i; + i = fmt_str(s,".in-addr.arpa."); len += i; if (s) s += i; + return len; +} + +int dns_ptr(sa,ip) +stralloc *sa; +struct ip_address *ip; +{ + int r; + + if (!stralloc_ready(sa,iaafmt((char *) 0,ip))) return DNS_MEM; + sa->len = iaafmt(sa->s,ip); + switch(resolve(sa,T_PTR)) + { + case DNS_MEM: return DNS_MEM; + case DNS_SOFT: return DNS_SOFT; + case DNS_HARD: return DNS_HARD; + } + while ((r = findname(T_PTR)) != 2) + { + if (r == DNS_SOFT) return DNS_SOFT; + if (r == 1) + { + if (!stralloc_copys(sa,name)) return DNS_MEM; + return 0; + } + } + return DNS_HARD; +} + +static int dns_ipplus(ia,sa,pref) +ipalloc *ia; +stralloc *sa; +int pref; +{ + int r; + struct ip_mx ix; + + if (sa->len && (sa->s[0] == '[')) + { + if (!stralloc_copy(&glue,sa)) return DNS_MEM; + if (!stralloc_0(&glue)) return DNS_MEM; + ix.pref = 0; + if (!glue.s[ip_scanbracket(glue.s,&ix.ip)]) + { + if (!ipalloc_append(ia,&ix)) return DNS_MEM; + return 0; + } + } + + switch(resolve(sa,T_A)) + { + case DNS_MEM: return DNS_MEM; + case DNS_SOFT: return DNS_SOFT; + case DNS_HARD: return DNS_HARD; + } + while ((r = findip(T_A)) != 2) + { + ix.ip = ip; + ix.pref = pref; + if (r == DNS_SOFT) return DNS_SOFT; + if (r == 1) + if (!ipalloc_append(ia,&ix)) return DNS_MEM; + } + return 0; +} + +int dns_ip(ia,sa) +ipalloc *ia; +stralloc *sa; +{ + if (!ipalloc_readyplus(ia,0)) return DNS_MEM; + ia->len = 0; + return dns_ipplus(ia,sa,0); +} + +int dns_mxip(ia,sa,random) +ipalloc *ia; +stralloc *sa; +unsigned long random; +{ + int r; + struct mx { stralloc sa; unsigned short p; } *mx; + int nummx; + int i; + int j; + int flagsoft; + + if (!ipalloc_readyplus(ia,0)) return DNS_MEM; + ia->len = 0; + + if (sa->len && (sa->s[0] == '[')) + { + struct ip_mx ix; + if (!stralloc_copy(&glue,sa)) return DNS_MEM; + if (!stralloc_0(&glue)) return DNS_MEM; + ix.pref = 0; + if (!glue.s[ip_scanbracket(glue.s,&ix.ip)]) + { + if (!ipalloc_append(ia,&ix)) return DNS_MEM; + return 0; + } + } + + switch(resolve(sa,T_MX)) + { + case DNS_MEM: return DNS_MEM; + case DNS_SOFT: return DNS_SOFT; + case DNS_HARD: return dns_ip(ia,sa); + } + + mx = (struct mx *) alloc(numanswers * sizeof(struct mx)); + if (!mx) return DNS_MEM; + nummx = 0; + + while ((r = findmx(T_MX)) != 2) + { + if (r == DNS_SOFT) { alloc_free(mx); return DNS_SOFT; } + if (r == 1) + { + mx[nummx].p = pref; + mx[nummx].sa.s = 0; + if (!stralloc_copys(&mx[nummx].sa,name)) + { + while (nummx > 0) alloc_free(mx[--nummx].sa.s); + alloc_free(mx); return DNS_MEM; + } + ++nummx; + } + } + + if (!nummx) return dns_ip(ia,sa); /* e.g., CNAME -> A */ + + flagsoft = 0; + while (nummx > 0) + { + unsigned long numsame; + + i = 0; + numsame = 1; + for (j = 1;j < nummx;++j) + if (mx[j].p < mx[i].p) + { + i = j; + numsame = 1; + } + else if (mx[j].p == mx[i].p) + { + ++numsame; + random = random * 69069 + 1; + if ((random / 2) < (2147483647 / numsame)) + i = j; + } + + switch(dns_ipplus(ia,&mx[i].sa,mx[i].p)) + { + case DNS_MEM: case DNS_SOFT: + flagsoft = 1; break; + } + + alloc_free(mx[i].sa.s); + mx[i] = mx[--nummx]; + } + + alloc_free(mx); + return flagsoft; +} diff --git a/dns.h b/dns.h @@ -0,0 +1,14 @@ +#ifndef DNS_H +#define DNS_H + +#define DNS_SOFT -1 +#define DNS_HARD -2 +#define DNS_MEM -3 + +void dns_init(); +int dns_cname(); +int dns_mxip(); +int dns_ip(); +int dns_ptr(); + +#endif diff --git a/dnscname.c b/dnscname.c @@ -0,0 +1,25 @@ +#include "substdio.h" +#include "subfd.h" +#include "stralloc.h" +#include "dns.h" +#include "dnsdoe.h" +#include "readwrite.h" +#include "exit.h" + +stralloc sa = {0}; + +void main(argc,argv) +int argc; +char **argv; +{ + if (!argv[1]) _exit(100); + + if (!stralloc_copys(&sa,argv[1])) + { substdio_putsflush(subfderr,"out of memory\n"); _exit(111); } + + dns_init(0); + dnsdoe(dns_cname(&sa)); + substdio_putflush(subfdout,sa.s,sa.len); + substdio_putsflush(subfdout,"\n"); + _exit(0); +} diff --git a/dnsdoe.c b/dnsdoe.c @@ -0,0 +1,16 @@ +#include "substdio.h" +#include "subfd.h" +#include "exit.h" +#include "dns.h" +#include "dnsdoe.h" + +void dnsdoe(r) +int r; +{ + switch (r) + { + case DNS_HARD: substdio_putsflush(subfderr,"hard error\n"); _exit(100); + case DNS_SOFT: substdio_putsflush(subfderr,"soft error\n"); _exit(111); + case DNS_MEM: substdio_putsflush(subfderr,"out of memory\n"); _exit(111); + } +} diff --git a/dnsdoe.h b/dnsdoe.h @@ -0,0 +1,6 @@ +#ifndef DNSDOE_H +#define DNSDOE_H + +extern void dnsdoe(); + +#endif diff --git a/dnsfq.c b/dnsfq.c @@ -0,0 +1,32 @@ +#include "substdio.h" +#include "subfd.h" +#include "stralloc.h" +#include "dns.h" +#include "dnsdoe.h" +#include "ip.h" +#include "ipalloc.h" +#include "exit.h" + +stralloc sa = {0}; +ipalloc ia = {0}; + +void main(argc,argv) +int argc; +char **argv; +{ + if (!argv[1]) _exit(100); + + if (!stralloc_copys(&sa,argv[1])) + { substdio_putsflush(subfderr,"out of memory\n"); _exit(111); } + + dns_init(1); + dnsdoe(dns_ip(&ia,&sa)); + if (ia.len <= 0) + { + substdio_putsflush(subfderr,"no IP addresses\n"); _exit(100); + } + dnsdoe(dns_ptr(&sa,&ia.ix[0].ip)); + substdio_putflush(subfdout,sa.s,sa.len); + substdio_putsflush(subfdout,"\n"); + _exit(0); +} diff --git a/dnsip.c b/dnsip.c @@ -0,0 +1,34 @@ +#include "substdio.h" +#include "subfd.h" +#include "stralloc.h" +#include "dns.h" +#include "dnsdoe.h" +#include "ip.h" +#include "ipalloc.h" +#include "exit.h" + +char temp[IPFMT]; + +stralloc sa = {0}; +ipalloc ia = {0}; + +void main(argc,argv) +int argc; +char **argv; +{ + int j; + + if (!argv[1]) _exit(100); + + if (!stralloc_copys(&sa,argv[1])) + { substdio_putsflush(subfderr,"out of memory\n"); _exit(111); } + + dns_init(0); + dnsdoe(dns_ip(&ia,&sa)); + for (j = 0;j < ia.len;++j) + { + substdio_put(subfdout,temp,ip_fmt(temp,&ia.ix[j].ip)); + substdio_putsflush(subfdout,"\n"); + } + _exit(0); +} diff --git a/dnsmxip.c b/dnsmxip.c @@ -0,0 +1,40 @@ +#include "substdio.h" +#include "subfd.h" +#include "stralloc.h" +#include "fmt.h" +#include "dns.h" +#include "dnsdoe.h" +#include "ip.h" +#include "ipalloc.h" +#include "now.h" +#include "exit.h" + +char temp[IPFMT + FMT_ULONG]; + +stralloc sa = {0}; +ipalloc ia = {0}; + +void main(argc,argv) +int argc; +char **argv; +{ + int j; + unsigned long r; + + if (!argv[1]) _exit(100); + + if (!stralloc_copys(&sa,argv[1])) + { substdio_putsflush(subfderr,"out of memory\n"); _exit(111); } + + r = now() + getpid(); + dns_init(0); + dnsdoe(dns_mxip(&ia,&sa,r)); + for (j = 0;j < ia.len;++j) + { + substdio_put(subfdout,temp,ip_fmt(temp,&ia.ix[j].ip)); + substdio_puts(subfdout," "); + substdio_put(subfdout,temp,fmt_ulong(temp,(unsigned long) ia.ix[j].pref)); + substdio_putsflush(subfdout,"\n"); + } + _exit(0); +} diff --git a/dnsptr.c b/dnsptr.c @@ -0,0 +1,27 @@ +#include "substdio.h" +#include "subfd.h" +#include "stralloc.h" +#include "str.h" +#include "scan.h" +#include "dns.h" +#include "dnsdoe.h" +#include "ip.h" +#include "exit.h" + +stralloc sa = {0}; +struct ip_address ip; + +void main(argc,argv) +int argc; +char **argv; +{ + if (!argv[1]) _exit(100); + + ip_scan(argv[1],&ip); + + dns_init(0); + dnsdoe(dns_ptr(&sa,&ip)); + substdio_putflush(subfdout,sa.s,sa.len); + substdio_putsflush(subfdout,"\n"); + _exit(0); +} diff --git a/dot-qmail.9 b/dot-qmail.9 @@ -0,0 +1,394 @@ +.TH dot-qmail 5 +.SH NAME +dot-qmail \- control the delivery of mail messages +.SH DESCRIPTION +Normally the +.B qmail-local +program delivers each incoming message to your system mailbox, +.IR homedir\fB/Mailbox , +where +.I homedir +is your home directory. + +It can instead +write the mail to a different file or directory, +forward it to another address, +distribute it to a mailing list, +or even execute programs, +all under your control. +.SH "THE QMAIL FILE" +To change +.BR qmail-local 's +behavior, set up a +.B .qmail +file in your home directory. + +.B .qmail +contains one or more lines. +Each line is a delivery instruction. +.B qmail-local +follows each instruction in turn. +There are five types of delivery instructions: +(1) comment; (2) program; (3) forward; (4) mbox; (5) maildir. +.TP 5 +(1) +A comment line begins with a number sign: + +.EX + # this is a comment +.EE + +.B qmail-local +ignores the line. +.TP 5 +(2) +A program line begins with a vertical bar: + +.EX + |/usr/ucb/vacation djb +.EE + +.B qmail-local +takes the rest of the line as a command to supply to +.BR sh . +See +.B qmail-command(8) +for further information. +.TP 5 +(3) +A forward line begins with an ampersand: + +.EX + &me@new.job.com +.EE + +.B qmail-local +takes the rest of the line as a mail address; +it uses +.B qmail-queue +to forward the message to that address. +The address must contain a fully qualified domain name; +it must not contain extra spaces, angle brackets, or comments: + +.EX + # the following examples are WRONG +.br + &me@new +.br + &<me@new.job.com> +.br + & me@new.job.com +.br + &me@new.job.com (New Address) +.EE + +If the address begins with a letter or number, +you may leave out the ampersand: + +.EX + me@new.job.com +.EE + +Note that +.B qmail-local +omits its new +.B Return-Path +line when forwarding messages. +.TP 5 +(4) +An +.I mbox +line begins with a slash or dot, +and does not end with a slash: + +.EX + /home/djb/Mailbox.sos +.EE + +.B qmail-local +takes the entire line as a filename. +It appends the mail message to that file, +using +.BR flock -style +file locking if possible. +.B qmail-local +stores the mail message in +.I mbox +format, as described in +.BR mbox(5) . + +.B WARNING: +On many systems, +anyone who can read a file can +.B flock +it, and thus hold up +.BR qmail-local 's +delivery forever. +Do not deliver mail to a publicly accessible file! + +If +.B qmail-local +is able to lock the file, but has trouble writing to it +(because, for example, the disk is full), +it will truncate the file back to its original length. +However, it cannot prevent mailbox corruption if the system +crashes during delivery. +.TP 5 +(5) +A +.I maildir +line begins with a slash or dot, +and ends with a slash: + +.EX + /home/djb/Maildir/ +.EE + +.B qmail-local +takes the entire line as the name of a directory in +.I maildir +format. +It reliably stores the incoming message in that directory. +See +.B maildir(5) +for more details. +.PP +If +.B .qmail +has the execute bit set, +it must not contain any +program lines, +.I mbox +lines, +or +.I maildir +lines. +If +.B qmail-local +sees any such lines, +it will stop and indicate a temporary failure. + +If +.B .qmail +is completely empty (0 bytes long), or does not exist, +.B qmail-local +follows the +.I aliasempty +instructions set by your system administrator; +normally +.I aliasempty +is +.BR ./Mailbox , +so +.B qmail-local +appends the mail message to +.B Mailbox +in +.I mbox +format. + +.B .qmail +may contain extra spaces and tabs at the end of a line. +Blank lines are allowed, but not for the first line of +.BR .qmail . + +If +.B .qmail +is world-writable or group-writable, +.B qmail-local +stops and indicates a temporary failure. +.SH "SAFE QMAIL EDITING" +Incoming messages can arrive at any moment. +If you want to safely edit your +.B .qmail +file, first set the sticky bit on your home directory: + +.EX + chmod +t $HOME +.EE + +.B qmail-local +will temporarily defer delivery of any message to you +if your home directory is sticky +(or group-writable or other-writable, +which should never happen). +Make sure to + +.EX + chmod -t $HOME +.EE + +when you are done! +It's a good idea to test your new +.B .qmail +file as follows: + +.EX + qmail-local -n $USER $HOME $USER '' '' '' '' +.EE +.SH "EXTENSION ADDRESSES" +In the +.B qmail +system, +you control all local addresses of the form +.IR user\fBBREAK\fIanything , +as well as the address +.I user +itself, +where +.I user +is your account name. +Delivery to +.I user\fBBREAK\fIanything +is controlled by the file +.IR homedir/\fB.qmail\-\fIanything . +(These rules may be changed by the system administrator; +see +.BR qmail-users (5).) + +The +.B alias +user controls all other addresses. +Delivery to +.I local +is controlled by the file +.IR homedir/\fB.qmail\-\fIlocal , +where +.I homedir +is +.BR alias 's +home directory. + +In the following description, +.B qmail-local +is handling a message addressed to +.IR local@domain , +where +.I local +is controlled by +.BR .qmail\-\fIext . +Here is what it does. + +If +.B .qmail\-\fIext +is completely empty, +.B qmail-local +follows the +.I aliasempty +instructions set by your system administrator. + +If +.B .qmail\-\fIext +doesn't exist, +.B qmail-local +will try some default +.B .qmail +files. +For example, +if +.I ext +is +.BR foo-bar , +.B qmail-local +will try first +.BR .qmail-foo-bar , +then +.BR .qmail-foo-default , +and finally +.BR .qmail-default . +If none of these exist, +.B qmail-local +will bounce the message. +(Exception: for the basic +.I user +address, +.B qmail-local +treats a nonexistent +.B .qmail +the same as an empty +.BR .qmail .) + +.B WARNING: +For security, +.B qmail-local +replaces any dots in +.I ext +with colons before checking +.BR .qmail\-\fIext . +For convenience, +.B qmail-local +converts any uppercase letters in +.I ext +to lowercase. + +When +.B qmail-local +forwards a message as instructed in +.B .qmail\-\fIext +(or +.BR .qmail-default ), +it checks whether +.B .qmail\-\fIext\fB-owner\fP +exists. +If so, +it uses +.I local\fB-owner@\fIdomain +as the envelope sender for the forwarded message. +Otherwise it retains the envelope sender of the original message. +Exception: +.B qmail-local +always retains the original envelope sender +if it is the empty address or +.BR #@[] , +i.e., if this is a bounce message. + +.B qmail-local +also supports +.B variable envelope return paths +(VERPs): +if +.B .qmail\-\fIext\fB-owner\fP +and +.B .qmail\-\fIext\fB-owner-default\fP +both exist, it uses +.I local\fB\-owner\-@\fIdomain\fB-@[] +as the envelope sender. +This will cause a recipient +.I recip\fB@\fIreciphost +to see an envelope sender of +.IR local\fB\-owner\-\fIrecip\fB=\fIreciphost\fB@\fIdomain . +.SH "ERROR HANDLING" +If a delivery instruction fails, +.B qmail-local +stops immediately and reports failure. +.B qmail-local +handles forwarding after all other instructions, +so any error in another type of delivery will prevent all forwarding. + +If a program returns exit code 99, +.B qmail-local +ignores all succeeding lines in +.BR .qmail , +but it still pays attention to previous forward lines. + +To set up independent instructions, +where a temporary or permanent failure in one instruction +does not affect the others, +move each instruction into a separate +.B .qmail\-\fIext +file, and set up a central +.B .qmail +file that forwards to all of the +.BR .qmail\-\fIext s. +Note that +.B qmail-local +can handle any number of forward lines simultaneously. +.SH "SEE ALSO" +envelopes(5), +maildir(5), +mbox(5), +qmail-users(5), +qmail-local(8), +qmail-command(8), +qmail-queue(8), +qmail-lspawn(8) diff --git a/elq.sh b/elq.sh @@ -0,0 +1 @@ +QMAIL/bin/maildir2mbox && exec elm ${1+"$@"} diff --git a/env.3 b/env.3 @@ -0,0 +1,31 @@ +.TH env 3 +.SH NAME +env \- manage the environment +.SH SYNTAX +.B #include <env.h> + +char **\fBenviron\fP; + +char *\fBenv_get\fP(\fIname\fR); +.br +char *\fBenv_pick\fP(); + +char *\fIname\fR; +.SH DESCRIPTION +The environment, +.BR environ , +is a 0-terminated array of 0-terminated strings, +called environment variables. +Each environment variable is of the form +.IR name\fB=\fIvalue . + +.B env_get +returns the value of the first variable whose name is +.IR name , +or 0 if there is no such variable. + +.B env_pick +returns any variable in the environment, +or 0 if the environment is empty. +.SH "SEE ALSO" +environ(7) diff --git a/env.c b/env.c @@ -0,0 +1,113 @@ +/* env.c, envread.c, env.h: environ library +Daniel J. Bernstein, djb@silverton.berkeley.edu. +Depends on str.h, alloc.h. +Requires environ. +19960113: rewrite. warning: interface is different. +No known patent problems. +*/ + +#include "str.h" +#include "alloc.h" +#include "env.h" + +int env_isinit = 0; /* if env_isinit: */ +static int ea; /* environ is a pointer to ea+1 char*'s. */ +static int en; /* the first en of those are ALLOCATED. environ[en] is 0. */ + +static void env_goodbye(i) int i; +{ + alloc_free(environ[i]); + environ[i] = environ[--en]; + environ[en] = 0; +} + +static char *null = 0; + +void env_clear() +{ + if (env_isinit) while (en) env_goodbye(0); + else environ = &null; +} + +static void env_unsetlen(s,len) char *s; int len; +{ + int i; + for (i = en - 1;i >= 0;--i) + if (!str_diffn(s,environ[i],len)) + if (environ[i][len] == '=') + env_goodbye(i); +} + +int env_unset(s) char *s; +{ + if (!env_isinit) if (!env_init()) return 0; + env_unsetlen(s,str_len(s)); + return 1; +} + +static int env_add(s) char *s; +{ + char *t; + t = env_findeq(s); + if (t) env_unsetlen(s,t - s); + if (en == ea) + { + ea += 30; + if (!alloc_re(&environ,(en + 1) * sizeof(char *),(ea + 1) * sizeof(char *))) + { ea = en; return 0; } + } + environ[en++] = s; + environ[en] = 0; + return 1; +} + +int env_put(s) char *s; +{ + char *u; + if (!env_isinit) if (!env_init()) return 0; + u = alloc(str_len(s) + 1); + if (!u) return 0; + str_copy(u,s); + if (!env_add(u)) { alloc_free(u); return 0; } + return 1; +} + +int env_put2(s,t) char *s; char *t; +{ + char *u; + int slen; + if (!env_isinit) if (!env_init()) return 0; + slen = str_len(s); + u = alloc(slen + str_len(t) + 2); + if (!u) return 0; + str_copy(u,s); + u[slen] = '='; + str_copy(u + slen + 1,t); + if (!env_add(u)) { alloc_free(u); return 0; } + return 1; +} + +int env_init() +{ + char **newenviron; + int i; + for (en = 0;environ[en];++en) ; + ea = en + 10; + newenviron = (char **) alloc((ea + 1) * sizeof(char *)); + if (!newenviron) return 0; + for (en = 0;environ[en];++en) + { + newenviron[en] = alloc(str_len(environ[en]) + 1); + if (!newenviron[en]) + { + for (i = 0;i < en;++i) alloc_free(newenviron[i]); + alloc_free(newenviron); + return 0; + } + str_copy(newenviron[en],environ[en]); + } + newenviron[en] = 0; + environ = newenviron; + env_isinit = 1; + return 1; +} diff --git a/env.h b/env.h @@ -0,0 +1,17 @@ +#ifndef ENV_H +#define ENV_H + +extern int env_isinit; + +extern int env_init(); +extern int env_put(); +extern int env_put2(); +extern int env_unset(); +extern /*@null@*/char *env_get(); +extern char *env_pick(); +extern void env_clear(); +extern char *env_findeq(); + +extern char **environ; + +#endif diff --git a/envelopes.5 b/envelopes.5 @@ -0,0 +1,231 @@ +.TH envelopes 5 +.SH "NAME" +envelopes \- sender/recipient lists attached to messages +.SH "INTRODUCTION" +Electronic mail messages are delivered in +.IR envelopes . + +An envelope lists a +.I sender +and one or more +.IR recipients . +Usually these +envelope addresses are the same +as the addresses listed in the message header: + +.EX + (envelope) from djb to root +.br + From: djb +.br + To: root +.EE + +In more complicated situations, though, +the envelope addresses may differ from the header addresses. +.SH "ENVELOPE EXAMPLES" +When a message is delivered to +several people at different locations, +it is first photocopied +and placed into several envelopes: + +.EX + (envelope) from djb to root +.br + From: djb Copy #1 of message +.br + To: root, god@brl.mil +.EE + +.EX + (envelope) from djb to god@brl.mil +.br + From: djb Copy #2 of message +.br + To: root, god@brl.mil +.EE + +When a message is delivered +to several people at the same location, +the sender doesn't have to photocopy it. +He can instead stuff it into +one envelope with several addresses; +the recipients will make the photocopy: + +.EX + (envelope) from djb to god@brl.mil, angel@brl.mil +.br + From: djb +.br + To: god@brl.mil, angel@brl.mil, joe, frde +.EE + +Bounced mail is sent back to the envelope sender address. +The bounced mail doesn't list an envelope sender, +so bounce loops are impossible: + +.EX + (envelope) from <> to djb +.br + From: MAILER-DAEMON +.br + To: djb +.br + Subject: unknown user frde +.EE + +The recipient of a message may make another copy +and forward it in a new envelope: + +.EX + (envelope) from djb to joe +.br + From: djb Original message +.br + To: joe +.EE + +.EX + (envelope) from joe to fred +.br + From: djb Forwarded message +.br + To: joe +.EE + +A mailing list works almost the same way: + +.EX + (envelope) from djb to sos-list +.br + From: djb Original message +.br + To: sos-list +.EE + +.EX + (envelope) from sos-owner to god@brl.mil +.br + From: djb Forwarded message +.br + To: sos-list to recipient #1 +.EE + +.EX + (envelope) from sos-owner to frde +.br + From: djb Forwarded message +.br + To: sos-list to recipient #2 +.EE + +Notice that the mailing list is set up +to replace the envelope sender with something new, +.BR sos-owner . +So bounces will come back to +.BR sos-owner : + +.EX + (envelope) from <> to sos-owner +.br + From: MAILER-DAEMON +.br + To: sos-owner +.br + Subject: unknown user frde +.EE + +It's a good idea to set up an extra address, +.BR sos-owner , +like this: +the original envelope sender (\fBdjb\fP) +has no way to fix bad +.B sos-list +addresses, +and of course bounces must not be sent to +.B sos-list +itself. +.SH "HOW ENVELOPE ADDRESSES ARE STORED" +Envelope sender and envelope recipient addresses +are transmitted and recorded in several ways. + +When a user injects mail through +.BR qmail-inject , +he can supply a +.B Return-Path +line or a +.B \-f +option for the envelope sender; +by default the envelope sender is his login name. +The envelope recipient addresses can be taken +from the command line or from various header fields, +depending on the options to +.BR qmail-inject . +Similar comments apply to +.BR sendmail . + +When a message is transferred from one machine to another through SMTP, +the envelope sender is given in a +.B MAIL FROM +command, +the envelope recipients are given in +.B RCPT TO +commands, +and the message is supplied separately by a +.B DATA +command. + +When a message is delivered by +.B qmail +to a single local recipient, +.B qmail-local +records the recipient in +.B Delivered-To +and the envelope sender in +.BR Return-Path . +It uses +.B Delivered-To +to detect mail forwarding loops. + +.B sendmail +normally records the envelope sender in +.BR Return-Path . +It does not record envelope recipient addresses, +on the theory that they are redundant: +you received the mail, +so you must have been one of the envelope recipients. + +Note that, +if the header doesn't have any recipient addresses, +.B sendmail +will move envelope recipient addresses back into the header. +This situation occurs if all addresses were originally listed as +.BR Bcc , +since +.B Bcc +is automatically removed. +When +.B sendmail +sees this, it creates a new +.B Apparently-To +header field with the envelope recipient addresses. +This has the strange effect that each blind-carbon-copy recipient will see +a list of all recipients on the same machine. + +When a message is stored in +.B mbox +format, +the envelope sender is recorded at the top of the message +as a UUCP-style +.B From +(no colon) line. +Note that this line is less reliable than the +.B Return-Path +line added by +.B qmail-local +or +.B sendmail\fP. +.SH "SEE ALSO" +qmail-header(5), +qmail-local(8), +qmail-inject(8) diff --git a/envread.c b/envread.c @@ -0,0 +1,30 @@ +#include "env.h" +#include "str.h" + +extern /*@null@*/char *env_get(s) +char *s; +{ + int i; + unsigned int slen; + char *envi; + + slen = str_len(s); + for (i = 0;envi = environ[i];++i) + if ((!str_diffn(s,envi,slen)) && (envi[slen] == '=')) + return envi + slen + 1; + return 0; +} + +extern char *env_pick() +{ + return environ[0]; +} + +extern char *env_findeq(s) +char *s; +{ + for (;*s;++s) + if (*s == '=') + return s; + return 0; +} diff --git a/error.3 b/error.3 @@ -0,0 +1,45 @@ +.TH error 3 +.SH NAME +error \- syscall error codes +.SH SYNTAX +.B #include <error.h> + +extern int \fBerrno\fP; + +extern int \fBerror_intr\fP; +.br +extern int \fBerror_nomem\fP; +.br +extern int \fBerror_noent\fP; +.br +extern int \fBerror_txtbsy\fP; +.br +extern int \fBerror_io\fP; +.br +extern int \fBerror_exist\fP; +.br +extern int \fBerror_timeout\fP; +.br +extern int \fBerror_inprogress\fP; +.br +extern int \fBerror_wouldblock\fP; +.br +extern int \fBerror_again\fP; +.br +extern int \fBerror_pipe\fP; +.br +extern int \fBerror_perm\fP; +.br +extern int \fBerror_acces\fP; +.SH DESCRIPTION +UNIX syscalls provide detailed error codes in the +.B errno +variable. +The +.B error +library provides portable names for a variety of possible +.B errno +values. +.SH "SEE ALSO" +error_str(3), +error_temp(3) diff --git a/error.c b/error.c @@ -0,0 +1,95 @@ +#include <errno.h> +#include "error.h" + +/* warning: as coverage improves here, should update error_{str,temp} */ + +int error_intr = +#ifdef EINTR +EINTR; +#else +-1; +#endif + +int error_nomem = +#ifdef ENOMEM +ENOMEM; +#else +-2; +#endif + +int error_noent = +#ifdef ENOENT +ENOENT; +#else +-3; +#endif + +int error_txtbsy = +#ifdef ETXTBSY +ETXTBSY; +#else +-4; +#endif + +int error_io = +#ifdef EIO +EIO; +#else +-5; +#endif + +int error_exist = +#ifdef EEXIST +EEXIST; +#else +-6; +#endif + +int error_timeout = +#ifdef ETIMEDOUT +ETIMEDOUT; +#else +-7; +#endif + +int error_inprogress = +#ifdef EINPROGRESS +EINPROGRESS; +#else +-8; +#endif + +int error_wouldblock = +#ifdef EWOULDBLOCK +EWOULDBLOCK; +#else +-9; +#endif + +int error_again = +#ifdef EAGAIN +EAGAIN; +#else +-10; +#endif + +int error_pipe = +#ifdef EPIPE +EPIPE; +#else +-11; +#endif + +int error_perm = +#ifdef EPERM +EPERM; +#else +-12; +#endif + +int error_acces = +#ifdef EACCES +EACCES; +#else +-13; +#endif diff --git a/error.h b/error.h @@ -0,0 +1,23 @@ +#ifndef ERROR_H +#define ERROR_H + +extern int errno; + +extern int error_intr; +extern int error_nomem; +extern int error_noent; +extern int error_txtbsy; +extern int error_io; +extern int error_exist; +extern int error_timeout; +extern int error_inprogress; +extern int error_wouldblock; +extern int error_again; +extern int error_pipe; +extern int error_perm; +extern int error_acces; + +extern char *error_str(); +extern int error_temp(); + +#endif diff --git a/error_str.3 b/error_str.3 @@ -0,0 +1,19 @@ +.TH error_str 3 +.SH NAME +error_str \- names for syscall error codes +.SH SYNTAX +.B #include <error.h> + +char *\fBerror_str\fP(\fIe\fR); + +int \fIe\fR; +.SH DESCRIPTION +.B error_str +returns a printable string describing syscall error code +.IR e . +Normally +.I e +is +.BR errno . +.SH "SEE ALSO" +error(3) diff --git a/error_str.c b/error_str.c @@ -0,0 +1,276 @@ +#include <errno.h> +#include "error.h" + +#define X(e,s) if (i == e) return s; + +char *error_str(i) +int i; +{ + X(0,"no error") + X(error_intr,"interrupted system call") + X(error_nomem,"out of memory") + X(error_noent,"file does not exist") + X(error_txtbsy,"text busy") + X(error_io,"input/output error") + X(error_exist,"file already exists") + X(error_timeout,"timed out") + X(error_inprogress,"operation in progress") + X(error_again,"temporary failure") + X(error_wouldblock,"input/output would block") + X(error_pipe,"broken pipe") + X(error_perm,"permission denied") + X(error_acces,"access denied") +#ifdef ESRCH + X(ESRCH,"no such process") +#endif +#ifdef ENXIO + X(ENXIO,"device not configured") +#endif +#ifdef E2BIG + X(E2BIG,"argument list too long") +#endif +#ifdef ENOEXEC + X(ENOEXEC,"exec format error") +#endif +#ifdef EBADF + X(EBADF,"file descriptor not open") +#endif +#ifdef ECHILD + X(ECHILD,"no child processes") +#endif +#ifdef EDEADLK + X(EDEADLK,"operation would cause deadlock") +#endif +#ifdef EFAULT + X(EFAULT,"bad address") +#endif +#ifdef ENOTBLK + X(ENOTBLK,"not a block device") +#endif +#ifdef EBUSY + X(EBUSY,"device busy") +#endif +#ifdef EXDEV + X(EXDEV,"cross-device link") +#endif +#ifdef ENODEV + X(ENODEV,"device does not support operation") +#endif +#ifdef ENOTDIR + X(ENOTDIR,"not a directory") +#endif +#ifdef EISDIR + X(EISDIR,"is a directory") +#endif +#ifdef EINVAL + X(EINVAL,"invalid argument") +#endif +#ifdef ENFILE + X(ENFILE,"system cannot open more files") +#endif +#ifdef EMFILE + X(EMFILE,"process cannot open more files") +#endif +#ifdef ENOTTY + X(ENOTTY,"not a tty") +#endif +#ifdef EFBIG + X(EFBIG,"file too big") +#endif +#ifdef ENOSPC + X(ENOSPC,"out of disk space") +#endif +#ifdef ESPIPE + X(ESPIPE,"unseekable descriptor") +#endif +#ifdef EROFS + X(EROFS,"read-only file system") +#endif +#ifdef EMLINK + X(EMLINK,"too many links") +#endif +#ifdef EDOM + X(EDOM,"input out of range") +#endif +#ifdef ERANGE + X(ERANGE,"output out of range") +#endif +#ifdef EALREADY + X(EALREADY,"operation already in progress") +#endif +#ifdef ENOTSOCK + X(ENOTSOCK,"not a socket") +#endif +#ifdef EDESTADDRREQ + X(EDESTADDRREQ,"destination address required") +#endif +#ifdef EMSGSIZE + X(EMSGSIZE,"message too long") +#endif +#ifdef EPROTOTYPE + X(EPROTOTYPE,"incorrect protocol type") +#endif +#ifdef ENOPROTOOPT + X(ENOPROTOOPT,"protocol not available") +#endif +#ifdef EPROTONOSUPPORT + X(EPROTONOSUPPORT,"protocol not supported") +#endif +#ifdef ESOCKTNOSUPPORT + X(ESOCKTNOSUPPORT,"socket type not supported") +#endif +#ifdef EOPNOTSUPP + X(EOPNOTSUPP,"operation not supported") +#endif +#ifdef EPFNOSUPPORT + X(EPFNOSUPPORT,"protocol family not supported") +#endif +#ifdef EAFNOSUPPORT + X(EAFNOSUPPORT,"address family not supported") +#endif +#ifdef EADDRINUSE + X(EADDRINUSE,"address already used") +#endif +#ifdef EADDRNOTAVAIL + X(EADDRNOTAVAIL,"address not available") +#endif +#ifdef ENETDOWN + X(ENETDOWN,"network down") +#endif +#ifdef ENETUNREACH + X(ENETUNREACH,"network unreachable") +#endif +#ifdef ENETRESET + X(ENETRESET,"network reset") +#endif +#ifdef ECONNABORTED + X(ECONNABORTED,"connection aborted") +#endif +#ifdef ECONNRESET + X(ECONNRESET,"connection reset") +#endif +#ifdef ENOBUFS + X(ENOBUFS,"out of buffer space") +#endif +#ifdef EISCONN + X(EISCONN,"already connected") +#endif +#ifdef ENOTCONN + X(ENOTCONN,"not connected") +#endif +#ifdef ESHUTDOWN + X(ESHUTDOWN,"socket shut down") +#endif +#ifdef ETOOMANYREFS + X(ETOOMANYREFS,"too many references") +#endif +#ifdef ECONNREFUSED + X(ECONNREFUSED,"connection refused") +#endif +#ifdef ELOOP + X(ELOOP,"symbolic link loop") +#endif +#ifdef ENAMETOOLONG + X(ENAMETOOLONG,"file name too long") +#endif +#ifdef EHOSTDOWN + X(EHOSTDOWN,"host down") +#endif +#ifdef EHOSTUNREACH + X(EHOSTUNREACH,"host unreachable") +#endif +#ifdef ENOTEMPTY + X(ENOTEMPTY,"directory not empty") +#endif +#ifdef EPROCLIM + X(EPROCLIM,"too many processes") +#endif +#ifdef EUSERS + X(EUSERS,"too many users") +#endif +#ifdef EDQUOT + X(EDQUOT,"disk quota exceeded") +#endif +#ifdef ESTALE + X(ESTALE,"stale NFS file handle") +#endif +#ifdef EREMOTE + X(EREMOTE,"too many levels of remote in path") +#endif +#ifdef EBADRPC + X(EBADRPC,"RPC structure is bad") +#endif +#ifdef ERPCMISMATCH + X(ERPCMISMATCH,"RPC version mismatch") +#endif +#ifdef EPROGUNAVAIL + X(EPROGUNAVAIL,"RPC program unavailable") +#endif +#ifdef EPROGMISMATCH + X(EPROGMISMATCH,"program version mismatch") +#endif +#ifdef EPROCUNAVAIL + X(EPROCUNAVAIL,"bad procedure for program") +#endif +#ifdef ENOLCK + X(ENOLCK,"no locks available") +#endif +#ifdef ENOSYS + X(ENOSYS,"system call not available") +#endif +#ifdef EFTYPE + X(EFTYPE,"bad file type") +#endif +#ifdef EAUTH + X(EAUTH,"authentication error") +#endif +#ifdef ENEEDAUTH + X(ENEEDAUTH,"not authenticated") +#endif +#ifdef ENOSTR + X(ENOSTR,"not a stream device") +#endif +#ifdef ETIME + X(ETIME,"timer expired") +#endif +#ifdef ENOSR + X(ENOSR,"out of stream resources") +#endif +#ifdef ENOMSG + X(ENOMSG,"no message of desired type") +#endif +#ifdef EBADMSG + X(EBADMSG,"bad message type") +#endif +#ifdef EIDRM + X(EIDRM,"identifier removed") +#endif +#ifdef ENONET + X(ENONET,"machine not on network") +#endif +#ifdef ERREMOTE + X(ERREMOTE,"object not local") +#endif +#ifdef ENOLINK + X(ENOLINK,"link severed") +#endif +#ifdef EADV + X(EADV,"advertise error") +#endif +#ifdef ESRMNT + X(ESRMNT,"srmount error") +#endif +#ifdef ECOMM + X(ECOMM,"communication error") +#endif +#ifdef EPROTO + X(EPROTO,"protocol error") +#endif +#ifdef EMULTIHOP + X(EMULTIHOP,"multihop attempted") +#endif +#ifdef EREMCHG + X(EREMCHG,"remote address changed") +#endif + return "unknown error"; +} diff --git a/error_temp.3 b/error_temp.3 @@ -0,0 +1,27 @@ +.TH error_temp 3 +.SH NAME +error_temp \- identify soft syscall error codes +.SH SYNTAX +.B #include <error.h> + +int \fBerror_temp\fP(\fIe\fR); + +int \fIe\fR; +.SH DESCRIPTION +.B error_temp +returns 1 if syscall error code +.I e +is a soft error, 0 if it is a hard error. +Normally +.I e +is +.BR errno . + +A hard error is persistent: +file not found, read-only file system, symbolic link loop, etc. + +A soft error is usually transient: +out of memory, out of disk space, I/O error, disk quota exceeded, +connection refused, host unreachable, etc. +.SH "SEE ALSO" +error(3) diff --git a/error_temp.c b/error_temp.c @@ -0,0 +1,80 @@ +#include <errno.h> +#include "error.h" + +#define X(n) if (e == n) return 1; + +int error_temp(e) +int e; +{ + X(error_intr) + X(error_nomem) + X(error_txtbsy) + X(error_io) + X(error_timeout) + X(error_wouldblock) + X(error_again) +#ifdef EDEADLK + X(EDEADLK) +#endif +#ifdef EBUSY + X(EBUSY) +#endif +#ifdef ENFILE + X(ENFILE) +#endif +#ifdef EMFILE + X(EMFILE) +#endif +#ifdef EFBIG + X(EFBIG) +#endif +#ifdef ENOSPC + X(ENOSPC) +#endif +#ifdef ENETDOWN + X(ENETDOWN) +#endif +#ifdef ENETUNREACH + X(ENETUNREACH) +#endif +#ifdef ENETRESET + X(ENETRESET) +#endif +#ifdef ECONNABORTED + X(ECONNABORTED) +#endif +#ifdef ECONNRESET + X(ECONNRESET) +#endif +#ifdef ENOBUFS + X(ENOBUFS) +#endif +#ifdef ETOOMANYREFS + X(ETOOMANYREFS) +#endif +#ifdef ECONNREFUSED + X(ECONNREFUSED) +#endif +#ifdef EHOSTDOWN + X(EHOSTDOWN) +#endif +#ifdef EHOSTUNREACH + X(EHOSTUNREACH) +#endif +#ifdef EPROCLIM + X(EPROCLIM) +#endif +#ifdef EUSERS + X(EUSERS) +#endif +#ifdef EDQUOT + X(EDQUOT) +#endif +#ifdef ESTALE + X(ESTALE) +#endif +#ifdef ENOLCK + X(ENOLCK) +#endif + return 0; +} diff --git a/exit.h b/exit.h @@ -0,0 +1,6 @@ +#ifndef EXIT_H +#define EXIT_H + +extern void _exit(); + +#endif diff --git a/extra.h b/extra.h @@ -0,0 +1,7 @@ +#ifndef EXTRA_H +#define EXTRA_H + +#define QUEUE_EXTRA "" +#define QUEUE_EXTRALEN 0 + +#endif diff --git a/fd.h b/fd.h @@ -0,0 +1,7 @@ +#ifndef FD_H +#define FD_H + +extern int fd_copy(); +extern int fd_move(); + +#endif diff --git a/fd_copy.3 b/fd_copy.3 @@ -0,0 +1,44 @@ +.TH fd_copy 3 +.SH NAME +fd_copy \- duplicate a descriptor +.SH SYNTAX +.B #include <fd.h> + +int \fBfd_copy\fP(\fIto\fR,\fIfrom\fR); + +int \fIto\fR; +.br +int \fIfrom\fR; +.SH DESCRIPTION +.B fd_copy +copies +descriptor +.I from +to descriptor +.IR to . +If +.I to +was already open, +.B fd_copy +closes it. +.B fd_copy +always leaves +.I from +intact; +if +.I to +and +.I from +are the same number, +.B fd_copy +does nothing. + +.B fd_copy +returns 0 on success, -1 on error. +.B fd_copy +does not guarantee that +.I to +will remain open, if it was open, in case of error. +.SH "SEE ALSO" +dup(2), +fd_move(3) diff --git a/fd_copy.c b/fd_copy.c @@ -0,0 +1,13 @@ +#include <fcntl.h> +#include "fd.h" + +int fd_copy(to,from) +int to; +int from; +{ + if (to == from) return 0; + if (fcntl(from,F_GETFL,0) == -1) return -1; + close(to); + if (fcntl(from,F_DUPFD,to) == -1) return -1; + return 0; +} diff --git a/fd_move.3 b/fd_move.3 @@ -0,0 +1,41 @@ +.TH fd_move 3 +.SH NAME +fd_move \- renumber a descriptor +.SH SYNTAX +.B #include <fd.h> + +int \fBfd_move\fP(\fIto\fR,\fIfrom\fR); + +int \fIto\fR; +.br +int \fIfrom\fR; +.SH DESCRIPTION +.B fd_move +moves +descriptor +.I from +to descriptor +.IR to . +If +.I to +was already open, +.B fd_move +closes it. +If the move is successful, +.B fd_move +closes +.IR from . +Exception: +if +.I to +and +.I from +are the same number, +.B fd_move +does nothing. + +.B fd_move +returns 0 on success, -1 on error. +.SH "SEE ALSO" +dup(2), +fd_copy(3) diff --git a/fd_move.c b/fd_move.c @@ -0,0 +1,11 @@ +#include "fd.h" + +int fd_move(to,from) +int to; +int from; +{ + if (to == from) return 0; + if (fd_copy(to,from) == -1) return -1; + close(from); + return 0; +} diff --git a/fifo.c b/fifo.c @@ -0,0 +1,10 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include "hasmkffo.h" +#include "fifo.h" + +#ifdef HASMKFIFO +int fifo_make(fn,mode) char *fn; int mode; { return mkfifo(fn,mode); } +#else +int fifo_make(fn,mode) char *fn; int mode; { return mknod(fn,S_IFIFO | mode,0); } +#endif diff --git a/fifo.h b/fifo.h @@ -0,0 +1,6 @@ +#ifndef FIFO_H +#define FIFO_H + +extern int fifo_make(); + +#endif diff --git a/fifo_make.3 b/fifo_make.3 @@ -0,0 +1,24 @@ +.TH fifo_make 3 +.SH NAME +fifo_make \- create a named pipe +.SH SYNTAX +.B #include <fifo.h> + +int \fBfifo_make\fP(\fIfn\fR,\fImode\fR); + +char *\fIfn\fR; +.br +int \fImode\fR; +.SH DESCRIPTION +.B fifo_make +creates a new named pipe +with name +.I fn +and mode +.I mode +(modified by the process umask). + +.B fifo_make +returns 0 on success, -1 on error. +.SH "SEE ALSO" +mkfifo(2) diff --git a/find-systype.sh b/find-systype.sh @@ -0,0 +1,144 @@ +# oper-:arch-:syst-:chip-:kern- +# oper = operating system type; e.g., sunos-4.1.4 +# arch = machine language; e.g., sparc +# syst = which binaries can run; e.g., sun4 +# chip = chip model; e.g., micro-2-80 +# kern = kernel version; e.g., sun4m +# dependence: arch --- chip +# \ \ +# oper --- syst --- kern +# so, for example, syst is interpreted in light of oper, but chip is not. +# anyway, no slashes, no extra colons, no uppercase letters. +# the point of the extra -'s is to ease parsing: can add hierarchies later. +# e.g., *:i386-*:*:pentium-*:* would handle pentium-100 as well as pentium, +# and i386-486 (486s do have more instructions, you know) as well as i386. +# the idea here is to include ALL useful available information. + +exec 2>/dev/null +sys="`uname -s | tr '/:[A-Z]' '..[a-z]'`" +if [ x"$sys" != x ] +then + unamer="`uname -r | tr /: ..`" + unamem="`uname -m | tr /: ..`" + unamev="`uname -v | tr /: ..`" + + case "$sys" in + bsd.os) + # in bsd 4.4, uname -v does not have useful info. + # in bsd 4.4, uname -m is arch, not chip. + oper="$sys-$unamer" + arch="$unamem" + syst="" + chip="`sysctl -n hw.model`" + kern="" + ;; + freebsd) + # see above about bsd 4.4 + oper="$sys-$unamer" + arch="$unamem" + syst="" + chip="`sysctl -n hw.model`" # hopefully + kern="" + ;; + netbsd) + # see above about bsd 4.4 + oper="$sys-$unamer" + arch="$unamem" + syst="" + chip="`sysctl -n hw.model`" # hopefully + kern="" + ;; + linux) + # as in bsd 4.4, uname -v does not have useful info. + oper="$sys-$unamer" + syst="" + chip="$unamem" + kern="" + case "$chip" in + i386|i486|i586|i686) + arch="i386" + ;; + alpha) + arch="alpha" + ;; + esac + ;; + aix) + # naturally IBM has to get uname -r and uname -v backwards. dorks. + oper="$sys-$unamev-$unamer" + arch="`arch | tr /: ..`" + syst="" + chip="$unamem" + kern="" + ;; + sunos) + oper="$sys-$unamer-$unamev" + arch="`(uname -p || mach) | tr /: ..`" + syst="`arch | tr /: ..`" + chip="$unamem" # this is wrong; is there any way to get the real info? + kern="`arch -k | tr /: ..`" + ;; + unix_sv) + oper="$sys-$unamer-$unamev" + arch="`uname -m`" + syst="" + chip="$unamem" + kern="" + ;; + *) + oper="$sys-$unamer-$unamev" + arch="`arch | tr /: ..`" + syst="" + chip="$unamem" + kern="" + ;; + esac +else + $CC -c trycpp.c + $LD -o trycpp trycpp.o + case `./trycpp` in + nextstep) + oper="nextstep-`hostinfo | sed -n 's/^[ ]*NeXT Mach \([^:]*\):.*$/\1/p'`" + arch="`hostinfo | sed -n 's/^Processor type: \(.*\) (.*)$/\1/p' | tr /: ..`" + syst="" + chip="`hostinfo | sed -n 's/^Processor type: .* (\(.*\))$/\1/p' | tr ' /:' '...'`" + kern="" + ;; + *) + oper="unknown" + arch="" + syst="" + chip="" + kern="" + ;; + esac + rm -f trycpp.o trycpp +fi + +case "$chip" in +80486) + # let's try to be consistent here. (BSD/OS) + chip=i486 + ;; +i486DX) + # respect the hyphen hierarchy. (FreeBSD) + chip=i486-dx + ;; +i486.DX2) + # respect the hyphen hierarchy. (FreeBSD) + chip=i486-dx2 + ;; +Intel.586) + # no, you nitwits, there is no such chip. (NeXTStep) + chip=pentium + ;; +i586) + # no, you nitwits, there is no such chip. (Linux) + chip=pentium + ;; +i686) + # STOP SAYING THAT! (Linux) + chip=ppro +esac + +echo "$oper-:$arch-:$syst-:$chip-:$kern-" | tr ' [A-Z]' '.[a-z]' diff --git a/fmt.h b/fmt.h @@ -0,0 +1,25 @@ +#ifndef FMT_H +#define FMT_H + +#define FMT_ULONG 40 /* enough space to hold 2^128 - 1 in decimal, plus \0 */ +#define FMT_LEN ((char *) 0) /* convenient abbreviation */ + +extern unsigned int fmt_uint(); +extern unsigned int fmt_uint0(); +extern unsigned int fmt_xint(); +extern unsigned int fmt_nbbint(); +extern unsigned int fmt_ushort(); +extern unsigned int fmt_xshort(); +extern unsigned int fmt_nbbshort(); +extern unsigned int fmt_ulong(); +extern unsigned int fmt_xlong(); +extern unsigned int fmt_nbblong(); + +extern unsigned int fmt_plusminus(); +extern unsigned int fmt_minus(); +extern unsigned int fmt_0x(); + +extern unsigned int fmt_str(); +extern unsigned int fmt_strn(); + +#endif diff --git a/fmt_str.c b/fmt_str.c @@ -0,0 +1,12 @@ +#include "fmt.h" + +unsigned int fmt_str(s,t) +register char *s; register char *t; +{ + register unsigned int len; + char ch; + len = 0; + if (s) { while (ch = t[len]) s[len++] = ch; } + else while (t[len]) len++; + return len; +} diff --git a/fmt_strn.c b/fmt_strn.c @@ -0,0 +1,12 @@ +#include "fmt.h" + +unsigned int fmt_strn(s,t,n) +register char *s; register char *t; register unsigned int n; +{ + register unsigned int len; + char ch; + len = 0; + if (s) { while (n-- && (ch = t[len])) s[len++] = ch; } + else while (n-- && t[len]) len++; + return len; +} diff --git a/fmt_uint.c b/fmt_uint.c @@ -0,0 +1,6 @@ +#include "fmt.h" + +unsigned int fmt_uint(s,u) register char *s; register unsigned int u; +{ + register unsigned long l; l = u; return fmt_ulong(s,l); +} diff --git a/fmt_uint0.c b/fmt_uint0.c @@ -0,0 +1,10 @@ +#include "fmt.h" + +unsigned int fmt_uint0(s,u,n) char *s; unsigned int u; unsigned int n; +{ + unsigned int len; + len = fmt_uint(FMT_LEN,u); + while (len < n) { if (s) *s++ = '0'; ++len; } + if (s) fmt_uint(s,u); + return len; +} diff --git a/fmt_ulong.c b/fmt_ulong.c @@ -0,0 +1,13 @@ +#include "fmt.h" + +unsigned int fmt_ulong(s,u) register char *s; register unsigned long u; +{ + register unsigned int len; register unsigned long q; + len = 1; q = u; + while (q > 9) { ++len; q /= 10; } + if (s) { + s += len; + do { *--s = '0' + (u % 10); u /= 10; } while(u); /* handles u == 0 */ + } + return len; +} diff --git a/fmtqfn.c b/fmtqfn.c @@ -0,0 +1,24 @@ +#include "fmtqfn.h" +#include "fmt.h" +#include "auto_split.h" + +unsigned int fmtqfn(s,dirslash,id,flagsplit) +char *s; +char *dirslash; +unsigned long id; +int flagsplit; +{ + unsigned int len; + unsigned int i; + + len = 0; + i = fmt_str(s,dirslash); len += i; if (s) s += i; + if (flagsplit) + { + i = fmt_ulong(s,id % auto_split); len += i; if (s) s += i; + i = fmt_str(s,"/"); len += i; if (s) s += i; + } + i = fmt_ulong(s,id); len += i; if (s) s += i; + if (s) *s++ = 0; ++len; + return len; +} diff --git a/fmtqfn.h b/fmtqfn.h @@ -0,0 +1,8 @@ +#ifndef FMTQFN_H +#define FMTQFN_H + +extern unsigned int fmtqfn(); + +#define FMTQFN 40 /* maximum space needed, if len(dirslash) <= 10 */ + +#endif diff --git a/forgeries.7 b/forgeries.7 @@ -0,0 +1,104 @@ +.TH forgeries 7 +.SH "NAME" +forgeries \- how easy it is to forge mail +.SH "SUMMARY" +An electronic mail message can easily be forged. +Almost everything in it, +including the return address, +is completely under the control of the sender. + +An electronic mail message can be manually traced to its origin +if (1) all system administrators of intermediate machines +are both cooperative and competent, +(2) the sender did not break low-level TCP/IP security, +and +(3) all intermediate machines are secure. + +Users of +.I cryptography +can automatically ensure the integrity and secrecy +of their mail messages, as long as +the sending and receiving machines are secure. +.SH "FORGERIES" +Like postal mail, +electronic mail can be created entirely at the whim of the sender. +.BR From , +.BR Sender , +.BR Return-Path , +and +.BR Message-ID +can all contain whatever information the sender wants. + +For example, if you inject a message through +.B sendmail +or +.B qmail-inject +or +.BR SMTP , +you can simply type in a +.B From +field. +In fact, +.B qmail-inject +lets you set up +.BR MAILUSER , +.BR MAILHOST , +and +.B MAILNAME +environment variables +to produce your desired +.B From +field on every message. +.SH "TRACING FORGERIES" +Like postal mail, +electronic mail is postmarked when it is sent. +Each machine that receives an electronic mail message +adds a +.B Received +line to the top. + +A modern +.B Received +line contains quite a bit of information. +In conjunction with the machine's logs, +it lets a competent system administrator +determine where the machine received the message from, +as long as the sender did not break low-level TCP/IP security +or security on that machine. + +Large multi-user machines often come with inadequate logging software. +Fortunately, a system administrator can easily obtain a copy of a +931/1413/Ident/TAP server, such as +.BR pidentd . +Unfortunately, +many incompetent system administrators fail to do this, +and are thus unable to figure out which local user +was responsible for generating a message. + +If all intermediate system administrators are competent, +and the sender did not break machine security or low-level TCP/IP security, +it is possible to trace a message backwards. +Unfortunately, some traces are stymied by intermediate system +administrators who are uncooperative or untrustworthy. +.SH "CRYPTOGRAPHY" +The sender of a mail message may place his message into a +.I cryptographic +envelope stamped with his seal. +Strong cryptography guarantees that any two messages with the same seal +were sent by the same cryptographic entity: +perhaps a single person, perhaps a group of cooperating people, +but in any case somebody who knows a secret originally held +only by the creator of the seal. +The seal is called a +.I public key\fR. + +Unfortunately, the creator of the seal is often an insecure machine, +or an untrustworthy central agency, +but most of the time seals are kept secure. + +One popular cryptographic program is +.BR pgp . +.SH "SEE ALSO" +pgp(1), +identd(8), +qmail-header(8) diff --git a/fork.h1 b/fork.h1 @@ -0,0 +1,7 @@ +#ifndef FORK_H +#define FORK_H + +extern int fork(); +#define vfork fork + +#endif diff --git a/fork.h2 b/fork.h2 @@ -0,0 +1,7 @@ +#ifndef FORK_H +#define FORK_H + +extern int fork(); +extern int vfork(); + +#endif diff --git a/forward.1 b/forward.1 @@ -0,0 +1,24 @@ +.TH forward 1 +.SH NAME +forward \- forward new mail to one or more addresses +.SH SYNOPSIS +in +.BR .qmail : +.B |forward +.I address ... +.SH DESCRIPTION +.B forward +forwards each new mail message to the specified list of addresses. +It is a simple wrapper around +.BR qmail-queue . +It achieves the same results as listing each +.I address +separately in +.BR .qmail , +but it is more programmable since +.I address +can be constructed on the fly. +.SH "SEE ALSO" +dot-qmail(5), +qmail-command(8), +qmail-queue(8) diff --git a/forward.c b/forward.c @@ -0,0 +1,58 @@ +#include "sig.h" +#include "readwrite.h" +#include "exit.h" +#include "env.h" +#include "qmail.h" +#include "stralloc.h" +#include "subfd.h" +#include "substdio.h" + +void die_success() { _exit(0); } +void die_perm(s) char *s; { substdio_putsflush(subfderr,s); _exit(100); } +void die_temp(s) char *s; { substdio_putsflush(subfderr,s); _exit(111); } +void die_nomem() { die_temp("forward: fatal: out of memory\n"); } + +struct qmail qqt; + +int mywrite(fd,buf,len) int fd; char *buf; int len; +{ + qmail_put(&qqt,buf,len); + return len; +} + +substdio ssin; +substdio ssout; +char inbuf[SUBSTDIO_INSIZE]; +char outbuf[16]; + +void main(argc,argv) +int argc; +char **argv; +{ + char *sender; + char *dtline; + + sig_pipeignore(); + + sender = env_get("NEWSENDER"); + if (!sender) die_perm("forward: fatal: NEWSENDER not set\n"); + dtline = env_get("DTLINE"); + if (!dtline) die_perm("forward: fatal: DTLINE not set\n"); + + if (qmail_open(&qqt) == -1) die_temp("forward: fatal: unable to fork\n"); + qmail_puts(&qqt,dtline); + substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf)); + substdio_fdbuf(&ssout,mywrite,-1,outbuf,sizeof(outbuf)); + if (substdio_copy(&ssout,&ssin) != 0) + die_temp("forward: fatal: error while reading message\n"); + substdio_flush(&ssout); + + qmail_from(&qqt,sender); + while (*++argv) qmail_to(&qqt,*argv); + switch(qmail_close(&qqt)) + { + case 0: die_success(); + case QMAIL_TOOLONG: die_perm("forward: fatal: permanent qmail-queue error\n"); + default: die_temp("forward: fatal: temporary qmail-queue error\n"); + } +} diff --git a/gen_alloc.h b/gen_alloc.h @@ -0,0 +1,7 @@ +#ifndef GEN_ALLOC_H +#define GEN_ALLOC_H + +#define GEN_ALLOC_typedef(ta,type,field,len,a) \ + typedef struct ta { type *field; unsigned int len; unsigned int a; } ta; + +#endif diff --git a/gen_allocdefs.h b/gen_allocdefs.h @@ -0,0 +1,34 @@ +#ifndef GEN_ALLOC_DEFS_H +#define GEN_ALLOC_DEFS_H + +#define GEN_ALLOC_ready(ta,type,field,len,a,i,n,x,base,ta_ready) \ +int ta_ready(x,n) register ta *x; register unsigned int n; \ +{ register unsigned int i; \ + if (x->field) { \ + i = x->a; \ + if (n > i) { \ + x->a = base + n + (n >> 3); \ + if (alloc_re(&x->field,i * sizeof(type),x->a * sizeof(type))) return 1; \ + x->a = i; return 0; } \ + return 1; } \ + x->len = 0; \ + return !!(x->field = (type *) alloc((x->a = n) * sizeof(type))); } + +#define GEN_ALLOC_readyplus(ta,type,field,len,a,i,n,x,base,ta_rplus) \ +int ta_rplus(x,n) register ta *x; register unsigned int n; \ +{ register unsigned int i; \ + if (x->field) { \ + i = x->a; n += x->len; \ + if (n > i) { \ + x->a = base + n + (n >> 3); \ + if (alloc_re(&x->field,i * sizeof(type),x->a * sizeof(type))) return 1; \ + x->a = i; return 0; } \ + return 1; } \ + x->len = 0; \ + return !!(x->field = (type *) alloc((x->a = n) * sizeof(type))); } + +#define GEN_ALLOC_append(ta,type,field,len,a,i,n,x,base,ta_rplus,ta_append) \ +int ta_append(x,i) register ta *x; register type *i; \ +{ if (!ta_rplus(x,1)) return 0; x->field[x->len++] = *i; return 1; } + +#endif diff --git a/getln.3 b/getln.3 @@ -0,0 +1,51 @@ +.TH getln 3 +.SH NAME +getln \- read one line of data +.SH SYNTAX +.B #include <getln.h> + +int \fBgetln\fP(&\fIss\fR,&\fIsa\fR,&\fImatch\fR,\fIsep\fR); + +substdio \fIss\fR; +.br +stralloc \fIsa\fR; +.br +int \fImatch\fR; +.br +int \fIsep\fR; +.SH DESCRIPTION +.B getln +reads a line of characters, terminated by a +.I sep +character, +from +.IR ss . +It returns the line in +.I sa +and sets +.I match +to 1. + +If +.B getln +sees end-of-input before it sees +.IR sep , +it returns the partial line in +.I sa +and sets +.I match +to 0. + +.B getln +normally returns 0. +If it runs out of memory, +or encounters an error from +.IR ss , +it returns -1, +setting +.B errno +appropriately. +.SH "SEE ALSO" +stralloc(3), +substdio(3), +getln2(3) diff --git a/getln.c b/getln.c @@ -0,0 +1,20 @@ +#include "substdio.h" +#include "byte.h" +#include "stralloc.h" +#include "getln.h" + +int getln(ss,sa,match,sep) +register substdio *ss; +register stralloc *sa; +int *match; +int sep; +{ + char *cont; + unsigned int clen; + + if (getln2(ss,sa,&cont,&clen,sep) == -1) return -1; + if (!clen) { *match = 0; return 0; } + if (!stralloc_catb(sa,cont,clen)) return -1; + *match = 1; + return 0; +} diff --git a/getln.h b/getln.h @@ -0,0 +1,7 @@ +#ifndef GETLN_H +#define GETLN_H + +extern int getln(); +extern int getln2(); + +#endif diff --git a/getln2.3 b/getln2.3 @@ -0,0 +1,64 @@ +.TH getln2 3 +.SH NAME +getln2 \- read one line of data +.SH SYNTAX +.B #include <getln.h> + +int \fBgetln2\fP(&\fIss\fR,&\fIsa\fR,&\fIcont\fR,&\fIclen\fR,\fIsep\fR); + +substdio \fIss\fR; +.br +stralloc \fIsa\fR; +.br +char *\fIcont\fR; +.br +unsigned int \fIclen\fR; +.br +int \fIsep\fR; +.SH DESCRIPTION +.B getln2 +reads a line of characters, terminated by a +.I sep +character, +from +.IR ss . + +The line is returned in two pieces. +The first piece is stored in +.IR sa . +The second piece is +.IR cont , +a pointer to +.I clen +characters inside the +.I ss +buffer. +The second piece must be copied somewhere else +before +.I ss +is used again. + +If +.B getln2 +sees end-of-input before it sees +.IR sep , +it sets +.I clen +to 0 and does not set +.IR cont . +It puts the partial line into +.IR sa . + +.B getln2 +normally returns 0. +If it runs out of memory, +or encounters an error from +.IR ss , +it returns -1, +setting +.B errno +appropriately. +.SH "SEE ALSO" +stralloc(3), +substdio(3), +getln(3) diff --git a/getln2.c b/getln2.c @@ -0,0 +1,31 @@ +#include "substdio.h" +#include "stralloc.h" +#include "byte.h" +#include "getln.h" + +int getln2(ss,sa,cont,clen,sep) +register substdio *ss; +register stralloc *sa; +/*@out@*/char **cont; +/*@out@*/unsigned int *clen; +int sep; +{ + register char *x; + register unsigned int i; + int n; + + if (!stralloc_ready(sa,0)) return -1; + sa->len = 0; + + for (;;) { + n = substdio_feed(ss); + if (n < 0) return -1; + if (n == 0) { *clen = 0; return 0; } + x = substdio_PEEK(ss); + i = byte_chr(x,n,sep); + if (i < n) { substdio_SEEK(ss,*clen = i + 1); *cont = x; return 0; } + if (!stralloc_readyplus(sa,n)) return -1; + i = sa->len; + sa->len = i + substdio_get(ss,sa->s + i,n); + } +} diff --git a/gfrom.c b/gfrom.c @@ -0,0 +1,10 @@ +#include "str.h" +#include "gfrom.h" + +int gfrom(s,len) +char *s; +int len; +{ + while ((len > 0) && (*s == '>')) { ++s; --len; } + return (len >= 5) && !str_diffn(s,"From ",5); +} diff --git a/gfrom.h b/gfrom.h @@ -0,0 +1,6 @@ +#ifndef GFROM_H +#define GFROM_H + +extern int gfrom(); + +#endif diff --git a/headerbody.c b/headerbody.c @@ -0,0 +1,87 @@ +#include "stralloc.h" +#include "substdio.h" +#include "getln.h" +#include "hfield.h" +#include "headerbody.h" + +static int getsa(ss,sa,match) +substdio *ss; +stralloc *sa; +int *match; +{ + if (!*match) return 0; + if (getln(ss,sa,match,'\n') == -1) return -1; + if (*match) return 1; + if (!sa->len) return 0; + if (!stralloc_append(sa,"\n")) return -1; + return 1; +} + +static stralloc line = {0}; +static stralloc nextline = {0}; + +int headerbody(ss,dohf,hdone,dobl) +substdio *ss; +void (*dohf)(); +void (*hdone)(); +void (*dobl)(); +{ + int match; + int flaglineok; + match = 1; + flaglineok = 0; + for (;;) + { + switch(getsa(ss,&nextline,&match)) + { + case -1: + return -1; + case 0: + if (flaglineok) dohf(&line); + hdone(); + /* no message body; could insert blank line here */ + return 0; + } + if (flaglineok) + { + if ((nextline.s[0] == ' ') || (nextline.s[0] == '\t')) + { + if (!stralloc_cat(&line,&nextline)) return -1; + continue; + } + dohf(&line); + } + if (nextline.len == 1) + { + hdone(); + dobl(&nextline); + break; + } + if (stralloc_starts(&nextline,"From ")) + { + if (!stralloc_copys(&line,"MBOX-Line: ")) return -1; + if (!stralloc_cat(&line,&nextline)) return -1; + } + else + if (hfield_valid(nextline.s,nextline.len)) + { + if (!stralloc_copy(&line,&nextline)) return -1; + } + else + { + hdone(); + if (!stralloc_copys(&line,"\n")) return -1; + dobl(&line); + dobl(&nextline); + break; + } + flaglineok = 1; + } + for (;;) + switch(getsa(ss,&nextline,&match)) + { + case -1: return -1; + case 0: return 0; + case 1: dobl(&nextline); + } +} diff --git a/headerbody.h b/headerbody.h @@ -0,0 +1,6 @@ +#ifndef HEADERBODY_H +#define HEADERBODY_H + +extern int headerbody(); + +#endif diff --git a/hfield.c b/hfield.c @@ -0,0 +1,124 @@ +#include "hfield.h" + +static char *(hname[]) = { + "unknown-header" +, "sender" +, "from" +, "reply-to" +, "to" +, "cc" +, "bcc" +, "date" +, "message-id" +, "subject" +, "resent-sender" +, "resent-from" +, "resent-reply-to" +, "resent-to" +, "resent-cc" +, "resent-bcc" +, "resent-date" +, "resent-message-id" +, "return-receipt-to" +, "errors-to" +, "apparently-to" +, "received" +, "return-path" +, "delivered-to" +, "content-length" +, "content-type" +, "content-transfer-encoding" +, "notice-requested-upon-delivery-to" +, 0 +}; + +static int hmatch(s,len,t) +char *s; +int len; +char *t; +{ + int i; + char ch; + + for (i = 0;ch = t[i];++i) + { + if (i >= len) return 0; + if (ch != s[i]) + { + if (ch == '-') return 0; + if (ch - 32 != s[i]) return 0; + } + } + for (;;) + { + if (i >= len) return 0; + ch = s[i]; + if (ch == ':') return 1; + if ((ch != ' ') && (ch != '\t')) return 0; + ++i; + } +} + +int hfield_known(s,len) +char *s; +int len; +{ + int i; + char *t; + + for (i = 1;t = hname[i];++i) + if (hmatch(s,len,t)) + return i; + return 0; +} + +int hfield_valid(s,len) +char *s; +int len; +{ + int i; + int j; + char ch; + + for (j = 0;j < len;++j) + if (s[j] == ':') + break; + if (j >= len) return 0; + while (j) + { + ch = s[j - 1]; + if ((ch != ' ') && (ch != '\t')) + break; + --j; + } + if (!j) return 0; + + for (i = 0;i < j;++i) + { + ch = s[i]; + if (ch <= 32) return 0; + if (ch >= 127) return 0; + } + return 1; +} + +unsigned int hfield_skipname(s,len) +char *s; +int len; +{ + int i; + char ch; + + for (i = 0;i < len;++i) + if (s[i] == ':') + break; + if (i < len) ++i; + while (i < len) + { + ch = s[i]; + if ((ch != '\t') && (ch != '\n') && (ch != '\r') && (ch != ' ')) + break; + ++i; + } + return i; +} diff --git a/hfield.h b/hfield.h @@ -0,0 +1,37 @@ +#ifndef HFIELD_H +#define HFIELD_H + +extern unsigned int hfield_skipname(); +extern int hfield_known(); +extern int hfield_valid(); + +#define H_SENDER 1 +#define H_FROM 2 +#define H_REPLYTO 3 +#define H_TO 4 +#define H_CC 5 +#define H_BCC 6 +#define H_DATE 7 +#define H_MESSAGEID 8 +#define H_SUBJECT 9 +#define H_R_SENDER 10 +#define H_R_FROM 11 +#define H_R_REPLYTO 12 +#define H_R_TO 13 +#define H_R_CC 14 +#define H_R_BCC 15 +#define H_R_DATE 16 +#define H_R_MESSAGEID 17 +#define H_RETURNRECEIPTTO 18 +#define H_ERRORSTO 19 +#define H_APPARENTLYTO 20 +#define H_RECEIVED 21 +#define H_RETURNPATH 22 +#define H_DELIVEREDTO 23 +#define H_CONTENTLENGTH 24 +#define H_CONTENTTYPE 25 +#define H_CONTENTTRANSFERENCODING 26 +#define H_NOTICEREQUESTEDUPONDELIVERYTO 27 +#define H_NUM 28 + +#endif diff --git a/hostname.c b/hostname.c @@ -0,0 +1,17 @@ +#include "substdio.h" +#include "subfd.h" +#include "readwrite.h" +#include "exit.h" + +char host[256]; + +void main() +{ + host[0] = 0; /* sigh */ + gethostname(host,sizeof(host)); + host[sizeof(host) - 1] = 0; + substdio_puts(subfdoutsmall,host); + substdio_puts(subfdoutsmall,"\n"); + substdio_flush(subfdoutsmall); + _exit(0); +} diff --git a/install.c b/install.c @@ -0,0 +1,167 @@ +#include "substdio.h" +#include "stralloc.h" +#include "getln.h" +#include "readwrite.h" +#include "exit.h" +#include "open.h" +#include "error.h" +#include "strerr.h" +#include "byte.h" +#include "fifo.h" + +stralloc target = {0}; +char *to; + +#define FATAL "install: fatal: " +void nomem() { strerr_die2x(111,FATAL,"out of memory"); } + +char inbuf[SUBSTDIO_INSIZE]; +char outbuf[SUBSTDIO_OUTSIZE]; +substdio ssin; +substdio ssout; + +void doit(line) +stralloc *line; +{ + char *x; + unsigned int xlen; + unsigned int i; + char *type; + char *uidstr; + char *gidstr; + char *modestr; + char *mid; + char *name; + unsigned long uid; + unsigned long gid; + unsigned long mode; + int fdin; + int fdout; + unsigned long zlen; + + x = line->s; xlen = line->len; + + type = x; + i = byte_chr(x,xlen,':'); if (i == xlen) return; + x[i++] = 0; x += i; xlen -= i; + + uidstr = x; + i = byte_chr(x,xlen,':'); if (i == xlen) return; + x[i++] = 0; x += i; xlen -= i; + + gidstr = x; + i = byte_chr(x,xlen,':'); if (i == xlen) return; + x[i++] = 0; x += i; xlen -= i; + + modestr = x; + i = byte_chr(x,xlen,':'); if (i == xlen) return; + x[i++] = 0; x += i; xlen -= i; + + mid = x; + i = byte_chr(x,xlen,':'); if (i == xlen) return; + x[i++] = 0; x += i; xlen -= i; + + name = x; + i = byte_chr(x,xlen,':'); if (i == xlen) return; + x[i++] = 0; x += i; xlen -= i; + + if (!stralloc_copys(&target,to)) nomem(); + if (!stralloc_cats(&target,mid)) nomem(); + if (!stralloc_cats(&target,name)) nomem(); + if (!stralloc_0(&target)) nomem(); + + uid = -1; if (*uidstr) scan_ulong(uidstr,&uid); + gid = -1; if (*gidstr) scan_ulong(gidstr,&gid); + scan_8long(modestr,&mode); + + switch(*type) { + case 'z': + scan_ulong(type + 1,&zlen); + + fdout = open_trunc(target.s); + if (fdout == -1) + strerr_die4sys(111,FATAL,"unable to write ",target.s,": "); + substdio_fdbuf(&ssout,write,fdout,outbuf,sizeof(outbuf)); + + while (zlen--) + if (substdio_put(&ssout,"",1) == -1) + strerr_die4sys(111,FATAL,"unable to write ",target.s,": "); + + if (substdio_flush(&ssout) == -1) + strerr_die4sys(111,FATAL,"unable to write ",target.s,": "); + if (fsync(fdout) == -1) + strerr_die4sys(111,FATAL,"unable to write ",target.s,": "); + close(fdout); + break; + + case 'p': + if (fifo_make(target.s,0700) == -1) + if (errno != error_exist) + strerr_die4sys(111,FATAL,"unable to mkfifo ",target.s,": "); + break; + + case 'd': + if (mkdir(target.s,0700) == -1) + if (errno != error_exist) + strerr_die4sys(111,FATAL,"unable to mkdir ",target.s,": "); + break; + + case 'c': + fdin = open_read(name); + if (fdin == -1) + strerr_die4sys(111,FATAL,"unable to read ",name,": "); + substdio_fdbuf(&ssin,read,fdin,inbuf,sizeof(inbuf)); + + fdout = open_trunc(target.s); + if (fdout == -1) + strerr_die4sys(111,FATAL,"unable to write ",target.s,": "); + substdio_fdbuf(&ssout,write,fdout,outbuf,sizeof(outbuf)); + + switch(substdio_copy(&ssout,&ssin)) { + case -2: + strerr_die4sys(111,FATAL,"unable to read ",name,": "); + case -3: + strerr_die4sys(111,FATAL,"unable to write ",target.s,": "); + } + + close(fdin); + if (substdio_flush(&ssout) == -1) + strerr_die4sys(111,FATAL,"unable to write ",target.s,": "); + if (fsync(fdout) == -1) + strerr_die4sys(111,FATAL,"unable to write ",target.s,": "); + close(fdout); + break; + + default: + return; + } + + if (chown(target.s,uid,gid) == -1) + strerr_die4sys(111,FATAL,"unable to chown ",target.s,": "); + if (chmod(target.s,mode) == -1) + strerr_die4sys(111,FATAL,"unable to chmod ",target.s,": "); +} + +char buf[256]; +substdio in = SUBSTDIO_FDBUF(read,0,buf,sizeof(buf)); +stralloc line = {0}; + +void main(argc,argv) +int argc; +char **argv; +{ + int match; + + umask(077); + + to = argv[1]; + if (!to) strerr_die2x(100,FATAL,"install: usage: install dir"); + + for (;;) { + if (getln(&in,&line,&match,'\n') == -1) + strerr_die2sys(111,FATAL,"unable to read input: "); + doit(&line); + if (!match) + _exit(0); + } +} diff --git a/instcheck.c b/instcheck.c @@ -0,0 +1,120 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include "substdio.h" +#include "stralloc.h" +#include "getln.h" +#include "readwrite.h" +#include "exit.h" +#include "error.h" +#include "strerr.h" +#include "byte.h" + +stralloc target = {0}; +char *to; + +#define WARNING "instcheck: warning: " +#define FATAL "instcheck: fatal: " +void nomem() { strerr_die2x(111,FATAL,"out of memory"); } + +void doit(line) +stralloc *line; +{ + struct stat st; + char *x; + unsigned int xlen; + unsigned int i; + char *type; + char *uidstr; + char *gidstr; + char *modestr; + char *mid; + char *name; + unsigned long uid; + unsigned long gid; + unsigned long mode; + int ftype; + + x = line->s; xlen = line->len; + + type = x; + i = byte_chr(x,xlen,':'); if (i == xlen) return; + x[i++] = 0; x += i; xlen -= i; + + uidstr = x; + i = byte_chr(x,xlen,':'); if (i == xlen) return; + x[i++] = 0; x += i; xlen -= i; + + gidstr = x; + i = byte_chr(x,xlen,':'); if (i == xlen) return; + x[i++] = 0; x += i; xlen -= i; + + modestr = x; + i = byte_chr(x,xlen,':'); if (i == xlen) return; + x[i++] = 0; x += i; xlen -= i; + + mid = x; + i = byte_chr(x,xlen,':'); if (i == xlen) return; + x[i++] = 0; x += i; xlen -= i; + + name = x; + i = byte_chr(x,xlen,':'); if (i == xlen) return; + x[i++] = 0; x += i; xlen -= i; + + if (!stralloc_copys(&target,to)) nomem(); + if (!stralloc_cats(&target,mid)) nomem(); + if (!stralloc_cats(&target,name)) nomem(); + if (!stralloc_0(&target)) nomem(); + + uid = -1; if (*uidstr) scan_ulong(uidstr,&uid); + gid = -1; if (*gidstr) scan_ulong(gidstr,&gid); + scan_8long(modestr,&mode); + + switch(*type) { + case 'd': ftype = S_IFDIR; break; + case 'c': ftype = S_IFREG; break; + case 'z': ftype = S_IFREG; break; + case 'p': ftype = S_IFIFO; break; + default: return; + } + + if (stat(target.s,&st) == -1) { + if (errno == error_noent) + strerr_warn3(WARNING,target.s," does not exist",0); + else + strerr_warn4(WARNING,"unable to stat ",target.s,": ",&strerr_sys); + return; + } + + if ((uid != -1) && (st.st_uid != uid)) + strerr_warn3(WARNING,target.s," has wrong owner",0); + if ((gid != -1) && (st.st_gid != gid)) + strerr_warn3(WARNING,target.s," has wrong group",0); + if ((st.st_mode & 07777) != mode) + strerr_warn3(WARNING,target.s," has wrong permissions",0); + if ((st.st_mode & S_IFMT) != ftype) + strerr_warn3(WARNING,target.s," has wrong type",0); +} + +char buf[256]; +substdio in = SUBSTDIO_FDBUF(read,0,buf,sizeof(buf)); +stralloc line = {0}; + +void main(argc,argv) +int argc; +char **argv; +{ + int match; + + umask(077); + + to = argv[1]; + if (!to) strerr_die2x(100,FATAL,"instcheck: usage: instcheck dir"); + + for (;;) { + if (getln(&in,&line,&match,'\n') == -1) + strerr_die2sys(111,FATAL,"unable to read input: "); + doit(&line); + if (!match) + _exit(0); + } +} diff --git a/ip.c b/ip.c @@ -0,0 +1,53 @@ +#include "fmt.h" +#include "scan.h" +#include "ip.h" + +unsigned int ip_fmt(s,ip) +char *s; +struct ip_address *ip; +{ + unsigned int len; + unsigned int i; + + len = 0; + i = fmt_ulong(s,(unsigned long) ip->d[0]); len += i; if (s) s += i; + i = fmt_str(s,"."); len += i; if (s) s += i; + i = fmt_ulong(s,(unsigned long) ip->d[1]); len += i; if (s) s += i; + i = fmt_str(s,"."); len += i; if (s) s += i; + i = fmt_ulong(s,(unsigned long) ip->d[2]); len += i; if (s) s += i; + i = fmt_str(s,"."); len += i; if (s) s += i; + i = fmt_ulong(s,(unsigned long) ip->d[3]); len += i; if (s) s += i; + return len; +} + +unsigned int ip_scan(s,ip) +char *s; +struct ip_address *ip; +{ + unsigned int i; + unsigned int len; + unsigned long u; + + len = 0; + i = scan_ulong(s,&u); if (!i) return 0; ip->d[0] = u; s += i; len += i; + if (*s != '.') return 0; ++s; ++len; + i = scan_ulong(s,&u); if (!i) return 0; ip->d[1] = u; s += i; len += i; + if (*s != '.') return 0; ++s; ++len; + i = scan_ulong(s,&u); if (!i) return 0; ip->d[2] = u; s += i; len += i; + if (*s != '.') return 0; ++s; ++len; + i = scan_ulong(s,&u); if (!i) return 0; ip->d[3] = u; s += i; len += i; + return len; +} + +unsigned int ip_scanbracket(s,ip) +char *s; +struct ip_address *ip; +{ + unsigned int len; + + if (*s != '[') return 0; + len = ip_scan(s + 1,ip); + if (!len) return 0; + if (s[len + 1] != ']') return 0; + return len + 2; +} diff --git a/ip.h b/ip.h @@ -0,0 +1,11 @@ +#ifndef IP_H +#define IP_H + +struct ip_address { unsigned char d[4]; } ; + +extern unsigned int ip_fmt(); +#define IPFMT 19 +extern unsigned int ip_scan(); +extern unsigned int ip_scanbracket(); + +#endif diff --git a/ipalloc.c b/ipalloc.c @@ -0,0 +1,7 @@ +#include "alloc.h" +#include "gen_allocdefs.h" +#include "ip.h" +#include "ipalloc.h" + +GEN_ALLOC_readyplus(ipalloc,struct ip_mx,ix,len,a,i,n,x,10,ipalloc_readyplus) +GEN_ALLOC_append(ipalloc,struct ip_mx,ix,len,a,i,n,x,10,ipalloc_readyplus,ipalloc_append) diff --git a/ipalloc.h b/ipalloc.h @@ -0,0 +1,14 @@ +#ifndef IPALLOC_H +#define IPALLOC_H + +#include "ip.h" + +struct ip_mx { struct ip_address ip; int pref; } ; + +#include "gen_alloc.h" + +GEN_ALLOC_typedef(ipalloc,struct ip_mx,ix,len,a) +extern int ipalloc_readyplus(); +extern int ipalloc_append(); + +#endif diff --git a/ipme.c b/ipme.c @@ -0,0 +1,95 @@ +#include <sys/types.h> +#include <sys/param.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> +#ifndef SIOCGIFCONF /* whatever works */ +#include <sys/sockio.h> +#endif +#include "hassalen.h" +#include "byte.h" +#include "ip.h" +#include "ipalloc.h" +#include "stralloc.h" +#include "ipme.h" + +static int ipmeok = 0; +ipalloc ipme = {0}; + +int ipme_is(ip) +struct ip_address *ip; +{ + int i; + if (ipme_init() != 1) return -1; + for (i = 0;i < ipme.len;++i) + if (byte_equal(&ipme.ix[i].ip,4,ip)) + return 1; + return 0; +} + +static stralloc buf = {0}; + +int ipme_init() +{ + struct ifconf ifc; + char *x; + struct ifreq *ifr; + struct sockaddr_in *sin; + int len; + int s; + struct ip_mx ix; + + if (ipmeok) return 1; + if (!ipalloc_readyplus(&ipme,0)) return 0; + ipme.len = 0; + ix.pref = 0; + + if ((s = socket(AF_INET,SOCK_STREAM,0)) == -1) return -1; + + len = 256; + for (;;) { + if (!stralloc_ready(&buf,len)) { close(s); return 0; } + buf.len = 0; + ifc.ifc_buf = buf.s; + ifc.ifc_len = len; + if (ioctl(s,SIOCGIFCONF,&ifc) >= 0) /* > is for System V */ + if (ifc.ifc_len + sizeof(*ifr) + 64 < len) { /* what a stupid interface */ + buf.len = ifc.ifc_len; + break; + } + if (len > 200000) { close(s); return -1; } + len += 100 + (len >> 2); + } + x = buf.s; + while (x < buf.s + buf.len) { + ifr = (struct ifreq *) x; +#ifdef HASSALEN + len = sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len; + if (len < sizeof(*ifr)) + len = sizeof(*ifr); + if (ifr->ifr_addr.sa_family == AF_INET) { + sin = (struct sockaddr_in *) &ifr->ifr_addr; + byte_copy(&ix.ip,4,&sin->sin_addr); + if (ioctl(s,SIOCGIFFLAGS,x) == 0) + if (ifr->ifr_flags & IFF_UP) + if (!ipalloc_append(&ipme,&ix)) { close(s); return 0; } + } +#else + len = sizeof(*ifr); + if (ioctl(s,SIOCGIFFLAGS,x) == 0) + if (ifr->ifr_flags & IFF_UP) + if (ioctl(s,SIOCGIFADDR,x) == 0) + if (ifr->ifr_addr.sa_family == AF_INET) { + sin = (struct sockaddr_in *) &ifr->ifr_addr; + byte_copy(&ix.ip,4,&sin->sin_addr); + if (!ipalloc_append(&ipme,&ix)) { close(s); return 0; } + } +#endif + x += len; + } + close(s); + ipmeok = 1; + return 1; +} diff --git a/ipme.h b/ipme.h @@ -0,0 +1,12 @@ +#ifndef IPME_H +#define IPME_H + +#include "ip.h" +#include "ipalloc.h" + +extern ipalloc ipme; + +extern int ipme_init(); +extern int ipme_is(); + +#endif diff --git a/ipmeprint.c b/ipmeprint.c @@ -0,0 +1,24 @@ +#include "subfd.h" +#include "substdio.h" +#include "ip.h" +#include "ipme.h" +#include "exit.h" + +char temp[IPFMT]; + +void main() +{ + int j; + switch(ipme_init()) + { + case 0: substdio_putsflush(subfderr,"out of memory\n"); _exit(111); + case -1: substdio_putsflush(subfderr,"hard error\n"); _exit(100); + } + for (j = 0;j < ipme.len;++j) + { + substdio_put(subfdout,temp,ip_fmt(temp,&ipme.ix[j].ip)); + substdio_puts(subfdout,"\n"); + } + substdio_flush(subfdout); + _exit(0); +} diff --git a/lock.h b/lock.h @@ -0,0 +1,8 @@ +#ifndef LOCK_H +#define LOCK_H + +extern int lock_ex(); +extern int lock_un(); +extern int lock_exnb(); + +#endif diff --git a/lock_ex.c b/lock_ex.c @@ -0,0 +1,11 @@ +#include <sys/types.h> +#include <sys/file.h> +#include <fcntl.h> +#include "hasflock.h" +#include "lock.h" + +#ifdef HASFLOCK +int lock_ex(fd) int fd; { return flock(fd,LOCK_EX); } +#else +int lock_ex(fd) int fd; { return lockf(fd,1,0); } +#endif diff --git a/lock_exnb.c b/lock_exnb.c @@ -0,0 +1,11 @@ +#include <sys/types.h> +#include <sys/file.h> +#include <fcntl.h> +#include "hasflock.h" +#include "lock.h" + +#ifdef HASFLOCK +int lock_exnb(fd) int fd; { return flock(fd,LOCK_EX | LOCK_NB); } +#else +int lock_exnb(fd) int fd; { return lockf(fd,2,0); } +#endif diff --git a/lock_un.c b/lock_un.c @@ -0,0 +1,11 @@ +#include <sys/types.h> +#include <sys/file.h> +#include <fcntl.h> +#include "hasflock.h" +#include "lock.h" + +#ifdef HASFLOCK +int lock_un(fd) int fd; { return flock(fd,LOCK_UN); } +#else +int lock_un(fd) int fd; { return lockf(fd,0,0); } +#endif diff --git a/maildir.5 b/maildir.5 @@ -0,0 +1,239 @@ +.TH maildir 5 +.SH "NAME" +maildir \- directory for incoming mail messages +.SH "INTRODUCTION" +.I maildir +is a structure for +directories of incoming mail messages. +It solves the reliability problems that plague +.I mbox +files and +.I mh +folders. +.SH "RELIABILITY ISSUES" +A machine may crash while it is delivering a message. +For both +.I mbox +files and +.I mh +folders this means that the message will be silently truncated. +Even worse: for +.I mbox +format, if the message is truncated in the middle of a line, +it will be silently joined to the next message. +The mail transport agent will try again later to deliver the message, +but it is unacceptable that a corrupted message should show up at all. +In +.IR maildir , +every message is guaranteed complete upon delivery. + +A machine may have two programs simultaneously delivering mail +to the same user. +The +.I mbox +and +.I mh +formats require the programs to update a single central file. +If the programs do not use some locking mechanism, +the central file will be corrupted. +There are several +.I mbox +and +.I mh +locking mechanisms, +none of which work portably and reliably. +In contrast, in +.IR maildir , +no locks are ever necessary. +Different delivery processes never touch the same file. + +A user may try to delete messages from his mailbox at the same +moment that the machine delivers a new message. +For +.I mbox +and +.I mh +formats, the user's mail-reading program must know +what locking mechanism the mail-delivery programs use. +In contrast, in +.IR maildir , +any delivered message +can be safely updated or deleted by a mail-reading program. + +Many sites use Sun's +.B Network F\fPa\fBil\fPur\fBe System +(NFS), +presumably because the operating system vendor does not offer +anything else. +NFS exacerbates all of the above problems. +Some NFS implementations don't provide +.B any +reliable locking mechanism. +With +.I mbox +and +.I mh +formats, +if two machines deliver mail to the same user, +or if a user reads mail anywhere except the delivery machine, +the user's mail is at risk. +.I maildir +works without trouble over NFS. +.SH "THE MAILDIR STRUCTURE" +A directory in +.I maildir +format has three subdirectories, +all on the same filesystem: +.BR tmp , +.BR new , +and +.BR cur . + +Each file in +.B new +is a newly delivered mail message. +The modification time of the file is the delivery date of the message. +The message is delivered +.I without +an extra UUCP-style +.B From_ +line, +.I without +any +.B >From +quoting, +and +.I without +an extra blank line at the end. +The message is normally in RFC 822 format, +starting with a +.B Return-Path +line and a +.B Delivered-To +line, +but it could contain arbitrary binary data. +It might not even end with a newline. + +Files in +.B cur +are just like files in +.BR new . +The big difference is that files in +.B cur +are no longer new mail: +they have been seen by the user's mail-reading program. +.SH "HOW A MESSAGE IS DELIVERED" +The +.B tmp +directory is used to ensure reliable delivery, +as discussed here. + +A program delivers a mail message in six steps. +First, it +.B chdir()\fPs +to the +.I maildir +directory. +Second, it +.B stat()s +the name +.BR tmp/\fItime.pid.host , +where +.I time +is the number of seconds since the beginning of 1970 GMT, +.I pid +is the program's process ID, +and +.I host +is the host name. +Third, if +.B stat() +returned anything other than ENOENT, +the program sleeps for two seconds, updates +.IR time , +and tries the +.B stat() +again, a limited number of times. +Fourth, the program +creates +.BR tmp/\fItime.pid.host . +Fifth, the program +.I NFS-writes +the message to the file. +Sixth, the program +.BR link() s +the file to +.BR new/\fItime.pid.host . +At that instant the message has been successfully delivered. + +The delivery program is required to start a 24-hour timer before +creating +.BR tmp/\fItime.pid.host , +and to abort the delivery +if the timer expires. +Upon error, timeout, or normal completion, +the delivery program may attempt to +.B unlink() +.BR tmp/\fItime.pid.host . + +.I NFS-writing +means +(1) as usual, checking the number of bytes returned from each +.B write() +call; +(2) calling +.B fsync() +and checking its return value; +(3) calling +.B close() +and checking its return value. +(Standard NFS implementations handle +.B fsync() +incorrectly +but make up for it by abusing +.BR close() .) +.SH "HOW A MESSAGE IS READ" +A mail reader operates as follows. + +It looks through the +.B new +directory for new messages. +Say there is a new message, +.BR new/\fIunique . +The reader may freely display the contents of +.BR new/\fIunique , +delete +.BR new/\fIunique , +or rename +.B new/\fIunique +as +.BR cur/\fIunique:info . +See +.B http://pobox.com/~djb/maildir.html +for the meaning of +.IR info . + +The reader is also expected to look through the +.B tmp +directory and to clean up any old files found there. +A file in +.B tmp +may be safely removed if it +has not been accessed in 36 hours. + +It is a good idea for readers to skip all filenames in +.B new +and +.B cur +starting with a dot. +Other than this, readers should not attempt to parse filenames. +.SH "ENVIRONMENT VARIABLES" +Mail readers supporting +.I maildir +use the +.B MAILDIR +environment variable +as the name of the user's primary mail directory. +.SH "SEE ALSO" +mbox(5), +qmail-local(8) diff --git a/maildir.c b/maildir.c @@ -0,0 +1,108 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include "prioq.h" +#include "env.h" +#include "stralloc.h" +#include "direntry.h" +#include "datetime.h" +#include "now.h" +#include "str.h" +#include "maildir.h" + +struct strerr maildir_chdir_err; +struct strerr maildir_scan_err; + +int maildir_chdir() +{ + char *maildir; + maildir = env_get("MAILDIR"); + if (!maildir) + STRERR(-1,maildir_chdir_err,"MAILDIR not set") + if (chdir(maildir) == -1) + STRERR_SYS3(-1,maildir_chdir_err,"unable to chdir to ",maildir,": ") + return 0; +} + +void maildir_clean(tmpname) +stralloc *tmpname; +{ + DIR *dir; + direntry *d; + datetime_sec time; + struct stat st; + + time = now(); + + dir = opendir("tmp"); + if (!dir) return; + + while (d = readdir(dir)) + { + if (d->d_name[0] == '.') continue; + if (!stralloc_copys(tmpname,"tmp/")) break; + if (!stralloc_cats(tmpname,d->d_name)) break; + if (!stralloc_0(tmpname)) break; + if (stat(tmpname->s,&st) == 0) + if (time > st.st_atime + 129600) + unlink(tmpname->s); + } + closedir(dir); +} + +static int append(pq,filenames,subdir,time) +prioq *pq; +stralloc *filenames; +char *subdir; +datetime_sec time; +{ + DIR *dir; + direntry *d; + struct prioq_elt pe; + unsigned int pos; + struct stat st; + + dir = opendir(subdir); + if (!dir) + STRERR_SYS3(-1,maildir_scan_err,"unable to scan $MAILDIR/",subdir,": ") + + while (d = readdir(dir)) + { + if (d->d_name[0] == '.') continue; + pos = filenames->len; + if (!stralloc_cats(filenames,subdir)) break; + if (!stralloc_cats(filenames,"/")) break; + if (!stralloc_cats(filenames,d->d_name)) break; + if (!stralloc_0(filenames)) break; + if (stat(filenames->s + pos,&st) == 0) + if (st.st_mtime < time) /* don't want to mix up the order */ + { + pe.dt = st.st_mtime; + pe.id = pos; + if (!prioq_insert(pq,&pe)) break; + } + } + + closedir(dir); + if (d) STRERR_SYS3(-1,maildir_scan_err,"unable to read $MAILDIR/",subdir,": ") + return 0; +} + +int maildir_scan(pq,filenames,flagnew,flagcur) +prioq *pq; +stralloc *filenames; +int flagnew; +int flagcur; +{ + struct prioq_elt pe; + datetime_sec time; + int r; + + if (!stralloc_copys(filenames,"")) return 0; + while (prioq_min(pq,&pe)) prioq_delmin(pq); + + time = now(); + + if (flagnew) if (append(pq,filenames,"new",time) == -1) return -1; + if (flagcur) if (append(pq,filenames,"cur",time) == -1) return -1; + return 0; +} diff --git a/maildir.h b/maildir.h @@ -0,0 +1,12 @@ +#ifndef MAILDIR_H +#define MAILDIR_H + +#include "strerr.h" +extern struct strerr maildir_chdir_err; +extern struct strerr maildir_scan_err; + +extern int maildir_chdir(); +extern void maildir_clean(); +extern int maildir_scan(); + +#endif diff --git a/maildir2mbox.1 b/maildir2mbox.1 @@ -0,0 +1,53 @@ +.TH maildir2mbox 1 +.SH NAME +maildir2mbox \- move mail from a maildir to an mbox +.SH SYNOPSIS +.B maildir2mbox +.SH DESCRIPTION +.B maildir2mbox +moves mail from a +.IR maildir -format +directory to an +.IR mbox -format +file. + +You must supply three environment variables to +.BR maildir2mbox : +.B MAILDIR +is the name of your +.I maildir +directory; +.B MAIL +is the name of your +.I mbox +file; +and +.B MAILTMP +is a temporary file that +.B maildir2mbox +can overwrite. +.B MAILTMP +and +.B MAIL +must be on the same filesystem. + +.B maildir2mbox +is reliable: +it will not remove messages +from +.B MAILDIR +until the messages have been successfully appended to +.BR MAIL . + +.B maildir2mbox +locks +.B MAIL +to protect against simultaneous access by a mail reader. +This locking system does not protect against simultaneous access +by another +.BR maildir2mbox ; +you should run only one +.B maildir2mbox +at a time. +.SH "SEE ALSO" +maildir(5) diff --git a/maildir2mbox.c b/maildir2mbox.c @@ -0,0 +1,162 @@ +#include "readwrite.h" +#include "prioq.h" +#include "env.h" +#include "stralloc.h" +#include "subfd.h" +#include "substdio.h" +#include "getln.h" +#include "error.h" +#include "open.h" +#include "lock.h" +#include "gfrom.h" +#include "str.h" +#include "exit.h" +#include "myctime.h" +#include "maildir.h" + +char *mbox; +char *mboxtmp; + +stralloc filenames = {0}; +prioq pq = {0}; +prioq pq2 = {0}; + +stralloc line = {0}; + +stralloc ufline = {0}; + +char inbuf[SUBSTDIO_INSIZE]; +char outbuf[SUBSTDIO_OUTSIZE]; + +#define FATAL "maildir2mbox: fatal: " +#define WARNING "maildir2mbox: warning: " + +void die_nomem() { strerr_die2x(111,FATAL,"out of memory"); } + +void main() +{ + substdio ssin; + substdio ssout; + struct prioq_elt pe; + int fdoldmbox; + int fdnewmbox; + int fd; + int match; + int fdlock; + + umask(077); + + mbox = env_get("MAIL"); + if (!mbox) strerr_die2x(111,FATAL,"MAIL not set"); + mboxtmp = env_get("MAILTMP"); + if (!mboxtmp) strerr_die2x(111,FATAL,"MAILTMP not set"); + + if (maildir_chdir() == -1) + strerr_die1(111,FATAL,&maildir_chdir_err); + maildir_clean(&filenames); + if (maildir_scan(&pq,&filenames,1,1) == -1) + strerr_die1(111,FATAL,&maildir_scan_err); + + if (!prioq_min(&pq,&pe)) _exit(0); /* nothing new */ + + fdlock = open_append(mbox); + if (fdlock == -1) + strerr_die4sys(111,FATAL,"unable to lock ",mbox,": "); + if (lock_ex(fdlock) == -1) + strerr_die4sys(111,FATAL,"unable to lock ",mbox,": "); + + fdoldmbox = open_read(mbox); + if (fdoldmbox == -1) + strerr_die4sys(111,FATAL,"unable to read ",mbox,": "); + + fdnewmbox = open_trunc(mboxtmp); + if (fdnewmbox == -1) + strerr_die4sys(111,FATAL,"unable to create ",mboxtmp,": "); + + substdio_fdbuf(&ssin,read,fdoldmbox,inbuf,sizeof(inbuf)); + substdio_fdbuf(&ssout,write,fdnewmbox,outbuf,sizeof(outbuf)); + + switch(substdio_copy(&ssout,&ssin)) + { + case -2: strerr_die4sys(111,FATAL,"unable to read ",mbox,": "); + case -3: strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": "); + } + + while (prioq_min(&pq,&pe)) + { + prioq_delmin(&pq); + if (!prioq_insert(&pq2,&pe)) die_nomem(); + + fd = open_read(filenames.s + pe.id); + if (fd == -1) + strerr_die4sys(111,FATAL,"unable to read $MAILDIR/",filenames.s + pe.id,": "); + substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf)); + + if (getln(&ssin,&line,&match,'\n') != 0) + strerr_die4sys(111,FATAL,"unable to read $MAILDIR/",filenames.s + pe.id,": "); + + if (!stralloc_copys(&ufline,"From XXX ")) die_nomem(); + if (match) + if (stralloc_starts(&line,"Return-Path: <")) + { + if (line.s[14] == '>') + { + if (!stralloc_copys(&ufline,"From MAILER-DAEMON ")) die_nomem(); + } + else + { + int i; + if (!stralloc_ready(&ufline,line.len)) die_nomem(); + if (!stralloc_copys(&ufline,"From ")) die_nomem(); + for (i = 14;i < line.len - 2;++i) + if ((line.s[i] == ' ') || (line.s[i] == '\t')) + ufline.s[ufline.len++] = '-'; + else + ufline.s[ufline.len++] = line.s[i]; + if (!stralloc_cats(&ufline," ")) die_nomem(); + } + } + if (!stralloc_cats(&ufline,myctime(pe.dt))) die_nomem(); + if (substdio_put(&ssout,ufline.s,ufline.len) == -1) + strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": "); + + while (match && line.len) + { + if (gfrom(line.s,line.len)) + if (substdio_puts(&ssout,">") == -1) + strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": "); + if (substdio_put(&ssout,line.s,line.len) == -1) + strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": "); + if (!match) + { + if (substdio_puts(&ssout,"\n") == -1) + strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": "); + break; + } + if (getln(&ssin,&line,&match,'\n') != 0) + strerr_die4sys(111,FATAL,"unable to read $MAILDIR/",filenames.s + pe.id,": "); + } + if (substdio_puts(&ssout,"\n")) + strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": "); + + close(fd); + } + + if (substdio_flush(&ssout) == -1) + strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": "); + if (fsync(fdnewmbox) == -1) + strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": "); + if (close(fdnewmbox) == -1) /* NFS dorks */ + strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": "); + if (rename(mboxtmp,mbox) == -1) + strerr_die6(111,FATAL,"unable to move ",mboxtmp," to ",mbox,": ",&strerr_sys); + + while (prioq_min(&pq2,&pe)) + { + prioq_delmin(&pq2); + if (unlink(filenames.s + pe.id) == -1) + strerr_warn4(WARNING,"$MAILDIR/",filenames.s + pe.id," will be delivered twice; unable to unlink: ",&strerr_sys); + } + + _exit(0); +} diff --git a/maildirmake.1 b/maildirmake.1 @@ -0,0 +1,15 @@ +.TH maildirmake 1 +.SH NAME +maildirmake \- create a maildir for incoming mail +.SH SYNOPSIS +.B maildirmake +.I dir +.SH DESCRIPTION +.B maildirmake +makes a new directory, +.IR dir , +in +.B maildir +format. +.SH "SEE ALSO" +maildir(5) diff --git a/maildirmake.c b/maildirmake.c @@ -0,0 +1,22 @@ +#include "subfd.h" +#include "substdio.h" +#include "error.h" +#include "exit.h" + +void die(s) char *s; { substdio_putsflush(subfderr,s); _exit(111); } + +void main(argc,argv) +int argc; +char **argv; +{ + umask(077); + if (!argv[1]) die("usage: maildirmake name\n"); + if (mkdir(argv[1],0700)) + if (errno == error_exist) die("fatal: directory already exists\n"); + else die("fatal: unable to mkdir\n"); + if (chdir(argv[1])) die("fatal: unable to chdir\n"); + if (mkdir("tmp",0700)) die("fatal: unable to make tmp/ subdir\n"); + if (mkdir("new",0700)) die("fatal: unable to make new/ subdir\n"); + if (mkdir("cur",0700)) die("fatal: unable to make cur/ subdir\n"); + _exit(0); +} diff --git a/maildirwatch.1 b/maildirwatch.1 @@ -0,0 +1,23 @@ +.TH maildirwatch 1 +.SH NAME +maildirwatch \- look for new mail in a maildir +.SH SYNOPSIS +.B maildirwatch +.SH DESCRIPTION +.B maildirwatch +watches your +.I maildir +for new mail. +You must supply a +.B MAILDIR +environment variable +with the name of your +.I maildir +directory. + +.B maildirwatch +prints a new mail summary twice per minute. +It is designed to run inside a (VT100-compatible) window; +it clears the window before each summary. +.SH "SEE ALSO" +maildir(5) diff --git a/maildirwatch.c b/maildirwatch.c @@ -0,0 +1,125 @@ +#include "getln.h" +#include "substdio.h" +#include "subfd.h" +#include "prioq.h" +#include "stralloc.h" +#include "str.h" +#include "exit.h" +#include "hfield.h" +#include "readwrite.h" +#include "open.h" +#include "headerbody.h" +#include "maildir.h" + +#define FATAL "maildirwatch: fatal: " + +void die_nomem() { strerr_die2x(111,FATAL,"out of memory"); } + +stralloc recipient = {0}; +stralloc sender = {0}; +stralloc fromline = {0}; +stralloc text = {0}; + +void addtext(s,n) char *s; int n; +{ + if (!stralloc_catb(&text,s,n)) die_nomem(); + if (text.len > 158) text.len = 158; +} +void dobody(h) stralloc *h; { addtext(h->s,h->len); } +void doheader(h) stralloc *h; +{ + int i; + switch(hfield_known(h->s,h->len)) + { + case H_SUBJECT: + i = hfield_skipname(h->s,h->len); + addtext(h->s + i,h->len - i); + break; + case H_DELIVEREDTO: + i = hfield_skipname(h->s,h->len); + if (i < h->len) + if (!stralloc_copyb(&recipient,h->s + i,h->len - i - 1)) die_nomem(); + break; + case H_RETURNPATH: + i = hfield_skipname(h->s,h->len); + if (i < h->len) + if (!stralloc_copyb(&sender,h->s + i,h->len - i - 1)) die_nomem(); + break; + case H_FROM: + if (!stralloc_copyb(&fromline,h->s,h->len - 1)) die_nomem(); + break; + } +} +void finishheader() { ; } + +stralloc filenames = {0}; +prioq pq = {0}; + +char inbuf[SUBSTDIO_INSIZE]; +substdio ssin; + +void main() +{ + struct prioq_elt pe; + int fd; + int i; + + if (maildir_chdir() == -1) + strerr_die1(111,FATAL,&maildir_chdir_err); + + for (;;) + { + maildir_clean(&filenames); + if (maildir_scan(&pq,&filenames,1,0) == -1) + strerr_die1(111,FATAL,&maildir_scan_err); + + substdio_putsflush(subfdout,"\033[;H\033[;J"); + + while (prioq_min(&pq,&pe)) + { + prioq_delmin(&pq); + + fd = open_read(filenames.s + pe.id); + if (fd == -1) continue; + substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf)); + + if (!stralloc_copys(&sender,"?")) die_nomem(); + if (!stralloc_copys(&recipient,"?")) die_nomem(); + if (!stralloc_copys(&fromline,"")) die_nomem(); + if (!stralloc_copys(&text,"")) die_nomem(); + if (headerbody(&ssin,doheader,finishheader,dobody) == -1) + strerr_die2x(111,FATAL,"trouble reading new message"); + + for (i = 0;i < fromline.len;++i) + if ((fromline.s[i] < 32) || (fromline.s[i] > 126)) + fromline.s[i] = '/'; + for (i = 0;i < sender.len;++i) + if ((sender.s[i] < 32) || (sender.s[i] > 126)) + sender.s[i] = '?'; + for (i = 0;i < recipient.len;++i) + if ((recipient.s[i] < 32) || (recipient.s[i] > 126)) + recipient.s[i] = '?'; + for (i = 0;i < text.len;++i) + if ((text.s[i] < 32) || (text.s[i] > 126)) + text.s[i] = '/'; + substdio_puts(subfdout,"FROM "); + substdio_put(subfdout,sender.s,sender.len); + substdio_puts(subfdout," TO <"); + substdio_put(subfdout,recipient.s,recipient.len); + substdio_puts(subfdout,">\n"); + if (fromline.len) + { + substdio_puts(subfdout,"\033[1m"); + substdio_put(subfdout,fromline.s,fromline.len); + substdio_puts(subfdout,"\033[0m\n"); + } + substdio_put(subfdout,text.s,text.len); + substdio_puts(subfdout,"\n\n"); + + close(fd); + } + + substdio_flush(subfdout); + sleep(30); + } +} diff --git a/mailsubj.1 b/mailsubj.1 @@ -0,0 +1,38 @@ +.TH mailsubj 1 +.SH NAME +mailsubj \- send a mail message with a subject line +.SH SYNOPSIS +.B mailsubj +.I subject +.I recip ... +.SH DESCRIPTION +.B mailsubj +inserts +.I subject +and the list of +.IR recip s +into a mail message: + +.EX + Subject: subject +.br + To: recip ... +.br + +.br + body +.EE + +.B mailsubj +reads the body of the message from its standard input. +Then it sends the message. + +Note that +.I subject +and +.I recip +must be quoted properly for the message header. +.SH "SEE ALSO" +addresses(5), +qmail-header(8), +qmail-inject(8) diff --git a/mailsubj.sh b/mailsubj.sh @@ -0,0 +1,7 @@ +subject="$1" +shift +( echo Subject: "$subject" + echo To: ${1+"$@"} + echo '' + cat +) | QMAIL/bin/qmail-inject diff --git a/make-compile.sh b/make-compile.sh @@ -0,0 +1 @@ +echo exec "$CC" -c '${1+"$@"}' diff --git a/make-load.sh b/make-load.sh @@ -0,0 +1,2 @@ +echo 'main="$1"; shift' +echo exec "$LD" '-o "$main" "$main".o ${1+"$@"}' diff --git a/make-makelib.sh b/make-makelib.sh @@ -0,0 +1,16 @@ +echo 'main="$1"; shift' +echo 'rm -f "$main"' +echo 'ar cr "$main" ${1+"$@"}' + +case "$1" in +sunos-5.*) ;; +unix_sv*) ;; +irix64-*) ;; +irix-*) ;; +dgux-*) ;; +hp-ux-*) ;; +sco*) ;; +*) + echo 'ranlib "$main"' + ;; +esac diff --git a/mbox.5 b/mbox.5 @@ -0,0 +1,235 @@ +.TH mbox 5 +.SH "NAME" +mbox \- file containing mail messages +.SH "INTRODUCTION" +The most common format for storage of mail messages is +.I mbox +format. +An +.I mbox +is a single file containing zero or more mail messages. +.SH "MESSAGE FORMAT" +A message encoded in +.I mbox +format begins with a +.B From_ +line, continues with a series of +.B \fRnon-\fBFrom_ +lines, +and ends with a blank line. +A +.B From_ +line means any line that begins with the characters +F, r, o, m, space: + +.EX + From god@heaven.af.mil Sat Jan 3 01:05:34 1996 +.br + Return-Path: <god@heaven.af.mil> +.br + Delivered-To: djb@silverton.berkeley.edu +.br + Date: 3 Jan 1996 01:05:34 -0000 +.br + From: God <god@heaven.af.mil> +.br + To: djb@silverton.berkeley.edu (D. J. Bernstein) +.br + +.br + How's that mail system project coming along? +.br + +.EE + +The final line is a completely blank line (no spaces or tabs). +Notice that blank lines may also appear elsewhere in the message. + +The +.B From_ +line always looks like +.B From +.I envsender +.I date +.IR moreinfo . +.I envsender +is one word, without spaces or tabs; +it is usually the envelope sender of the message. +.I date +is the delivery date of the message. +It always contains exactly 24 characters in +.B asctime +format. +.I moreinfo +is optional; it may contain arbitrary information. + +Between the +.B From_ +line and the blank line is a message in RFC 822 format, +as described in +.BR qmail-header(5) , +subject to +.B >From quoting +as described below. +.SH "HOW A MESSAGE IS DELIVERED" +Here is how a program appends a message to an +.I mbox +file. + +It first creates a +.B From_ +line given the message's envelope sender and the current date. +If the envelope sender is empty (i.e., if this is a bounce message), +the program uses +.B MAILER-DAEMON +instead. +If the envelope sender contains spaces, tabs, or newlines, +the program replaces them with hyphens. + +The program then copies the message, applying +.B >From quoting +to each line. +.B >From quoting +ensures that the resulting lines are not +.B From_ +lines: +the program prepends a +.B > +to any +.B From_ +line, +.B >From_ +line, +.B >>From_ +line, +.B >>>From_ +line, +etc. + +Finally the program appends a blank line to the message. +If the last line of the message was a partial line, +it writes two newlines; +otherwise it writes one. +.SH "HOW A MESSAGE IS READ" +A reader scans through an +.I mbox +file looking for +.B From_ +lines. +Any +.B From_ +line marks the beginning of a message. +The reader should not attempt to take advantage of the fact that every +.B From_ +line (past the beginning of the file) +is preceded by a blank line. + +Once the reader finds a message, +it extracts a (possibly corrupted) envelope sender +and delivery date out of the +.B From_ +line. +It then reads until the next +.B From_ +line or end of file, whichever comes first. +It strips off the final blank line +and +deletes the +quoting of +.B >From_ +lines and +.B >>From_ +lines and so on. +The result is an RFC 822 message. +.SH "COMMON MBOX VARIANTS" +There are many variants of +.I mbox +format. +The variant described above is +.I mboxrd +format, popularized by Rahul Dhesi in June 1995. + +The original +.I mboxo +format quotes only +.B From_ +lines, not +.B >From_ +lines. +As a result it is impossible to tell whether + +.EX + From: djb@silverton.berkeley.edu (D. J. Bernstein) +.br + To: god@heaven.af.mil +.br + +.br + >From now through August I'll be doing beta testing. +.br + Thanks for your interest. +.EE + +was quoted in the original message. +An +.I mboxrd +reader will always strip off the quoting. + +.I mboxcl +format is like +.I mboxo +format, but includes a Content-Length field with the +number of bytes in the message. +.I mboxcl2 +format is like +.I mboxcl +but has no +.B >From +quoting. +These formats are used by SVR4 mailers. +.I mboxcl2 +cannot be read safely by +.I mboxrd +readers. +.SH "UNSPECIFIED DETAILS" +There are many locking mechanisms for +.I mbox +files. +.B qmail-local +always uses +.B flock +on systems that have it, otherwise +.BR lockf . + +The delivery date in a +.B From_ +line does not specify a time zone. +.B qmail-local +always creates the delivery date in GMT +so that +.I mbox +files can be safely transported from one time zone to another. + +If the mtime on a nonempty +.I mbox +file is greater than the atime, +the file has new mail. +If the mtime is smaller than the atime, +the new mail has been read. +If the atime equals the mtime, +there is no way to tell whether the file has new mail, +since +.B qmail-local +takes much less than a second to run. +One solution is for a mail reader to artificially set the +atime to the mtime plus 1. +Then the file has new mail if and only if the atime is +less than or equal to the mtime. + +Some mail readers place +.B Status +fields in each message to indicate which messages have been read. +.SH "SEE ALSO" +maildir(5), +qmail-header(5), +qmail-local(8) diff --git a/myctime.c b/myctime.c @@ -0,0 +1,37 @@ +#include "datetime.h" +#include "fmt.h" +#include "myctime.h" + +static char *daytab[7] = { +"Sun","Mon","Tue","Wed","Thu","Fri","Sat" +}; +static char *montab[12] = { +"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" +}; + +static char result[30]; + +char *myctime(t) +datetime_sec t; +{ + struct datetime dt; + unsigned int len; + datetime_tai(&dt,t); + len = 0; + len += fmt_str(result + len,daytab[dt.wday]); + result[len++] = ' '; + len += fmt_str(result + len,montab[dt.mon]); + result[len++] = ' '; + len += fmt_uint0(result + len,dt.mday,2); + result[len++] = ' '; + len += fmt_uint0(result + len,dt.hour,2); + result[len++] = ':'; + len += fmt_uint0(result + len,dt.min,2); + result[len++] = ':'; + len += fmt_uint0(result + len,dt.sec,2); + result[len++] = ' '; + len += fmt_uint(result + len,1900 + dt.year); + result[len++] = '\n'; + result[len++] = 0; + return result; +} diff --git a/myctime.h b/myctime.h @@ -0,0 +1,6 @@ +#ifndef MYCTIME_H +#define MYCTIME_H + +extern char *myctime(); + +#endif diff --git a/ndelay.c b/ndelay.c @@ -0,0 +1,9 @@ +#include <sys/types.h> +#include <fcntl.h> +#include "ndelay.h" + +int ndelay_on(fd) +int fd; +{ + return fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0) | O_NDELAY); +} diff --git a/ndelay.h b/ndelay.h @@ -0,0 +1,7 @@ +#ifndef NDELAY_H +#define NDELAY_H + +extern int ndelay_on(); +extern int ndelay_off(); + +#endif diff --git a/ndelay_off.c b/ndelay_off.c @@ -0,0 +1,9 @@ +#include <sys/types.h> +#include <fcntl.h> +#include "ndelay.h" + +int ndelay_off(fd) +int fd; +{ + return fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0) & ~O_NDELAY); +} diff --git a/newfield.c b/newfield.c @@ -0,0 +1,68 @@ +#include "fmt.h" +#include "datetime.h" +#include "stralloc.h" +#include "date822fmt.h" +#include "newfield.h" + +/* "Date: 26 Sep 1995 04:46:53 -0000\n" */ +stralloc newfield_date = {0}; +/* "Message-ID: <19950926044653.12345.qmail@silverton.berkeley.edu>\n" */ +stralloc newfield_msgid = {0}; + +static unsigned int datefmt(s,when) +char *s; +datetime_sec when; +{ + unsigned int i; + unsigned int len; + struct datetime dt; + datetime_tai(&dt,when); + len = 0; + i = fmt_str(s,"Date: "); len += i; if (s) s += i; + i = date822fmt(s,&dt); len += i; if (s) s += i; + return len; +} + +static unsigned int msgidfmt(s,idhost,idhostlen,when) +char *s; +char *idhost; +int idhostlen; +datetime_sec when; +{ + unsigned int i; + unsigned int len; + struct datetime dt; + datetime_tai(&dt,when); + len = 0; + i = fmt_str(s,"Message-ID: <"); len += i; if (s) s += i; + i = fmt_uint(s,dt.year + 1900); len += i; if (s) s += i; + i = fmt_uint0(s,dt.mon + 1,2); len += i; if (s) s += i; + i = fmt_uint0(s,dt.mday,2); len += i; if (s) s += i; + i = fmt_uint0(s,dt.hour,2); len += i; if (s) s += i; + i = fmt_uint0(s,dt.min,2); len += i; if (s) s += i; + i = fmt_uint0(s,dt.sec,2); len += i; if (s) s += i; + i = fmt_str(s,"."); len += i; if (s) s += i; + i = fmt_uint(s,getpid()); len += i; if (s) s += i; + i = fmt_str(s,".qmail@"); len += i; if (s) s += i; + i = fmt_strn(s,idhost,idhostlen); len += i; if (s) s += i; + i = fmt_str(s,">\n"); len += i; if (s) s += i; + return len; +} + +int newfield_datemake(when) +datetime_sec when; +{ + if (!stralloc_ready(&newfield_date,datefmt(FMT_LEN,when))) return 0; + newfield_date.len = datefmt(newfield_date.s,when); + return 1; +} + +int newfield_msgidmake(idhost,idhostlen,when) +char *idhost; +int idhostlen; +datetime_sec when; +{ + if (!stralloc_ready(&newfield_msgid,msgidfmt(FMT_LEN,idhost,idhostlen,when))) return 0; + newfield_msgid.len = msgidfmt(newfield_msgid.s,idhost,idhostlen,when); + return 1; +} diff --git a/newfield.h b/newfield.h @@ -0,0 +1,12 @@ +#ifndef NEWFIELD_H +#define NEWFIELD_H + +#include "stralloc.h" + +extern stralloc newfield_date; +extern int newfield_datemake(); + +extern stralloc newfield_msgid; +extern int newfield_msgidmake(); + +#endif diff --git a/now.3 b/now.3 @@ -0,0 +1,14 @@ +.TH now 3 +.SH NAME +now \- get current time, in seconds +.SH SYNTAX +.B #include <now.h> + +datetime_sec \fBnow\fP(); +.SH DESCRIPTION +.B now +returns the number of real-time seconds that have elapsed +since the end of 1969 TAI. +.SH "SEE ALSO" +datetime(3), +time(3) diff --git a/now.c b/now.c @@ -0,0 +1,8 @@ +#include <time.h> +#include "datetime.h" +#include "now.h" + +datetime_sec now() +{ + return time((long *) 0); +} diff --git a/now.h b/now.h @@ -0,0 +1,8 @@ +#ifndef NOW_H +#define NOW_H + +#include "datetime.h" + +extern datetime_sec now(); + +#endif diff --git a/open.h b/open.h @@ -0,0 +1,10 @@ +#ifndef OPEN_H +#define OPEN_H + +extern int open_read(); +extern int open_excl(); +extern int open_append(); +extern int open_trunc(); +extern int open_write(); + +#endif diff --git a/open_append.c b/open_append.c @@ -0,0 +1,6 @@ +#include <sys/types.h> +#include <fcntl.h> +#include "open.h" + +int open_append(fn) char *fn; +{ return open(fn,O_WRONLY | O_NDELAY | O_APPEND | O_CREAT,0600); } diff --git a/open_excl.c b/open_excl.c @@ -0,0 +1,6 @@ +#include <sys/types.h> +#include <fcntl.h> +#include "open.h" + +int open_excl(fn) char *fn; +{ return open(fn,O_WRONLY | O_EXCL | O_CREAT,0644); } diff --git a/open_read.c b/open_read.c @@ -0,0 +1,6 @@ +#include <sys/types.h> +#include <fcntl.h> +#include "open.h" + +int open_read(fn) char *fn; +{ return open(fn,O_RDONLY | O_NDELAY); } diff --git a/open_trunc.c b/open_trunc.c @@ -0,0 +1,6 @@ +#include <sys/types.h> +#include <fcntl.h> +#include "open.h" + +int open_trunc(fn) char *fn; +{ return open(fn,O_WRONLY | O_NDELAY | O_TRUNC | O_CREAT,0644); } diff --git a/open_write.c b/open_write.c @@ -0,0 +1,6 @@ +#include <sys/types.h> +#include <fcntl.h> +#include "open.h" + +int open_write(fn) char *fn; +{ return open(fn,O_WRONLY | O_NDELAY); } diff --git a/pinq.sh b/pinq.sh @@ -0,0 +1 @@ +QMAIL/bin/maildir2mbox && exec pine ${1+"$@"} diff --git a/predate.c b/predate.c @@ -0,0 +1,125 @@ +#include <sys/types.h> +#include <time.h> +#include "datetime.h" +#include "fork.h" +#include "wait.h" +#include "fd.h" +#include "fmt.h" +#include "substdio.h" +#include "subfd.h" +#include "readwrite.h" +#include "exit.h" + +static char *montab[12] = { +"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" +}; + +char num[FMT_ULONG]; +char outbuf[1024]; + +void main(argc,argv) +int argc; +char **argv; +{ + time_t now; + struct tm *tm; + struct datetime dt; + datetime_sec utc; + datetime_sec local; + int minutes; + int pi[2]; + substdio ss; + int wstat; + int pid; + + sig_pipeignore(); + + if (!argv[1]) { + substdio_putsflush(subfderr,"predate: usage: predate child\n"); + _exit(100); + } + + if (pipe(pi) == -1) { + substdio_putsflush(subfderr,"predate: fatal: unable to create pipe\n"); + _exit(111); + } + + switch(pid = fork()) { + case -1: + substdio_putsflush(subfderr,"predate: fatal: unable to fork\n"); + _exit(111); + case 0: + close(pi[1]); + if (fd_move(0,pi[0]) == -1) { + substdio_putsflush(subfderr,"predate: fatal: unable to set up fds\n"); + _exit(111); + } + sig_pipedefault(); + execvp(argv[1],argv + 1); + substdio_putsflush(subfderr,"predate: fatal: unable to exec\n"); + _exit(111); + } + close(pi[0]); + substdio_fdbuf(&ss,write,pi[1],outbuf,sizeof(outbuf)); + + time(&now); + + tm = gmtime(&now); + dt.year = tm->tm_year; + dt.mon = tm->tm_mon; + dt.mday = tm->tm_mday; + dt.hour = tm->tm_hour; + dt.min = tm->tm_min; + dt.sec = tm->tm_sec; + utc = datetime_untai(&dt); /* utc == now, if gmtime ignores leap seconds */ + + tm = localtime(&now); + dt.year = tm->tm_year; + dt.mon = tm->tm_mon; + dt.mday = tm->tm_mday; + dt.hour = tm->tm_hour; + dt.min = tm->tm_min; + dt.sec = tm->tm_sec; + local = datetime_untai(&dt); + + substdio_puts(&ss,"Date: "); + substdio_put(&ss,num,fmt_uint(num,dt.mday)); + substdio_puts(&ss," "); + substdio_puts(&ss,montab[dt.mon]); + substdio_puts(&ss," "); + substdio_put(&ss,num,fmt_uint(num,dt.year + 1900)); + substdio_puts(&ss," "); + substdio_put(&ss,num,fmt_uint0(num,dt.hour,2)); + substdio_puts(&ss,":"); + substdio_put(&ss,num,fmt_uint0(num,dt.min,2)); + substdio_puts(&ss,":"); + substdio_put(&ss,num,fmt_uint0(num,dt.sec,2)); + + if (local < utc) { + minutes = (utc - local + 30) / 60; + substdio_puts(&ss," -"); + substdio_put(&ss,num,fmt_uint0(num,minutes / 60,2)); + substdio_put(&ss,num,fmt_uint0(num,minutes % 60,2)); + } + else { + minutes = (local - utc + 30) / 60; + substdio_puts(&ss," +"); + substdio_put(&ss,num,fmt_uint0(num,minutes / 60,2)); + substdio_put(&ss,num,fmt_uint0(num,minutes % 60,2)); + } + + substdio_puts(&ss,"\n"); + substdio_copy(&ss,subfdin); + substdio_flush(&ss); + close(pi[1]); + + if (wait_pid(&wstat,pid) == -1) { + substdio_putsflush(subfderr,"predate: fatal: wait failed\n"); + _exit(111); + } + if (wait_crashed(wstat)) { + substdio_putsflush(subfderr,"predate: fatal: child crashed\n"); + _exit(111); + } + _exit(wait_exitcode(wstat)); +} diff --git a/preline.1 b/preline.1 @@ -0,0 +1,57 @@ +.TH preline 1 +.SH NAME +preline \- prepend lines to message +.SH SYNOPSIS +in +.BR .qmail\fIext : +.B | preline \fIcommand +.SH DESCRIPTION +.B preline +feeds each incoming mail message through +.IR command . +At the top of each message it inserts +a UUCP-style +.B From_ +line, a +.B Return-Path +line, and a +.B Delivered-To +line. + +.B preline +is useful for +.B procmail +and +ELM's +.BR filter , +which +do not understand the +.B qmail-command +environment variables. +.SH OPTIONS +.TP +.B \-d +Do not include the +.B Delivered-To +line. You should use this option when the +recipient of the incoming mail message is actually under remote control, +but was sent here through +.B control/virtualdomains +for manual routing. +.TP +.B \-f +Do not include the +.B From_ +line. You should use this option except for +.IR command s +that create +.I mbox +files. +.TP +.B \-r +Do not include the +.B Return-Path +line. +.SH "SEE ALSO" +mbox(5), +qmail-command(8) diff --git a/preline.c b/preline.c @@ -0,0 +1,87 @@ +#include "fd.h" +#include "sgetopt.h" +#include "readwrite.h" +#include "subfd.h" +#include "substdio.h" +#include "exit.h" +#include "fork.h" +#include "wait.h" +#include "env.h" +#include "sig.h" +#include "error.h" + +void die(e,s) int e; char *s; { substdio_putsflush(subfderr,s); _exit(e); } +void die_usage() { die(100,"preline: fatal: incorrect usage\n"); } +void die_temp() { die(111,"preline: fatal: temporary problem\n"); } +void die_read() { die(111,"preline: fatal: unable to read message\n"); } +void die_badcmd() { die(100,"preline: fatal: command not found\n"); } + +int flagufline = 1; char *ufline; +int flagrpline = 1; char *rpline; +int flagdtline = 1; char *dtline; + +substdio ssout; +char outbuf[SUBSTDIO_OUTSIZE]; +substdio ssin; +char inbuf[SUBSTDIO_INSIZE]; + +void main(argc,argv) +int argc; +char **argv; +{ + int opt; + int pi[2]; + int pid; + int wstat; + + sig_pipeignore(); + + if (!(ufline = env_get("UFLINE"))) die_usage(); + if (!(rpline = env_get("RPLINE"))) die_usage(); + if (!(dtline = env_get("DTLINE"))) die_usage(); + + while ((opt = getopt(argc,argv,"frdFRD")) != opteof) + switch(opt) + { + case 'f': flagufline = 0; break; + case 'r': flagrpline = 0; break; + case 'd': flagdtline = 0; break; + case 'F': flagufline = 1; break; + case 'R': flagrpline = 1; break; + case 'D': flagdtline = 1; break; + default: + _exit(100); + } + argc -= optind; + argv += optind; + if (!*argv) die_usage(); + + if (pipe(pi) == -1) die_temp(); + + switch(pid = fork()) + { + case -1: + die_temp(); + case 0: + close(pi[1]); + if (fd_move(0,pi[0])) die_temp(); + sig_pipedefault(); + execvp(*argv,argv); + if (error_temp(errno)) die_temp(); + die_badcmd(); + } + close(pi[0]); + + substdio_fdbuf(&ssout,write,pi[1],outbuf,sizeof(outbuf)); + substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf)); + if (flagufline) substdio_bputs(&ssout,ufline); + if (flagrpline) substdio_bputs(&ssout,rpline); + if (flagdtline) substdio_bputs(&ssout,dtline); + if (substdio_copy(&ssout,&ssin) == -2) die_read(); + substdio_flush(&ssout); + close(pi[1]); + + if (wait_pid(&wstat,pid) == -1) die_temp(); + if (wait_crashed(wstat)) die_temp(); + _exit(wait_exitcode(wstat)); +} diff --git a/prioq.c b/prioq.c @@ -0,0 +1,58 @@ +#include "alloc.h" +#include "gen_allocdefs.h" +#include "prioq.h" + +GEN_ALLOC_readyplus(prioq,struct prioq_elt,p,len,a,i,n,x,100,prioq_readyplus) + +int prioq_insert(pq,pe) +prioq *pq; +struct prioq_elt *pe; +{ + int i; + int j; + if (!prioq_readyplus(pq,1)) return 0; + j = pq->len++; + while (j) + { + i = (j - 1)/2; + if (pq->p[i].dt <= pe->dt) break; + pq->p[j] = pq->p[i]; + j = i; + } + pq->p[j] = *pe; + return 1; +} + +int prioq_min(pq,pe) +prioq *pq; +struct prioq_elt *pe; +{ + if (!pq->p) return 0; + if (!pq->len) return 0; + *pe = pq->p[0]; + return 1; +} + +void prioq_delmin(pq) +prioq *pq; +{ + int i; + int j; + int n; + if (!pq->p) return; + n = pq->len; + if (!n) return; + i = 0; + --n; + for (;;) + { + j = i + i + 2; + if (j > n) break; + if (pq->p[j - 1].dt <= pq->p[j].dt) --j; + if (pq->p[n].dt <= pq->p[j].dt) break; + pq->p[i] = pq->p[j]; + i = j; + } + pq->p[i] = pq->p[n]; + pq->len = n; +} diff --git a/prioq.h b/prioq.h @@ -0,0 +1,15 @@ +#ifndef PRIOQ_H +#define PRIOQ_H + +#include "datetime.h" +#include "gen_alloc.h" + +struct prioq_elt { datetime_sec dt; unsigned long id; } ; + +GEN_ALLOC_typedef(prioq,struct prioq_elt,p,len,a) + +extern int prioq_insert(); +extern int prioq_min(); +extern void prioq_delmin(); + +#endif diff --git a/prot.c b/prot.c @@ -0,0 +1,21 @@ +#include "hasshsgr.h" +#include "prot.h" + +/* XXX: there are more portability problems here waiting to leap out at me */ + +int prot_gid(gid) int gid; +{ +#ifdef HASSHORTSETGROUPS + short x[2]; + x[0] = gid; x[1] = 73; /* catch errors */ + if (setgroups(1,x) == -1) return -1; +#else + if (setgroups(1,&gid) == -1) return -1; +#endif + return setgid(gid); /* _should_ be redundant, but on some systems it isn't */ +} + +int prot_uid(uid) int uid; +{ + return setuid(uid); +} diff --git a/prot.h b/prot.h @@ -0,0 +1,7 @@ +#ifndef PROT_H +#define PROT_H + +extern int prot_gid(); +extern int prot_uid(); + +#endif diff --git a/qail.sh b/qail.sh @@ -0,0 +1 @@ +QMAIL/bin/maildir2mbox && exec Mail ${1+"$@"} diff --git a/qbiff.1 b/qbiff.1 @@ -0,0 +1,31 @@ +.TH qbiff 1 +.SH NAME +qbiff \- announce new mail the moment it arrives +.SH SYNOPSIS +in +.BR .qmail : +.B |qbiff +.SH DESCRIPTION +.B qbiff +writes a message to your screen +whenever a new mail message is delivered, +if you ran +.B biff y +after logging in. + +.B WARNING: +If you create a +.B .qmail +file to enable +.BR qbiff , +make sure to also add a line specifying delivery to your normal mailbox. +For example: + +.EX + /home/joe/Mailbox +.br + |qbiff +.EE +.SH "SEE ALSO" +biff(1), +dot-qmail(5) diff --git a/qbiff.c b/qbiff.c @@ -0,0 +1,113 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include <utmp.h> +#ifndef UTMP_FILE +#ifdef _PATH_UTMP +#define UTMP_FILE _PATH_UTMP +#else +#define UTMP_FILE "/etc/utmp" +#endif +#endif +#include "readwrite.h" +#include "stralloc.h" +#include "substdio.h" +#include "subfd.h" +#include "open.h" +#include "byte.h" +#include "str.h" +#include "headerbody.h" +#include "hfield.h" +#include "env.h" +#include "exit.h" + +substdio ssutmp; +char bufutmp[sizeof(struct utmp) * 16]; +int fdutmp; +substdio sstty; +char buftty[1024]; +int fdtty; + +struct utmp ut; +char line[sizeof(ut.ut_line) + 1]; +stralloc woof = {0}; +stralloc tofrom = {0}; +stralloc text = {0}; + +void doit(s,n) char *s; int n; +{ + if (!stralloc_catb(&text,s,n)) _exit(0); + if (text.len > 78) text.len = 78; +} +void dobody(h) stralloc *h; { doit(h->s,h->len); } +void doheader(h) stralloc *h; +{ + int i; + if (hfield_known(h->s,h->len) == H_SUBJECT) + { + i = hfield_skipname(h->s,h->len); + doit(h->s + i,h->len - i); + } +} +void finishheader() { ; } + +void main() +{ + char *user; + char *sender; + char *userext; + struct stat st; + int i; + + if (chdir("/dev") == -1) _exit(0); + + if (!(user = env_get("USER"))) _exit(0); + if (!(sender = env_get("SENDER"))) _exit(0); + if (!(userext = env_get("LOCAL"))) _exit(0); + if (str_len(user) > sizeof(ut.ut_name)) _exit(0); + + if (!stralloc_copys(&tofrom,"*** TO <")) _exit(0); + if (!stralloc_cats(&tofrom,userext)) _exit(0); + if (!stralloc_cats(&tofrom,"> FROM <")) _exit(0); + if (!stralloc_cats(&tofrom,sender)) _exit(0); + if (!stralloc_cats(&tofrom,">")) _exit(0); + + for (i = 0;i < tofrom.len;++i) + if ((tofrom.s[i] < 32) || (tofrom.s[i] > 126)) + tofrom.s[i] = '_'; + + if (!stralloc_copys(&text," ")) _exit(0); + if (headerbody(subfdin,doheader,finishheader,dobody) == -1) _exit(0); + + for (i = 0;i < text.len;++i) + if ((text.s[i] < 32) || (text.s[i] > 126)) + text.s[i] = '/'; + + if (!stralloc_copys(&woof,"\015\n\007")) _exit(0); + if (!stralloc_cat(&woof,&tofrom)) _exit(0); + if (!stralloc_cats(&woof,"\015\n")) _exit(0); + if (!stralloc_cat(&woof,&text)) _exit(0); + if (!stralloc_cats(&woof,"\015\n")) _exit(0); + + fdutmp = open_read(UTMP_FILE); + if (fdutmp == -1) _exit(0); + substdio_fdbuf(&ssutmp,read,fdutmp,bufutmp,sizeof(bufutmp)); + + while (substdio_get(&ssutmp,&ut,sizeof(ut)) == sizeof(ut)) + if (!str_diffn(ut.ut_name,user,sizeof(ut.ut_name))) + { + byte_copy(line,sizeof(ut.ut_line),ut.ut_line); + line[sizeof(ut.ut_line)] = 0; + if (line[0] == '/') continue; + if (!line[0]) continue; + if (line[str_chr(line,'.')]) continue; + fdtty = open_append(line); + if (fdtty == -1) continue; + if (fstat(fdtty,&st) == -1) { close(fdtty); continue; } + if (!(st.st_mode & 0100)) { close(fdtty); continue; } + if (st.st_uid != getuid()) { close(fdtty); continue; } + substdio_fdbuf(&sstty,write,fdtty,buftty,sizeof(buftty)); + substdio_putflush(&sstty,woof.s,woof.len); + close(fdtty); + } + _exit(0); +} diff --git a/qlist.1 b/qlist.1 @@ -0,0 +1,112 @@ +.TH qlist 1 +.SH NAME +qlist \- handle mailing list subscription requests +.SH SYNOPSIS +in +.BR .qmail-\fIlist-request : +.br +.B |qlist +.I user-list@host +.I user-list-request@host +.br +.B .qmail-\fIlist +.B .qmail-\fIlist-request +.B .qtemp-\fIlist +.br +.I owner +[ +.I moreinfo +] +.br +(all on one line) +.SH DESCRIPTION +.B qlist +manages a +.B qmail +mailing list. + +When +.B qlist +receives a message, +it looks through the body of the message for commands. +.B WARNING: +.B qlist +looks for a command only at the beginning of a line. +Exception: +.B qlist +also looks at +.B Subject +lines. + +.B qlist +supports two commands. +.B SUBSCRIBE +adds a new subscription to the mailing list; +.B UNSUBSCRIBE +removes a subscription. +.B qlist +looks for the subscription address in the +.BR Reply-To , +.BR From , +or +.BR Return-Path +fields in the message. + +.B qlist +inserts an acknowledgment of each action it took, +along with general help instructions, into the message. +It then forwards the message to the subscription address, +with a copy to +.IR owner . + +.BR qlist 's +general help instructions identify +.I user-list@host +as the address of the mailing list, +.I user-list-request@host +as the address of +.B qlist +itself, and +.I owner +as the address +of the mailing list owner. +If +.I moreinfo +is supplied, +it is inserted into the middle of the instructions, +surrounded by blank lines. + +.B qlist +maintains its address list in +.BR .qmail-\fIlist , +so mail to +.I user-list +will be forwarded to each subscription address. +While +.B qlist +is editing +.BR .qmail-\fIlist , +it locks +.BR .qmail-\fIlist-request . +It uses +.B .qtemp-\fIlist +as a temporary file. + +Note that +.B qlist +only manipulates lines beginning with an ampersand; +if you manually add an address without an ampersand, +it cannot be removed by +.BR qlist . + +.B qlist +automatically sets the execute bit on +.BR qmail-\fIlist , +so +.B qmail-local +will ignore any program or file instructions in +.BR qmail-\fIlist . +.SH "SEE ALSO" +dot-qmail(5), +envelopes(5), +qmail-queue(8) diff --git a/qlist.c b/qlist.c @@ -0,0 +1,333 @@ +#include "sig.h" +#include "readwrite.h" +#include "substdio.h" +#include "stralloc.h" +#include "subfd.h" +#include "getln.h" +#include "alloc.h" +#include "str.h" +#include "env.h" +#include "hfield.h" +#include "case.h" +#include "token822.h" +#include "error.h" +#include "gen_alloc.h" +#include "gen_allocdefs.h" +#include "headerbody.h" +#include "exit.h" +#include "open.h" +#include "lock.h" +#include "qmail.h" + +#define ADDRLIMIT 100 + +void die() { _exit(100); } +void die_temp() { _exit(111); } +void die_nomem() { + substdio_putsflush(subfderr,"qlist: fatal: out of memory\n"); die_temp(); } +void die_fork() { + substdio_putsflush(subfderr,"qlist: fatal: unable to fork\n"); die_temp(); } +void die_nolock() { + substdio_putsflush(subfderr,"qlist: fatal: unable to open lock file\n"); die_temp(); } +void die_boing() { + substdio_putsflush(subfderr,"qlist: fatal: I don't reply to bounces\n"); die(); } +void die_badaddr() { + substdio_putsflush(subfderr,"qlist: fatal: sorry, I'm not allowed to use that address\n"); die(); } +void die_qqperm() { + substdio_putsflush(subfderr,"qlist: fatal: permanent qmail-queue error\n"); die(); } +void die_qqtemp() { + substdio_putsflush(subfderr,"qlist: fatal: temporary qmail-queue error\n"); die_temp(); } +void die_usage() { + substdio_putsflush(subfderr, + "qlist: usage: qlist user-list@host user-list-request@host .qmail-list .qmail-list-request .qtemp-list owner [moreinfo]\n"); die(); } +void die_read() { + if (errno == error_nomem) die_nomem(); + substdio_putsflush(subfderr,"qlist: fatal: read error\n"); die_temp(); } +void doordie(sa,r) stralloc *sa; int r; { + if (r == 1) return; if (r == -1) die_nomem(); + substdio_putsflush(subfderr,"qlist: fatal: unable to parse this: "); + substdio_putflush(subfderr,sa->s,sa->len); die(); } + +int subjectaction = 0; +int numcommands; + +int fdlock; + +struct qmail qqt; + +char *target; +char *listathost; +char *requestathost; +char *qmaillist; +char *qmailrequest; +char *qtemplist; +char *owner; +char *moreinfo; + +char *dtline; +char *returnpath; +stralloc safrom = {0}; +stralloc sart = {0}; + +int rwfrom(addr) token822_alloc *addr; { token822_reverse(addr); + if (token822_unquote(&safrom,addr) != 1) die_nomem(); + token822_reverse(addr); return 1; } +int rwrt(addr) token822_alloc *addr; { token822_reverse(addr); + if (token822_unquote(&sart,addr) != 1) die_nomem(); + token822_reverse(addr); return 1; } + +GEN_ALLOC_typedef(saa,stralloc,sa,len,a) +GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus) +static stralloc sauninit = {0}; saa savedh = {0}; +void savedh_append(h) stralloc *h; { + if (!saa_readyplus(&savedh,1)) die_nomem(); savedh.sa[savedh.len] = sauninit; + if (!stralloc_copy(savedh.sa + savedh.len,h)) die_nomem(); ++savedh.len; } +void savedh_print() { int i; for (i = 0;i < savedh.len;++i) + qmail_put(&qqt,savedh.sa[i].s,savedh.sa[i].len); } + +void finishheader() +{ + int i; + + if (sart.s) + { if (!stralloc_0(&sart)) die_nomem(); target = sart.s; } + else if (safrom.s) + { if (!stralloc_0(&safrom)) die_nomem(); target = safrom.s; } + else + target = returnpath; + + for (i = 0;target[i];++i) + if (target[i] == '\n') + die_badaddr(); + if (i > ADDRLIMIT) die_badaddr(); + if (str_equal(target,"")) die_boing(); + if (str_equal(target,"#@[]")) die_boing(); + + if (qmail_open(&qqt) == -1) die_fork(); + + qmail_puts(&qqt,dtline); + savedh_print(); + + qmail_puts(&qqt,"\n***** Text inserted by "); + qmail_puts(&qqt,requestathost); + qmail_puts(&qqt,"\n\ +*\n\ +* Hi! This is the qlist program. I'm handling subscriptions for the\n\ +* "); + qmail_puts(&qqt,listathost); + qmail_puts(&qqt," mailing list.\n\ +*\n"); + if (moreinfo) + { + qmail_puts(&qqt,"* "); + qmail_puts(&qqt,moreinfo); + qmail_puts(&qqt,"\n*\n"); + } + qmail_puts(&qqt,"* My human owner is "); + qmail_puts(&qqt,owner); + qmail_puts(&qqt,".\n\ +*\n\ +* To the recipient: This message was sent to me on your behalf. (Your\n\ +* address was listed in the Reply-To or From field.) For security,\n\ +* I'm forwarding this message to you, along with my notes.\n\ +*\n\ +* Anyway, to subscribe, send me an empty message. To unsubscribe, send me\n\ +* a message with the word UNSUBSCRIBE at the beginning of a line. Remember,\n\ +* my address is "); + qmail_puts(&qqt,requestathost); + qmail_puts(&qqt,".\n\ +*\n\ +* Now I'll look for requests inside this message...\n\ +*\n\ +*****\n"); +} + +substdio subin; char subinbuf[SUBSTDIO_INSIZE]; +substdio subout; char suboutbuf[SUBSTDIO_OUTSIZE]; +stralloc subline = {0}; +void subscribe(flagadd) +int flagadd; +{ + int fdin; + int fdout; + int match; + int flagwasthere; + + ++numcommands; + + if (lock_ex(fdlock) == -1) goto bad; + fdin = open_read(qmaillist); + if (fdin == -1) goto badlock; + fdout = open_trunc(qtemplist); + if (fdout == -1) goto badinlock; + if (chmod(qtemplist,0700) == -1) goto badoutinlock; + + flagwasthere = 0; + + substdio_fdbuf(&subin,read,fdin,subinbuf,sizeof(subinbuf)); + substdio_fdbuf(&subout,write,fdout,suboutbuf,sizeof(suboutbuf)); + for (;;) + { + if (getln(&subin,&subline,&match,'\n') == -1) goto badoutinlock; + if (!match) break; /* goodbye partial lines */ + if (subline.len == str_len(target) + 2) + if (!str_diffn(subline.s + 1,target,subline.len - 2)) + if (subline.s[0] == '&') + { + flagwasthere = 1; + if (!flagadd) + continue; + } + if (substdio_put(&subout,subline.s,subline.len) == -1) goto badoutinlock; + } + + if (flagadd && !flagwasthere) + { + if (substdio_puts(&subout,"&") == -1) goto badoutinlock; + if (substdio_puts(&subout,target) == -1) goto badoutinlock; + if (substdio_puts(&subout,"\n") == -1) goto badoutinlock; + } + if (substdio_flush(&subout) == -1) goto badoutinlock; + + close(fdout); + close(fdin); + if (rename(qtemplist,qmaillist) == -1) goto badlock; + if (chmod(qmaillist,0500) == -1) goto badlock; + + lock_un(fdlock); + + qmail_puts(&qqt,"***** Text inserted by "); + qmail_puts(&qqt,requestathost); + qmail_puts(&qqt,"\n*\n* "); + if (flagadd) + if (flagwasthere) + { + qmail_puts(&qqt,"Acknowledgment: "); + qmail_puts(&qqt,target); + qmail_puts(&qqt," was already a subscriber.\n"); + } + else + { + qmail_puts(&qqt,"Acknowledgment: "); + qmail_puts(&qqt,target); + qmail_puts(&qqt," is now a subscriber.\n"); + } + else + if (flagwasthere) + { + qmail_puts(&qqt,"Acknowledgment: "); + qmail_puts(&qqt,target); + qmail_puts(&qqt," is no longer a subscriber.\n"); + } + else + { + qmail_puts(&qqt,"Hmmm, I don't see "); + qmail_puts(&qqt,target); + qmail_puts(&qqt," on the subscription list.\n* I'll let my owner know.\n"); + } + qmail_puts(&qqt,"*\n*****\n"); + return; + +badoutinlock: close(fdout); +badinlock: close(fdin); +badlock: lock_un(fdlock); +bad: + qmail_puts(&qqt,"***** Text inserted by "); + qmail_puts(&qqt,requestathost); + qmail_puts(&qqt,"\n*\n\ +* Oh no! Trouble making the new list. I'll let my owner know.\n\ +*\n\ +*****\n"); +} + +void dobody(h) stralloc *h; +{ + qmail_put(&qqt,h->s,h->len); + if (case_starts(h->s,"subs")) subscribe(1); + if (case_starts(h->s,"unsu")) subscribe(0); +} + +stralloc hfbuf = {0}; +token822_alloc hfin = {0}; +token822_alloc hfrewrite = {0}; +token822_alloc hfaddr = {0}; + +void doheaderfield(h) +stralloc *h; +{ + char *x; + switch(hfield_known(h->s,h->len)) + { + case H_CONTENTLENGTH: /* SVR4 silliness */ + case H_CONTENTTYPE: + case H_CONTENTTRANSFERENCODING: /* A-bombs 4.2.1.5.2 is idiotic */ + return; + case H_FROM: + doordie(h,token822_parse(&hfin,h,&hfbuf)); + doordie(h,token822_addrlist(&hfrewrite,&hfaddr,&hfin,rwfrom)); + break; + case H_REPLYTO: + doordie(h,token822_parse(&hfin,h,&hfbuf)); + doordie(h,token822_addrlist(&hfrewrite,&hfaddr,&hfin,rwrt)); + break; + case H_SUBJECT: + x = h->s + hfield_skipname(h->s,h->len); + if (!case_diffb(x,4,"subs")) subjectaction = 1; + if (!case_diffb(x,4,"unsu")) subjectaction = 2; + break; + } + savedh_append(h); +} + +void main(argc,argv) +int argc; +char **argv; +{ + sig_pipeignore(); + + if (!(listathost = argv[1])) die_usage(); + if (!(requestathost = argv[2])) die_usage(); + if (!(qmaillist = argv[3])) die_usage(); + if (!(qmailrequest = argv[4])) die_usage(); + if (!(qtemplist = argv[5])) die_usage(); + if (!(owner = argv[6])) die_usage(); + moreinfo = argv[7]; + if (!(returnpath = env_get("NEWSENDER"))) die_usage(); + if (!(dtline = env_get("DTLINE"))) die_usage(); + + fdlock = open_append(qmailrequest); + if (fdlock == -1) die_nolock(); + + numcommands = 0; + if (headerbody(subfdin,doheaderfield,finishheader,dobody) == -1) die_read(); + if (!numcommands) + { + qmail_puts(&qqt,"***** Text inserted by "); + qmail_puts(&qqt,requestathost); + qmail_puts(&qqt,"\n*\n* "); + if (subjectaction) + { + qmail_puts(&qqt,"\ +Hmmm, no commands? Let me check the Subject line...\n*\n*****\n"); + subscribe(subjectaction == 1); + } + else + { + qmail_puts(&qqt,"\ +I didn't see any commands. I presume this is a subscription request.\n\ +*\n*****\n"); + subscribe(1); + } + } + + qmail_from(&qqt,returnpath); + qmail_to(&qqt,owner); + qmail_to(&qqt,target); + + switch(qmail_close(&qqt)) + { + case 0: _exit(0); + case QMAIL_TOOLONG: die_qqperm(); + default: die_qqtemp(); + } +} diff --git a/qlist2.sh b/qlist2.sh @@ -0,0 +1 @@ +exec QMAIL/bin/qlist "$USERBREAK$1@$2" "$USERBREAK$1-request@$2" .qmail-"$1" .qmail-"$1"-request .qtemp-"$1" "$USERBREAK$1-owner" ${3+"$3"} diff --git a/qlx.h b/qlx.h @@ -0,0 +1,18 @@ +#ifndef QLX_H +#define QLX_H + +/* 0, 111, 100 are qmail-local success, soft, hard */ + +#define QLX_USAGE 112 +#define QLX_BUG 101 +#define QLX_ROOT 113 +#define QLX_NFS 115 +#define QLX_NOALIAS 116 +#define QLX_CDB 117 +#define QLX_SYS 118 +#define QLX_NOMEM 119 +#define QLX_EXECSOFT 120 +#define QLX_EXECPW 121 +#define QLX_EXECHARD 126 + +#endif diff --git a/qmail-clean.8 b/qmail-clean.8 @@ -0,0 +1,13 @@ +.TH qmail-clean 8 +.SH NAME +qmail-clean \- clean up the queue directory +.SH SYNOPSIS +.B qmail-clean +.SH DESCRIPTION +.B qmail-clean +reads a cleanup command from descriptor 0, +performs the cleanup, +prints the results to descriptor 1, +and repeats. +.SH "SEE ALSO" +qmail-send(8) diff --git a/qmail-clean.c b/qmail-clean.c @@ -0,0 +1,98 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include "readwrite.h" +#include "sig.h" +#include "now.h" +#include "str.h" +#include "direntry.h" +#include "getln.h" +#include "stralloc.h" +#include "substdio.h" +#include "subfd.h" +#include "byte.h" +#include "scan.h" +#include "fmt.h" +#include "error.h" +#include "exit.h" +#include "fmtqfn.h" +#include "auto_qmail.h" + +#define OSSIFIED 129600 /* see qmail-send.c */ + +stralloc line = {0}; + +void cleanuppid() +{ + DIR *dir; + direntry *d; + struct stat st; + datetime_sec time; + + time = now(); + dir = opendir("pid"); + if (!dir) return; + while (d = readdir(dir)) + { + if (str_equal(d->d_name,".")) continue; + if (str_equal(d->d_name,"..")) continue; + if (!stralloc_copys(&line,"pid/")) continue; + if (!stralloc_cats(&line,d->d_name)) continue; + if (!stralloc_0(&line)) continue; + if (stat(line.s,&st) == -1) continue; + if (time < st.st_atime + OSSIFIED) continue; + unlink(line.s); + } + closedir(dir); +} + +char fnbuf[FMTQFN]; + +void respond(s) char *s; { if (substdio_putflush(subfdoutsmall,s,1) == -1) _exit(100); } + +void main() +{ + int i; + int match; + int cleanuploop; + unsigned long id; + + if (chdir(auto_qmail) == -1) _exit(111); + if (chdir("queue") == -1) _exit(111); + + sig_pipeignore(); + + if (!stralloc_ready(&line,200)) _exit(111); + + cleanuploop = 0; + + for (;;) + { + if (cleanuploop) --cleanuploop; else { cleanuppid(); cleanuploop = 30; } + if (getln(subfdinsmall,&line,&match,'\0') == -1) break; + if (!match) break; + if (line.len < 7) { respond("x"); continue; } + if (line.len > 100) { respond("x"); continue; } + if (line.s[line.len - 1]) { respond("x"); continue; } /* impossible */ + for (i = 5;i < line.len - 1;++i) + if ((unsigned char) (line.s[i] - '0') > 9) + { respond("x"); continue; } + if (!scan_ulong(line.s + 5,&id)) { respond("x"); continue; } + if (byte_equal(line.s,5,"foop/")) + { +#define U(prefix,flag) fmtqfn(fnbuf,prefix,id,flag); \ +if (unlink(fnbuf) == -1) if (errno != error_noent) { respond("!"); continue; } + U("intd/",0) + U("mess/",1) + respond("+"); + } + else if (byte_equal(line.s,4,"todo/")) + { + U("intd/",0) + U("todo/",0) + respond("+"); + } + else + respond("x"); + } + _exit(0); +} diff --git a/qmail-command.8 b/qmail-command.8 @@ -0,0 +1,123 @@ +.TH qmail-command 8 +.SH NAME +qmail-command \- user-specified mail delivery program +.SH SYNOPSIS +in +.BR .qmail\fIext : +.B |\fIcommand +.SH DESCRIPTION +.B qmail-local +will, upon your request, +feed each incoming mail message through a program of your choice. + +When a mail message arrives, +.B qmail-local +runs +.B sh -c \fIcommand +in your home directory. +It makes the message available on +.IR command 's +standard input. + +.B WARNING: +The mail message does not begin with +.BR qmail-local 's +usual +.B Return-Path +and +.B Delivered-To +lines. + +Note that +.B qmail-local +uses the same file descriptor for every delivery +in your +.B .qmail +file, so it is not safe for +.I command +to fork a child that +reads the message in the background while the parent exits. + +.IR command 's +exit codes are interpreted as follows: +0 means that the delivery was successful; +99 means that the delivery was successful, +but that +.B qmail-local +should ignore all further delivery instructions; +100 means that the delivery failed permanently (hard error); +111 means that the delivery failed but should be tried again +in a little while (soft error). +Currently 64, 65, 70, 76, 77, 78, and 112 are considered hard errors, +and all other codes are considered soft errors, +but +.I command +should avoid relying on this. + +.B qmail-local +supplies several useful environment variables to +.IR command . +.B SENDER +is the envelope sender address. +.B NEWSENDER +is the forwarding envelope sender address, +as described in +.BR dot-qmail(5) . +.B RECIPIENT +is the envelope recipient address, +.IR local@domain . +.B USER +is +.IR user . +.B HOME +is your home directory, +.IR homedir . +.B HOST +is the +.I domain +part of the recipient address. +.B LOCAL +is the +.I local +part. +.B EXT +is the +.B .qmail +extension, +.IR ext . +.B EXT2 +is the portion of +.B EXT +following the first dash; +.B EXT3 +is the portion +following the second dash; +.B EXT4 +is the portion +following the third dash. +.B DTLINE +and +.B RPLINE +are the usual +.B Delivered-To +and +.B Return-Path +lines, +including newlines. +.B UFLINE +is the UUCP-style +.B From_ +line that +.B qmail-local +adds to +.IR mbox -format +files. + +.B WARNING: +These environment variables are not quoted. +They may contain special characters. +They are under the control of a possibly malicious remote user. +.SH "SEE ALSO" +dot-qmail(5), +envelopes(5), +qmail-local(8) diff --git a/qmail-config.sh b/qmail-config.sh @@ -0,0 +1,64 @@ +./hostname | tr '[A-Z]' '[a-z]' | ( + if read host + then + echo Your hostname is "$host". + ./dnsfq "$host" | tr '[A-Z]' '[a-z]' | ( + if read fqdn + then + echo Your host\'s fully qualified name in DNS is "$fqdn". + echo Putting "$fqdn" into control/me... + echo "$fqdn" > QMAIL/control/me + chmod 644 QMAIL/control/me + ( echo "$fqdn" | sed 's/^\([^\.]*\)\.\([^\.]*\)\./\2\./' | ( + read ddom + echo Putting "$ddom" into control/defaultdomain... + echo "$ddom" > QMAIL/control/defaultdomain + chmod 644 QMAIL/control/defaultdomain + ) ) + ( echo "$fqdn" | sed 's/^.*\.\([^\.]*\)\.\([^\.]*\)$/\1.\2/' | ( + read pdom + echo Putting "$pdom" into control/plusdomain... + echo "$pdom" > QMAIL/control/plusdomain + chmod 644 QMAIL/control/plusdomain + ) ) + echo ' ' + echo Checking local IP addresses: + : > QMAIL/control/locals + chmod 644 QMAIL/control/locals + ( ./dnsip "$fqdn" + ./ipmeprint ) | sort -u | \ + ( + while read localip + do + echo "$localip: " | tr -d '\012' + ./dnsptr "$localip" 2>/dev/null | ( + if read local + then + echo Adding "$local" to control/locals... + echo "$local" >> QMAIL/control/locals + else + echo PTR lookup failed. I assume this address has no DNS name. + fi + ) + done + ) + echo ' ' + echo If there are any other domain names that point to you, + echo you will have to add them to QMAIL/control/locals. + echo You don\'t have to worry about aliases, i.e., domains with CNAME records. + echo ' ' + echo Copying QMAIL/control/locals to QMAIL/control/rcpthosts... + cp QMAIL/control/locals QMAIL/control/rcpthosts + chmod 644 QMAIL/control/rcpthosts + echo 'Now qmail will refuse to accept SMTP messages except to those hosts.' + echo 'Make sure to change rcpthosts if you add hosts to locals or virtualdomains!' + else + echo Sorry, I couldn\'t find your host\'s canonical name in DNS. + echo You will have to set up control/me yourself. + fi + ) + else + echo Sorry, I couldn\'t find your hostname. + echo You will have to set up control/me yourself. + fi +) diff --git a/qmail-control.9 b/qmail-control.9 @@ -0,0 +1,74 @@ +.TH qmail-control 5 +.SH "NAME" +qmail-control \- qmail configuration files +.SH "INTRODUCTION" +You can change the behavior of the +.B qmail +system by modifying +.BR qmail 's +.I control files +in +.BR QMAILHOME/control . + +.B qmail +can survive with just one control file, +.IR me , +containing the +fully-qualified name of the current host. +This file is used as the default for +other hostname-related control files. + +Comments are allowed +in +.IR badmailfrom , +.IR locals , +.IR percenthack , +.IR rcpthosts , +.IR smtproutes , +and +.IR virtualdomains . +Trailing spaces and tabs are allowed in any control file. + +The following table lists all control files +other than +.IR me . +See the corresponding man pages for further details. + +.RS +.nf +.ta 5c 10c +control default used by + +.I badmailfrom \fR(none) \fRqmail-smtpd +.I bouncefrom \fRMAILER-DAEMON \fRqmail-send +.I bouncehost \fIme \fRqmail-send +.I concurrencylocal \fR10 \fRqmail-send +.I concurrencyremote \fR20 \fRqmail-send +.I defaultdomain \fIme \fRqmail-inject +.I defaulthost \fIme \fRqmail-inject +.I doublebouncehost \fIme \fRqmail-send +.I doublebounceto \fRpostmaster \fRqmail-send +.I envnoathost \fIme \fRqmail-send +.I helohost \fIme \fRqmail-remote +.I idhost \fIme \fRqmail-inject +.I localiphost \fIme \fRqmail-smtpd +.I locals \fIme \fRqmail-send +.I percenthack \fR(none) \fRqmail-send +.I plusdomain \fIme \fRqmail-inject +.I queuelifetime \fR604800 \fRqmail-send +.I rcpthosts \fR(none) \fRqmail-smtpd +.I recipientmap \fR(none) \fRqmail-send +.I smtpgreeting \fIme \fRqmail-smtpd +.I smtproutes \fR(none) \fRqmail-remote +.I timeoutconnect \fR60 \fRqmail-remote +.I timeoutremote \fR1200 \fRqmail-remote +.I timeoutsmtpd \fR1200 \fRqmail-smtpd +.I virtualdomains \fR(none) \fRqmail-send +.fi +.RE +.SH "SEE ALSO" +qmail-inject(8), +qmail-remote(8), +qmail-send(8), +qmail-showctl(8), +qmail-smtpd(8) diff --git a/qmail-getpw.9 b/qmail-getpw.9 @@ -0,0 +1,109 @@ +.TH qmail-getpw 8 +.SH NAME +qmail-getpw \- give addresses to users +.SH SYNOPSIS +.B qmail-getpw +.I local +.SH DESCRIPTION +In +.BR qmail , +each user controls a vast array of local addresses. +.B qmail-getpw +finds the user that controls a particular address, +.IR local . +It prints six pieces of information, +each terminated by NUL: +.IR user ; +.IR uid ; +.IR gid ; +.IR homedir ; +.IR dash ; +and +.IR ext . +The user's account name is +.IR user ; +the user's uid and gid in decimal are +.I uid +and +.IR gid ; +the user's home directory is +.IR homedir ; +and messages to +.I local +will be handled by +.IR homedir\fB/.qmail\fIdashext . + +In case of trouble, +.B qmail-getpw +exits nonzero without printing anything. + +.B WARNING: +The operating system's +.B getpwnam +function, which is at the heart of +.BR qmail-getpw , +is inherently unreliable. +.SH "RULES" +.B qmail-getpw +considers an account in +.B /etc/passwd +to be a user if +(1) the account has a nonzero uid, +(2) the account's home directory exists (and is visible to +.BR qmail-getpw ), +and +(3) the account owns its home directory. +.B qmail-getpw +ignores account names containing uppercase letters. +.B qmail-getpw +also assumes that all account names are shorter than 32 characters. + +.B qmail-getpw +gives each user +control over the basic +.I user +address and +all addresses of the form +.IR user\fBBREAK\fIanything . +When +.I local +is +.IR user , +.I dash +and +.I ext +are both empty. +When +.I local +is +.IR user\fBBREAK\fIanything , +.I dash +is a hyphen and +.I ext +is +.IR anything . +.I user +may appear in any combination of uppercase and lowercase letters +at the front of +.IR local . + +A catch-all user, +.BR alias , +controls all other addresses. +In this case +.I ext +is +.I local +and +.I dash +is a hyphen. + +You can override all of +.BR qmail-getpw 's +decisions with the +.B qmail-users +mechanism, which is reliable, highly configurable, and much faster than +.BR qmail-getpw . +.SH "SEE ALSO" +qmail-users(5), +qmail-lspawn(8) diff --git a/qmail-getpw.c b/qmail-getpw.c @@ -0,0 +1,86 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include <pwd.h> +#include "readwrite.h" +#include "substdio.h" +#include "subfd.h" +#include "error.h" +#include "exit.h" +#include "byte.h" +#include "str.h" +#include "case.h" +#include "fmt.h" +#include "auto_usera.h" +#include "auto_break.h" +#include "qlx.h" + +#define GETPW_USERLEN 32 + +char *local; +struct passwd *pw; +char *dash; +char *extension; + +int userext() +{ + char username[GETPW_USERLEN]; + struct stat st; + + extension = local + str_len(local); + for (;;) { + if (extension - local < sizeof(username)) + if (!*extension || (*extension == *auto_break)) { + byte_copy(username,extension - local,local); + username[extension - local] = 0; + case_lowers(username); + pw = getpwnam(username); + if (pw) + if (pw->pw_uid) + if (stat(pw->pw_dir,&st) == 0) { + if (st.st_uid == pw->pw_uid) { + dash = ""; + if (*extension) { ++extension; dash = "-"; } + return 1; + } + } + else + if (error_temp(errno)) _exit(QLX_NFS); + } + if (extension == local) return 0; + --extension; + } +} + +char num[FMT_ULONG]; + +void main(argc,argv) +int argc; +char **argv; +{ + local = argv[1]; + if (!local) _exit(100); + + if (!userext()) { + extension = local; + dash = "-"; + pw = getpwnam(auto_usera); + } + + if (!pw) _exit(QLX_NOALIAS); + + substdio_puts(subfdoutsmall,pw->pw_name); + substdio_put(subfdoutsmall,"",1); + substdio_put(subfdoutsmall,num,fmt_ulong(num,(long) pw->pw_uid)); + substdio_put(subfdoutsmall,"",1); + substdio_put(subfdoutsmall,num,fmt_ulong(num,(long) pw->pw_gid)); + substdio_put(subfdoutsmall,"",1); + substdio_puts(subfdoutsmall,pw->pw_dir); + substdio_put(subfdoutsmall,"",1); + substdio_puts(subfdoutsmall,dash); + substdio_put(subfdoutsmall,"",1); + substdio_puts(subfdoutsmall,extension); + substdio_put(subfdoutsmall,"",1); + substdio_flush(subfdoutsmall); + + _exit(0); +} diff --git a/qmail-header.5 b/qmail-header.5 @@ -0,0 +1,331 @@ +.TH qmail-header 5 +.SH NAME +qmail-header \- format of a mail message +.SH OVERVIEW +At the top of every mail message is a +highly structured +.BR header . +Many programs expect the header to carry certain information, +as described below. +The main function of +.B qmail-inject +is to make sure that each outgoing message has an appropriate header. + +For more detailed information, see RFC 822 and RFC 1123. +.SH "MESSAGE STRUCTURE" +A message contains a series of +.I header fields\fR, +a blank line, +and a +.IR body : + +.EX + Received: (qmail-queue invoked by uid 666); +.br + 30 Jul 1996 11:54:54 -0000 +.br + From: djb@silverton.berkeley.edu (D. J. Bernstein) +.br + To: fred@silverton.berkeley.edu +.br + Date: 30 Jul 1996 11:54:54 -0000 +.br + Subject: Go, Bears! +.br + +.br + I've got money on this one. How about you? +.br + +.br + ---Dan (this is the third line of the body) +.EE + +Each header field has a +.IR name , +a colon, +some +.IR contents , +and a newline: + +.EX + Subject: Go, Bears! +.EE + +The field contents may be folded across several lines. +Each line past the first must begin with a space or tab: + +.EX + Received: (qmail-queue invoked by uid 666); +.br + 30 Jul 1996 11:54:54 -0000 +.EE + +The field name must not contain spaces, tabs, or colons. +Also, an empty field name is illegal. +.B qmail-inject +does not allow field names with unprintable characters. + +Case is irrelevant in field names: +.B subject +and +.B SUBJECT +and +.B SuBjEcT +have the same meaning. +.SH "ADDRESS LISTS" +Certain fields, such as +.BR To , +contain +.I address lists\fR. + +An address list contains some number of +.I addresses +or +.I address groups\fR, +separated by commas: + +.EX + a@b, c@d (Somebody), A Person <e@f>, +.br + random group: g@h, i@j;, k@l +.EE + +An +.I address group +has some text, a colon, a list of addresses, +and a semicolon: + +.EX + random group: g@h, i@j; +.EE + +An address can appear in several forms. +The most common form is +.IR box@host . + +Every address must include a host name. +If +.B qmail-inject +sees a lone box name +it adds the +.I default host name\fR. + +All host names should be fully qualified. +.B qmail-inject +appends the +.I default domain name +to any name without dots: + +.EX + djb@silverton -> djb@silverton.berkeley.edu +.EE + +It appends the +.I plus domain name +to any name +that ends with a plus sign: + +.EX + eric@mammoth.cs+ -> eric@mammoth.cs.berkeley.edu +.EE + +A host name may be a dotted-decimal address: + +.EX + djb@[128.32.183.163] +.EE + +RFC 822 allows mailbox names inside angle brackets +to include +.I source routes\fR, +but +.B qmail-inject +strips all source routes out of addresses. +.SH "SENDER ADDRESSES" +.B qmail-inject +looks for sender address lists in the following fields: +.BR Sender , +.BR From , +.BR Reply-To , +.BR Return-Path , +.BR Return-Receipt-To , +.BR Errors-To , +.BR Resent-Sender , +.BR Resent-From , +.BR Resent-Reply-To . + +If there is no +.B From +field, +.B qmail-inject +adds a new +.B From +field with the name of the user invoking +.B qmail-inject. + +RFC 822 requires that certain sender fields contain +only a single address, but +.B qmail-inject +does not enforce this restriction. +.SH "RECIPIENT ADDRESSES" +.B qmail-inject +looks for recipient address lists in the following fields: +.BR To , +.BR Cc , +.BR Bcc , +.BR Apparently-To , +.BR Resent-To , +.BR Resent-Cc , +.BR Resent-Bcc . + +Every message must contain at least one +.B To +or +.B Cc +or +.BR Bcc . +.B qmail-inject +deletes any +.B Bcc +field. +If there is no +.B To +or +.B Cc +field, +.B qmail-inject +adds a line + +.EX + Cc: recipient list not shown: ; +.EE + +This complies with RFC 822; +it also works around some strange +.B sendmail +behavior, in case the message is passed through +.B sendmail +on another machine. +.SH STAMPS +Every message must contain a +.B Date +field, with the date in a strict format defined by RFC 822. +If necessary +.B qmail-inject +creates a new +.B Date +field with the current date (in GMT). + +Every message should contain a +.B Message-Id +field. +The field contents are a unique worldwide identifier for this message. +If necessary +.B qmail-inject +creates a new +.B Message-Id +field. + +Another important field is +.BR Received . +Every time the message is sent from one system to another, +a new +.B Received +field is added to the top of the message. +.B qmail-inject +does not create any +.B Received +fields. +.SH "RESENT MESSAGES" +A message is +.I forwarded +if it contains any of the following fields: +.BR Resent-Sender , +.BR Resent-From , +.BR Resent-Reply-To , +.BR Resent-To , +.BR Resent-Cc , +.BR Resent-Bcc , +.BR Resent-Date , +.BR Resent-Message-ID . + +If a message is forwarded, +.B qmail-inject +changes its behavior as follows. + +It deletes any +.B Resent-Bcc +field (as well as any +.B Bcc +field); +if there are no +.B Resent-To +or +.B Resent-Cc +fields, +.B qmail-inject +adds an appropriate +.B Resent-Cc +line. +It does +.I not +add a +.B Cc +line, +even if neither +.B To +nor +.B Cc +is present. + +If there is no +.B Resent-From +field, +.B qmail-inject +adds a new +.B Resent-From +field. +It does +.I not +add a new +.B From +field. + +.B qmail-inject +adds +.B Resent-Date +if one is not already present; +same for +.BR Resent-Message-Id . +It does +.I not +add new +.B Date +or +.B Message-Id +fields. +.SH "OTHER FEATURES" +Addresses are separated by commas, not spaces. +When +.B qmail-inject +sees an illegal space, +it inserts a comma: + +.EX + djb fred -> djb, fred +.EE + +.B qmail-inject +removes all +.B Return-Path +header fields. + +.B qmail-inject +also removes any +.B Content-Length +fields. +.SH "SEE ALSO" +addresses(5), +envelopes(5), +qmail-inject(8) diff --git a/qmail-hier.c b/qmail-hier.c @@ -0,0 +1,248 @@ +#include "subfd.h" +#include "substdio.h" +#include "auto_split.h" +#include "auto_uids.h" +#include "fmt.h" + +char strnum[FMT_ULONG]; + +void uidgid(uid) +int uid; +{ + substdio_put(subfdout,strnum,fmt_ulong(strnum,(unsigned long) uid)); + substdio_puts(subfdout,":"); + substdio_put(subfdout,strnum,fmt_ulong(strnum,(unsigned long) auto_gidq)); + substdio_puts(subfdout,":"); +} + +void copy(uid,mode,sub,fn) +int uid; +char *mode; +char *sub; +char *fn; +{ + substdio_puts(subfdout,"c:"); + uidgid(uid); + substdio_puts(subfdout,mode); + substdio_puts(subfdout,":"); + substdio_puts(subfdout,sub); + substdio_puts(subfdout,":"); + substdio_puts(subfdout,fn); + substdio_puts(subfdout,":\n"); +} + +void dir(uid,mode,fn) +int uid; +char *mode; +char *fn; +{ + substdio_puts(subfdout,"d:"); + uidgid(uid); + substdio_puts(subfdout,mode); + substdio_puts(subfdout,":"); + substdio_puts(subfdout,fn); + substdio_puts(subfdout,"::\n"); +} + +void dirsplit(uid,mode,fn) +int uid; +char *mode; +char *fn; +{ + unsigned long i; + dir(uid,mode,fn); + for (i = 0;i < auto_split;++i) { + substdio_puts(subfdout,"d:"); + uidgid(uid); + substdio_puts(subfdout,mode); + substdio_puts(subfdout,":"); + substdio_puts(subfdout,fn); + substdio_puts(subfdout,":/"); + substdio_put(subfdout,strnum,fmt_ulong(strnum,i)); + substdio_puts(subfdout,":\n"); + } +} + +void main() +{ + dir(auto_uido,"755",""); + dir(auto_uido,"755","/control"); + dir(auto_uido,"755","/users"); + dir(auto_uido,"755","/bin"); + dir(auto_uido,"755","/man"); + dir(auto_uido,"755","/man/cat1"); + dir(auto_uido,"755","/man/cat5"); + dir(auto_uido,"755","/man/cat7"); + dir(auto_uido,"755","/man/cat8"); + dir(auto_uido,"755","/man/man1"); + dir(auto_uido,"755","/man/man5"); + dir(auto_uido,"755","/man/man7"); + dir(auto_uido,"755","/man/man8"); + + dir(auto_uida,"755","/alias"); + dir(auto_uidq,"750","/queue"); + dir(auto_uidq,"700","/queue/pid"); + dir(auto_uidq,"700","/queue/intd"); + dir(auto_uidq,"750","/queue/todo"); + dir(auto_uidq,"750","/queue/lock"); + dir(auto_uids,"700","/queue/bounce"); + + substdio_puts(subfdout,"z0:"); + uidgid(auto_uids); + substdio_puts(subfdout,"600:/queue/lock/:sendmutex:\n"); + + substdio_puts(subfdout,"z1024:"); + uidgid(auto_uidr); + substdio_puts(subfdout,"644:/queue/lock/:tcpto:\n"); + + substdio_puts(subfdout,"p:"); + uidgid(auto_uids); + substdio_puts(subfdout,"622:/queue/lock/:trigger:\n"); + + dirsplit(auto_uidq,"750","/queue/mess"); + dirsplit(auto_uids,"700","/queue/info"); + dirsplit(auto_uids,"700","/queue/local"); + dirsplit(auto_uids,"700","/queue/remote"); + + copy(auto_uidq,"4711","/bin/","qmail-queue"); + copy(auto_uido,"700","/bin/","qmail-lspawn"); + copy(auto_uido,"700","/bin/","qmail-start"); + copy(auto_uido,"711","/bin/","qmail-getpw"); + copy(auto_uido,"711","/bin/","qmail-local"); + copy(auto_uido,"711","/bin/","qmail-remote"); + copy(auto_uido,"711","/bin/","qmail-rspawn"); + copy(auto_uido,"711","/bin/","qmail-clean"); + copy(auto_uido,"711","/bin/","qmail-send"); + copy(auto_uido,"711","/bin/","splogger"); + copy(auto_uido,"700","/bin/","qmail-newu"); + copy(auto_uido,"711","/bin/","qmail-pw2u"); + copy(auto_uido,"755","/bin/","qmail-inject"); + copy(auto_uido,"755","/bin/","predate"); + copy(auto_uido,"755","/bin/","datemail"); + copy(auto_uido,"755","/bin/","mailsubj"); + copy(auto_uido,"755","/bin/","qmail-showctl"); + copy(auto_uido,"755","/bin/","qmail-qread"); + copy(auto_uido,"755","/bin/","qmail-qstat"); + copy(auto_uido,"755","/bin/","qmail-tcpto"); + copy(auto_uido,"755","/bin/","qmail-pop3d"); + copy(auto_uido,"700","/bin/","qmail-popup"); + copy(auto_uido,"755","/bin/","qmail-qmtpd"); + copy(auto_uido,"755","/bin/","qmail-smtpd"); + copy(auto_uido,"755","/bin/","sendmail"); + copy(auto_uido,"755","/bin/","tcp-env"); + copy(auto_uido,"755","/bin/","qlist"); + copy(auto_uido,"755","/bin/","qlist2"); + copy(auto_uido,"755","/bin/","qreceipt"); + copy(auto_uido,"755","/bin/","qsmhook"); + copy(auto_uido,"755","/bin/","qbiff"); + copy(auto_uido,"755","/bin/","forward"); + copy(auto_uido,"755","/bin/","preline"); + copy(auto_uido,"755","/bin/","condredirect"); + copy(auto_uido,"755","/bin/","maildirmake"); + copy(auto_uido,"755","/bin/","maildir2mbox"); + copy(auto_uido,"755","/bin/","maildirwatch"); + copy(auto_uido,"755","/bin/","qail"); + copy(auto_uido,"755","/bin/","elq"); + copy(auto_uido,"755","/bin/","pinq"); + + copy(auto_uido,"644","/man/man5/","addresses.5"); + copy(auto_uido,"644","/man/cat5/","addresses.0"); + copy(auto_uido,"644","/man/man5/","envelopes.5"); + copy(auto_uido,"644","/man/cat5/","envelopes.0"); + copy(auto_uido,"644","/man/man5/","maildir.5"); + copy(auto_uido,"644","/man/cat5/","maildir.0"); + copy(auto_uido,"644","/man/man5/","mbox.5"); + copy(auto_uido,"644","/man/cat5/","mbox.0"); + copy(auto_uido,"644","/man/man5/","dot-qmail.5"); + copy(auto_uido,"644","/man/cat5/","dot-qmail.0"); + copy(auto_uido,"644","/man/man5/","qmail-control.5"); + copy(auto_uido,"644","/man/cat5/","qmail-control.0"); + copy(auto_uido,"644","/man/man5/","qmail-header.5"); + copy(auto_uido,"644","/man/cat5/","qmail-header.0"); + copy(auto_uido,"644","/man/man5/","qmail-log.5"); + copy(auto_uido,"644","/man/cat5/","qmail-log.0"); + copy(auto_uido,"644","/man/man5/","qmail-users.5"); + copy(auto_uido,"644","/man/cat5/","qmail-users.0"); + copy(auto_uido,"644","/man/man5/","tcp-environ.5"); + copy(auto_uido,"644","/man/cat5/","tcp-environ.0"); + + copy(auto_uido,"644","/man/man7/","forgeries.7"); + copy(auto_uido,"644","/man/cat7/","forgeries.0"); + copy(auto_uido,"644","/man/man7/","qmail-limits.7"); + copy(auto_uido,"644","/man/cat7/","qmail-limits.0"); + copy(auto_uido,"644","/man/man7/","qmail-upgrade.7"); + copy(auto_uido,"644","/man/cat7/","qmail-upgrade.0"); + copy(auto_uido,"644","/man/man7/","qmail.7"); + copy(auto_uido,"644","/man/cat7/","qmail.0"); + + copy(auto_uido,"644","/man/man1/","forward.1"); + copy(auto_uido,"644","/man/cat1/","forward.0"); + copy(auto_uido,"644","/man/man1/","condredirect.1"); + copy(auto_uido,"644","/man/cat1/","condredirect.0"); + copy(auto_uido,"644","/man/man1/","maildirmake.1"); + copy(auto_uido,"644","/man/cat1/","maildirmake.0"); + copy(auto_uido,"644","/man/man1/","maildir2mbox.1"); + copy(auto_uido,"644","/man/cat1/","maildir2mbox.0"); + copy(auto_uido,"644","/man/man1/","maildirwatch.1"); + copy(auto_uido,"644","/man/cat1/","maildirwatch.0"); + copy(auto_uido,"644","/man/man1/","mailsubj.1"); + copy(auto_uido,"644","/man/cat1/","mailsubj.0"); + copy(auto_uido,"644","/man/man1/","qlist.1"); + copy(auto_uido,"644","/man/cat1/","qlist.0"); + copy(auto_uido,"644","/man/man1/","qreceipt.1"); + copy(auto_uido,"644","/man/cat1/","qreceipt.0"); + copy(auto_uido,"644","/man/man1/","qbiff.1"); + copy(auto_uido,"644","/man/cat1/","qbiff.0"); + copy(auto_uido,"644","/man/man1/","preline.1"); + copy(auto_uido,"644","/man/cat1/","preline.0"); + copy(auto_uido,"644","/man/man1/","tcp-env.1"); + copy(auto_uido,"644","/man/cat1/","tcp-env.0"); + + copy(auto_uido,"644","/man/man8/","qmail-local.8"); + copy(auto_uido,"644","/man/cat8/","qmail-local.0"); + copy(auto_uido,"644","/man/man8/","qmail-lspawn.8"); + copy(auto_uido,"644","/man/cat8/","qmail-lspawn.0"); + copy(auto_uido,"644","/man/man8/","qmail-getpw.8"); + copy(auto_uido,"644","/man/cat8/","qmail-getpw.0"); + copy(auto_uido,"644","/man/man8/","qmail-remote.8"); + copy(auto_uido,"644","/man/cat8/","qmail-remote.0"); + copy(auto_uido,"644","/man/man8/","qmail-rspawn.8"); + copy(auto_uido,"644","/man/cat8/","qmail-rspawn.0"); + copy(auto_uido,"644","/man/man8/","qmail-clean.8"); + copy(auto_uido,"644","/man/cat8/","qmail-clean.0"); + copy(auto_uido,"644","/man/man8/","qmail-send.8"); + copy(auto_uido,"644","/man/cat8/","qmail-send.0"); + copy(auto_uido,"644","/man/man8/","qmail-start.8"); + copy(auto_uido,"644","/man/cat8/","qmail-start.0"); + copy(auto_uido,"644","/man/man8/","splogger.8"); + copy(auto_uido,"644","/man/cat8/","splogger.0"); + copy(auto_uido,"644","/man/man8/","qmail-queue.8"); + copy(auto_uido,"644","/man/cat8/","qmail-queue.0"); + copy(auto_uido,"644","/man/man8/","qmail-inject.8"); + copy(auto_uido,"644","/man/cat8/","qmail-inject.0"); + copy(auto_uido,"644","/man/man8/","qmail-showctl.8"); + copy(auto_uido,"644","/man/cat8/","qmail-showctl.0"); + copy(auto_uido,"644","/man/man8/","qmail-newu.8"); + copy(auto_uido,"644","/man/cat8/","qmail-newu.0"); + copy(auto_uido,"644","/man/man8/","qmail-pw2u.8"); + copy(auto_uido,"644","/man/cat8/","qmail-pw2u.0"); + copy(auto_uido,"644","/man/man8/","qmail-qread.8"); + copy(auto_uido,"644","/man/cat8/","qmail-qread.0"); + copy(auto_uido,"644","/man/man8/","qmail-qstat.8"); + copy(auto_uido,"644","/man/cat8/","qmail-qstat.0"); + copy(auto_uido,"644","/man/man8/","qmail-tcpto.8"); + copy(auto_uido,"644","/man/cat8/","qmail-tcpto.0"); + copy(auto_uido,"644","/man/man8/","qmail-pop3d.8"); + copy(auto_uido,"644","/man/cat8/","qmail-pop3d.0"); + copy(auto_uido,"644","/man/man8/","qmail-popup.8"); + copy(auto_uido,"644","/man/cat8/","qmail-popup.0"); + copy(auto_uido,"644","/man/man8/","qmail-qmtpd.8"); + copy(auto_uido,"644","/man/cat8/","qmail-qmtpd.0"); + copy(auto_uido,"644","/man/man8/","qmail-smtpd.8"); + copy(auto_uido,"644","/man/cat8/","qmail-smtpd.0"); + copy(auto_uido,"644","/man/man8/","qmail-command.8"); + copy(auto_uido,"644","/man/cat8/","qmail-command.0"); + + substdio_flush(subfdout); + _exit(0); +} diff --git a/qmail-inject.8 b/qmail-inject.8 @@ -0,0 +1,294 @@ +.TH qmail-inject 8 +.SH NAME +qmail-inject \- preprocess and send a mail message +.SH SYNOPSIS +.B qmail-inject +[ +.B \-nNaAhH +] [ +.B \-f\fIsender +] [ +.I recip ... +] +.SH DESCRIPTION +.B qmail-inject +reads a mail message from its standard input, +adds appropriate information to the message header, +and invokes +.B qmail-queue +to send the message +to one or more recipients. + +See +.B qmail-header(5) +for information on how +.B qmail-inject +rewrites header fields. + +.B qmail-inject +normally exits 0. +It exits 100 if it was invoked improperly +or if there is a severe syntax error in the message. +It exits 111 for temporary errors. +.SH "ENVIRONMENT VARIABLES" +For the convenience of users who do not run +.B qmail-inject +directly, +.B qmail-inject +takes many options through environment variables. + +The user name in the +.B From +header field is set by +.BR QMAILUSER , +.BR MAILUSER , +.BR USER , +or +.BR LOGNAME , +whichever comes first. + +The host name is normally set by the +.I defaulthost +control +but can be overridden with +.B QMAILHOST +or +.BR MAILHOST . + +The personal name is +.BR QMAILNAME , +.BR MAILNAME , +or +.BR NAME . + +The default envelope sender address is the same as the +default +.B From +address, +but it can be overridden with +.B QMAILSUSER +and +.BR QMAILSHOST . +It may also be modified by the +.B r +and +.B m +letters described below. +Bounces will be sent to this address. + +The +.B QMAILINJECT +environment variable +can contain any of the following letters: +.TP +.B c +Use address-comment style for the +.B From +field. +Normally +.B qmail-inject +uses name-address style. +.TP +.B s +Do not look at any incoming +.B Return-Path +field. +Normally, if +.B Return-Path +is supplied, it sets the envelope sender address, +overriding all environment variables. +.B Return-Path +is deleted in any case. +.TP +.B f +Delete any incoming +.B From +field. +Normally, if +.B From +is supplied, it overrides the usual +.B From +field created by +.BR qmail-inject . +.TP +.B i +Delete any incoming +.B Message-ID +field. +Normally, if +.B Message-ID +is supplied, it overrides the usual +.B Message-ID +field created by +.BR qmail-inject . +.TP +.B r +Use a per-recipient VERP. +.B qmail-inject +will append each recipient address to the envelope sender +of the copy going to that recipient. +.TP +.B m +Use a per-message VERP. +.B qmail-inject +will append the current date and process ID to the envelope sender. +.SH OPTIONS +.TP +.B \-a +Send the message to all addresses given as +.I recip +arguments; +do not use header recipient addresses. +.TP +.B \-h +Send the message to all header recipient addresses. +For non-forwarded messages, this means +the addresses listed under +.BR To , +.BR Cc , +.BR Bcc , +.BR Apparently-To . +For forwarded messages, this means +the addresses listed under +.BR Resent-To , +.BR Resent-Cc , +.BR Resent-Bcc . +Do not use any +.I recip +arguments. +.TP +.B \-A +(Default.) +Send the message to all addresses given as +.I recip +arguments. +If no +.I recip +arguments are supplied, +send the message to all header recipient addresses. +.TP +.B \-H +Send the message to all header recipient addresses, +and to all addresses given as +.I recip +arguments. +.TP +.B \-f\fIsender +Pass +.I sender +to +.B qmail-queue +as the envelope sender address. +This overrides +.B Return-Path +and all environment variables. +.TP +.B \-N +(Default.) +Feed the resulting message to +.BR qmail-queue . +.TP +.B \-n +Print the message rather than feeding it to +.BR qmail-queue . +.SH "CONTROL FILES" +.TP 5 +.I defaultdomain +Default domain name. +Default: +.IR me , +if that is supplied; +otherwise the literal name +.BR defaultdomain , +which is probably not what you want. +.B qmail-inject +adds this name to any host name without dots, +including +.I defaulthost +if +.I defaulthost +does not have dots. +(Exception: see +.IR plusdomain .) + +The +.B QMAILDEFAULTDOMAIN +environment variable +overrides +.IR defaultdomain . +.TP 5 +.I defaulthost +Default host name. +Default: +.IR me , +if that is supplied; +otherwise the literal name +.BR defaulthost , +which is probably not what you want. +.B qmail-inject +adds this name to any address without a host name. +.I defaulthost +need not be the current host's name. +For example, +you may prefer that outgoing mail show +just your domain name. + +The +.B QMAILDEFAULTHOST +environment variable overrides +.IR defaulthost . +.TP 5 +.I idhost +Host name for Message-IDs. +Default: +.IR me , +if that is supplied; +otherwise the literal name +.BR idhost , +which is certainly not what you want. +.I idhost +need not be the current host's name. +For example, you may prefer to use fake +host names in Message-IDs. +However, +.I idhost +must be a fully-qualified name within your domain, +and each host in your domain should use a different +.IR idhost . + +The +.B QMAILIDHOST +environment variable overrides +.IR idhost . +.TP 5 +.I plusdomain +Plus domain name. +Default: +.IR me , +if that is supplied; +otherwise the literal name +.BR plusdomain , +which is probably not what you want. +.B qmail-inject +adds this name to any host name that ends with a plus sign, +including +.I defaulthost +if +.I defaulthost +ends with a plus sign. +If a host name does not have dots but ends with a plus sign, +.B qmail-inject +uses +.IR plusdomain , +not +.IR defaultdomain . + +The +.B QMAILPLUSDOMAIN +environment variable overrides +.IR plusdomain . +.SH "SEE ALSO" +addresses(5), +qmail-control(5), +qmail-header(5), +qmail-queue(8) diff --git a/qmail-inject.c b/qmail-inject.c @@ -0,0 +1,735 @@ +#include "sig.h" +#include "substdio.h" +#include "stralloc.h" +#include "subfd.h" +#include "sgetopt.h" +#include "getln.h" +#include "alloc.h" +#include "str.h" +#include "fmt.h" +#include "hfield.h" +#include "token822.h" +#include "control.h" +#include "env.h" +#include "gen_alloc.h" +#include "gen_allocdefs.h" +#include "error.h" +#include "qmail.h" +#include "now.h" +#include "exit.h" +#include "quote.h" +#include "headerbody.h" +#include "auto_qmail.h" +#include "newfield.h" + +#define LINELEN 80 + +datetime_sec starttime; + +char *qmopts; +int flagdeletesender = 0; +int flagdeletefrom = 0; +int flagdeletemessid = 0; +int flagnamecomment = 0; +int flaghackmess = 0; +int flaghackrecip = 0; +char *mailhost; +char *mailuser; +int mailusertokentype; +char *mailrhost; +char *mailruser; + +stralloc control_idhost = {0}; +stralloc control_defaultdomain = {0}; +stralloc control_defaulthost = {0}; +stralloc control_plusdomain = {0}; + +stralloc sender = {0}; +stralloc envsbuf = {0}; +token822_alloc envs = {0}; +int flagrh; + +int flagqueue; +struct qmail qqt; + +void put(s,len) char *s; int len; +{ if (flagqueue) qmail_put(&qqt,s,len); else substdio_put(subfdout,s,len); } +void puts(s) char *s; { put(s,str_len(s)); } + +void perm() { _exit(100); } +void temp() { _exit(111); } +void die_nomem() { + substdio_putsflush(subfderr,"qmail-inject: fatal: out of memory\n"); temp(); } +void die_invalid(sa) stralloc *sa; { + substdio_putsflush(subfderr,"qmail-inject: fatal: invalid header field: "); + substdio_putflush(subfderr,sa->s,sa->len); perm(); } +void die_exec() { + substdio_putsflush(subfderr,"qmail-inject: fatal: unable to exec qmail-queue\n"); temp(); } +void die_qqt() { + substdio_putsflush(subfderr,"qmail-inject: fatal: unable to run qmail-queue\n"); temp(); } +void die_chdir() { + substdio_putsflush(subfderr,"qmail-inject: fatal: internal bug\n"); temp(); } +void die_bug() { + substdio_putsflush(subfderr,"qmail-inject: fatal: internal bug\n"); temp(); } +void die_read() { + if (errno == error_nomem) die_nomem(); + substdio_putsflush(subfderr,"qmail-inject: fatal: read error\n"); temp(); } +void doordie(sa,r) stralloc *sa; int r; { + if (r == 1) return; if (r == -1) die_nomem(); + substdio_putsflush(subfderr,"qmail-inject: fatal: unable to parse this line:\n"); + substdio_putflush(subfderr,sa->s,sa->len); perm(); } + +void die_comm() { + substdio_putsflush(subfderr,"qmail-inject: fatal: qmail-queue lost communications link\n"); temp(); } +void die_qq() { + substdio_putsflush(subfderr,"qmail-inject: fatal: qmail-queue died\n"); temp(); } +void die_qqwrite() { + substdio_putsflush(subfderr,"qmail-inject: fatal: qmail-queue unable to write message to disk; disk full?\n"); temp(); } +void die_qqsig() { + substdio_putsflush(subfderr,"qmail-inject: fatal: qmail-queue was killed\n"); temp(); } +void die_qqtimeout() { + substdio_putsflush(subfderr,"qmail-inject: fatal: qmail-queue timed out\n"); temp(); } +void die_qqtoolong() { + substdio_putsflush(subfderr,"qmail-inject: fatal: qmail-queue unhappy with long addresses\n"); perm(); } + +GEN_ALLOC_typedef(saa,stralloc,sa,len,a) +GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus) + +static stralloc sauninit = {0}; + +saa savedh = {0}; +saa hrlist = {0}; +saa hrrlist = {0}; +saa reciplist = {0}; +int flagresent; + +void exitnicely() +{ + if (!flagqueue) substdio_flush(subfdout); + + if (flagqueue) + { + int i; + + if (!stralloc_0(&sender)) die_nomem(); + qmail_from(&qqt,sender.s); + + for (i = 0;i < reciplist.len;++i) + { + if (!stralloc_0(&reciplist.sa[i])) die_nomem(); + qmail_to(&qqt,reciplist.sa[i].s); + } + if (flagrh) + if (flagresent) + for (i = 0;i < hrrlist.len;++i) + { + if (!stralloc_0(&hrrlist.sa[i])) die_nomem(); + qmail_to(&qqt,hrrlist.sa[i].s); + } + else + for (i = 0;i < hrlist.len;++i) + { + if (!stralloc_0(&hrlist.sa[i])) die_nomem(); + qmail_to(&qqt,hrlist.sa[i].s); + } + + switch(qmail_close(&qqt)) + { + case 0: break; + case QMAIL_CRASHED: die_qqsig(); + case QMAIL_USAGE: case QMAIL_BUG: die_bug(); + case QMAIL_EXECSOFT: die_exec(); + case QMAIL_NOMEM: die_nomem(); + case QMAIL_READ: die_comm(); + case QMAIL_WRITE: die_qqwrite(); + case QMAIL_TOOLONG: die_qqtoolong(); + case QMAIL_TIMEOUT: die_qqtimeout(); + default: die_qq(); + } + } + + _exit(0); +} + +void savedh_append(h) +stralloc *h; +{ + if (!saa_readyplus(&savedh,1)) die_nomem(); + savedh.sa[savedh.len] = sauninit; + if (!stralloc_copy(savedh.sa + savedh.len,h)) die_nomem(); + ++savedh.len; +} + +void savedh_print() +{ + int i; + + for (i = 0;i < savedh.len;++i) + put(savedh.sa[i].s,savedh.sa[i].len); +} + +stralloc defaultdomainbuf = {0}; +token822_alloc defaultdomain = {0}; +stralloc defaulthostbuf = {0}; +token822_alloc defaulthost = {0}; +stralloc plusdomainbuf = {0}; +token822_alloc plusdomain = {0}; + +void rwroute(addr) +token822_alloc *addr; +{ + if (addr->t[addr->len - 1].type == TOKEN822_AT) + while (addr->len) + if (addr->t[--addr->len].type == TOKEN822_COLON) + return; +} + +void rwextraat(addr) +token822_alloc *addr; +{ + int i; + if (addr->t[0].type == TOKEN822_AT) + { + --addr->len; + for (i = 0;i < addr->len;++i) + addr->t[i] = addr->t[i + 1]; + } +} + +void rwextradot(addr) +token822_alloc *addr; +{ + int i; + if (addr->t[0].type == TOKEN822_DOT) + { + --addr->len; + for (i = 0;i < addr->len;++i) + addr->t[i] = addr->t[i + 1]; + } +} + +void rwnoat(addr) +token822_alloc *addr; +{ + int i; + int shift; + + for (i = 0;i < addr->len;++i) + if (addr->t[i].type == TOKEN822_AT) + return; + shift = defaulthost.len; + if (!token822_readyplus(addr,shift)) die_nomem(); + for (i = addr->len - 1;i >= 0;--i) + addr->t[i + shift] = addr->t[i]; + addr->len += shift; + for (i = 0;i < shift;++i) + addr->t[i] = defaulthost.t[shift - 1 - i]; +} + +void rwnodot(addr) +token822_alloc *addr; +{ + int i; + int shift; + for (i = 0;i < addr->len;++i) + { + if (addr->t[i].type == TOKEN822_DOT) + return; + if (addr->t[i].type == TOKEN822_AT) + break; + } + for (i = 0;i < addr->len;++i) + { + if (addr->t[i].type == TOKEN822_LITERAL) + return; + if (addr->t[i].type == TOKEN822_AT) + break; + } + shift = defaultdomain.len; + if (!token822_readyplus(addr,shift)) die_nomem(); + for (i = addr->len - 1;i >= 0;--i) + addr->t[i + shift] = addr->t[i]; + addr->len += shift; + for (i = 0;i < shift;++i) + addr->t[i] = defaultdomain.t[shift - 1 - i]; +} + +void rwplus(addr) +token822_alloc *addr; +{ + int i; + int shift; + + if (addr->t[0].type != TOKEN822_ATOM) return; + if (!addr->t[0].slen) return; + if (addr->t[0].s[addr->t[0].slen - 1] != '+') return; + + --addr->t[0].slen; /* remove + */ + + shift = plusdomain.len; + if (!token822_readyplus(addr,shift)) die_nomem(); + for (i = addr->len - 1;i >= 0;--i) + addr->t[i + shift] = addr->t[i]; + addr->len += shift; + for (i = 0;i < shift;++i) + addr->t[i] = plusdomain.t[shift - 1 - i]; +} + +void rwgeneric(addr) +token822_alloc *addr; +{ + if (!addr->len) return; /* don't rewrite <> */ + if (addr->len >= 2) + if (addr->t[1].type == TOKEN822_AT) + if (addr->t[0].type == TOKEN822_LITERAL) + if (!addr->t[0].slen) /* don't rewrite <foo@[]> */ + return; + rwroute(addr); + if (!addr->len) return; /* <@foo:> -> <> */ + rwextradot(addr); + if (!addr->len) return; /* <.> -> <> */ + rwextraat(addr); + if (!addr->len) return; /* <@> -> <> */ + rwnoat(addr); + rwplus(addr); + rwnodot(addr); +} + +int setreturn(addr) +token822_alloc *addr; +{ + if (!sender.s) + { + token822_reverse(addr); + if (token822_unquote(&sender,addr) != 1) die_nomem(); + if (flaghackrecip) + if (!stralloc_cats(&sender,"-@[]")) die_nomem(); + token822_reverse(addr); + } + return 1; +} + +int rwreturn(addr) +token822_alloc *addr; +{ + rwgeneric(addr); + setreturn(addr); + return 1; +} + +int rwsender(addr) +token822_alloc *addr; +{ + rwgeneric(addr); + return 1; +} + +void rwrecip(addr,xl) +token822_alloc *addr; +saa *xl; +{ + rwgeneric(addr); + token822_reverse(addr); + if (!saa_readyplus(xl,1)) die_nomem(); + xl->sa[xl->len] = sauninit; + if (token822_unquote(&xl->sa[xl->len],addr) != 1) die_nomem(); + ++xl->len; + token822_reverse(addr); +} + +int rwhrr(addr) token822_alloc *addr; +{ rwrecip(addr,&hrrlist); return 1; } +int rwhr(addr) token822_alloc *addr; +{ rwrecip(addr,&hrlist); return 1; } + +int htypeseen[H_NUM]; +stralloc hfbuf = {0}; +token822_alloc hfin = {0}; +token822_alloc hfrewrite = {0}; +token822_alloc hfaddr = {0}; + +void doheaderfield(h) +stralloc *h; +{ + int htype; + int flagrewrite; + int flagrecip; + int flagrr; + + htype = hfield_known(h->s,h->len); + if (flagdeletefrom) if (htype == H_FROM) return; + if (flagdeletemessid) if (htype == H_MESSAGEID) return; + if (flagdeletesender) if (htype == H_RETURNPATH) return; + + if (htype) + htypeseen[htype] = 1; + else + if (!hfield_valid(h->s,h->len)) + die_invalid(h); + + flagrewrite = 0; + flagrecip = 0; + flagrr = 0; + switch(htype) + { + case H_R_TO: case H_R_CC: case H_R_BCC: + flagrr = 1; + case H_TO: case H_CC: case H_BCC: case H_APPARENTLYTO: + flagrecip = 1; + case H_SENDER: case H_FROM: case H_REPLYTO: + case H_RETURNRECEIPTTO: case H_ERRORSTO: case H_RETURNPATH: + case H_R_SENDER: case H_R_FROM: case H_R_REPLYTO: + flagrewrite = 1; + break; + } + + if (flagrewrite) + { + doordie(h,token822_parse(&hfin,h,&hfbuf)); + doordie(h,token822_addrlist(&hfrewrite,&hfaddr,&hfin,(htype == H_RETURNPATH) ? rwreturn : (flagrecip ? (flagrr ? rwhrr : rwhr) : rwsender))); + if (token822_unparse(h,&hfrewrite,LINELEN) != 1) + die_nomem(); + } + + if (htype == H_BCC) return; + if (htype == H_R_BCC) return; + if (htype == H_RETURNPATH) return; + if (htype == H_CONTENTLENGTH) return; /* some things are just too stupid */ + savedh_append(h); +} + +void dobody(h) +stralloc *h; +{ + put(h->s,h->len); +} + +stralloc torecip = {0}; +token822_alloc tr = {0}; + +void dorecip(s) +char *s; +{ + if (!quote2(&torecip,s)) die_nomem(); + switch(token822_parse(&tr,&torecip,&hfbuf)) + { + case -1: die_nomem(); + case 0: + substdio_puts(subfderr,"qmail-inject: fatal: unable to parse address: "); + substdio_puts(subfderr,s); + substdio_putsflush(subfderr,"\n"); + perm(); + } + token822_reverse(&tr); + rwrecip(&tr,&reciplist); +} + +stralloc defaultfrom = {0}; +token822_alloc df = {0}; + +void defaultfrommake() +{ + char *fullname; + fullname = env_get("QMAILNAME"); + if (!fullname) fullname = env_get("MAILNAME"); + if (!fullname) fullname = env_get("NAME"); + if (!token822_ready(&df,20)) die_nomem(); + df.len = 0; + df.t[df.len].type = TOKEN822_ATOM; + df.t[df.len].s = "From"; + df.t[df.len].slen = 4; + ++df.len; + df.t[df.len].type = TOKEN822_COLON; + ++df.len; + if (fullname && !flagnamecomment) + { + df.t[df.len].type = TOKEN822_QUOTE; + df.t[df.len].s = fullname; + df.t[df.len].slen = str_len(fullname); + ++df.len; + df.t[df.len].type = TOKEN822_LEFT; + ++df.len; + } + df.t[df.len].type = mailusertokentype; + df.t[df.len].s = mailuser; + df.t[df.len].slen = str_len(mailuser); + ++df.len; + if (mailhost) + { + df.t[df.len].type = TOKEN822_AT; + ++df.len; + df.t[df.len].type = TOKEN822_ATOM; + df.t[df.len].s = mailhost; + df.t[df.len].slen = str_len(mailhost); + ++df.len; + } + if (fullname && !flagnamecomment) + { + df.t[df.len].type = TOKEN822_RIGHT; + ++df.len; + } + if (fullname && flagnamecomment) + { + df.t[df.len].type = TOKEN822_COMMENT; + df.t[df.len].s = fullname; + df.t[df.len].slen = str_len(fullname); + ++df.len; + } + if (token822_unparse(&defaultfrom,&df,LINELEN) != 1) die_nomem(); + doordie(&defaultfrom,token822_parse(&df,&defaultfrom,&hfbuf)); + doordie(&defaultfrom,token822_addrlist(&hfrewrite,&hfaddr,&df,rwsender)); + if (token822_unparse(&defaultfrom,&hfrewrite,LINELEN) != 1) die_nomem(); +} + +stralloc defaultreturnpath = {0}; +token822_alloc drp = {0}; +stralloc hackedruser = {0}; +char strnum[FMT_ULONG]; + +void dodefaultreturnpath() +{ + if (!stralloc_copys(&hackedruser,mailruser)) die_nomem(); + if (flaghackmess) + { + if (!stralloc_cats(&hackedruser,"-")) die_nomem(); + if (!stralloc_catb(&hackedruser,strnum,fmt_ulong(strnum,(unsigned long) starttime))) die_nomem(); + if (!stralloc_cats(&hackedruser,".")) die_nomem(); + if (!stralloc_catb(&hackedruser,strnum,fmt_ulong(strnum,(unsigned long) getpid()))) die_nomem(); + } + if (flaghackrecip) + if (!stralloc_cats(&hackedruser,"-")) die_nomem(); + if (!token822_ready(&drp,10)) die_nomem(); + drp.len = 0; + drp.t[drp.len].type = TOKEN822_ATOM; + drp.t[drp.len].s = "Return-Path"; + drp.t[drp.len].slen = 11; + ++drp.len; + drp.t[drp.len].type = TOKEN822_COLON; + ++drp.len; + drp.t[drp.len].type = TOKEN822_QUOTE; + drp.t[drp.len].s = hackedruser.s; + drp.t[drp.len].slen = hackedruser.len; + ++drp.len; + if (mailrhost) + { + drp.t[drp.len].type = TOKEN822_AT; + ++drp.len; + drp.t[drp.len].type = TOKEN822_ATOM; + drp.t[drp.len].s = mailrhost; + drp.t[drp.len].slen = str_len(mailrhost); + ++drp.len; + } + if (token822_unparse(&defaultreturnpath,&drp,LINELEN) != 1) die_nomem(); + doordie(&defaultreturnpath,token822_parse(&drp,&defaultreturnpath,&hfbuf)); + doordie(&defaultreturnpath + ,token822_addrlist(&hfrewrite,&hfaddr,&drp,rwreturn)); + if (token822_unparse(&defaultreturnpath,&hfrewrite,LINELEN) != 1) die_nomem(); +} + +void finishheader() +{ + flagresent = + htypeseen[H_R_SENDER] || htypeseen[H_R_FROM] || htypeseen[H_R_REPLYTO] + || htypeseen[H_R_TO] || htypeseen[H_R_CC] || htypeseen[H_R_BCC] + || htypeseen[H_R_DATE] || htypeseen[H_R_MESSAGEID]; + + if (!sender.s) + dodefaultreturnpath(); + + if (!flagqueue) + { + static stralloc sa = {0}; + static stralloc sa2 = {0}; + + if (!stralloc_copy(&sa,&sender)) die_nomem(); + if (!stralloc_0(&sa)) die_nomem(); + if (!quote2(&sa2,sa.s)) die_nomem(); + + puts("Return-Path: <"); + put(sa2.s,sa2.len); + puts(">\n"); + } + + /* could check at this point whether there are any recipients */ + if (flagqueue) + if (qmail_open(&qqt) == -1) die_qqt(); + + if (flagresent) + { + if (!htypeseen[H_R_DATE]) + { + if (!newfield_datemake(starttime)) die_nomem(); + puts("Resent-"); + put(newfield_date.s,newfield_date.len); + } + if (!htypeseen[H_R_MESSAGEID]) + { + if (!newfield_msgidmake(control_idhost.s,control_idhost.len,starttime)) die_nomem(); + puts("Resent-"); + put(newfield_msgid.s,newfield_msgid.len); + } + if (!htypeseen[H_R_FROM]) + { + defaultfrommake(); + puts("Resent-"); + put(defaultfrom.s,defaultfrom.len); + } + if (!htypeseen[H_R_TO] && !htypeseen[H_R_CC]) + puts("Resent-Cc: recipient list not shown: ;\n"); + } + else + { + if (!htypeseen[H_DATE]) + { + if (!newfield_datemake(starttime)) die_nomem(); + put(newfield_date.s,newfield_date.len); + } + if (!htypeseen[H_MESSAGEID]) + { + if (!newfield_msgidmake(control_idhost.s,control_idhost.len,starttime)) die_nomem(); + put(newfield_msgid.s,newfield_msgid.len); + } + if (!htypeseen[H_FROM]) + { + defaultfrommake(); + put(defaultfrom.s,defaultfrom.len); + } + if (!htypeseen[H_TO] && !htypeseen[H_CC]) + puts("Cc: recipient list not shown: ;\n"); + } + + savedh_print(); +} + +void getcontrols() +{ + static stralloc sa = {0}; + char *x; + + if (control_init() == -1) die_read(); + + if (control_rldef(&control_defaultdomain,"control/defaultdomain",1,"defaultdomain") != 1) + die_read(); + x = env_get("QMAILDEFAULTDOMAIN"); + if (x) if (!stralloc_copys(&control_defaultdomain,x)) die_nomem(); + if (!stralloc_copys(&sa,".")) die_nomem(); + if (!stralloc_cat(&sa,&control_defaultdomain)) die_nomem(); + doordie(&sa,token822_parse(&defaultdomain,&sa,&defaultdomainbuf)); + + if (control_rldef(&control_defaulthost,"control/defaulthost",1,"defaulthost") != 1) + die_read(); + x = env_get("QMAILDEFAULTHOST"); + if (x) if (!stralloc_copys(&control_defaulthost,x)) die_nomem(); + if (!stralloc_copys(&sa,"@")) die_nomem(); + if (!stralloc_cat(&sa,&control_defaulthost)) die_nomem(); + doordie(&sa,token822_parse(&defaulthost,&sa,&defaulthostbuf)); + + if (control_rldef(&control_plusdomain,"control/plusdomain",1,"plusdomain") != 1) + die_read(); + x = env_get("QMAILPLUSDOMAIN"); + if (x) if (!stralloc_copys(&control_plusdomain,x)) die_nomem(); + if (!stralloc_copys(&sa,".")) die_nomem(); + if (!stralloc_cat(&sa,&control_plusdomain)) die_nomem(); + doordie(&sa,token822_parse(&plusdomain,&sa,&plusdomainbuf)); + + if (control_rldef(&control_idhost,"control/idhost",1,"idhost") != 1) + die_read(); + x = env_get("QMAILIDHOST"); + if (x) if (!stralloc_copys(&control_idhost,x)) die_nomem(); +} + +#define RECIP_DEFAULT 1 +#define RECIP_ARGS 2 +#define RECIP_HEADER 3 +#define RECIP_AH 4 + +void main(argc,argv) +int argc; +char **argv; +{ + int i; + int opt; + int recipstrategy; + + sig_pipeignore(); + + starttime = now(); + + qmopts = env_get("QMAILINJECT"); + if (qmopts) + while (*qmopts) + switch(*qmopts++) + { + case 'c': flagnamecomment = 1; break; + case 's': flagdeletesender = 1; break; + case 'f': flagdeletefrom = 1; break; + case 'i': flagdeletemessid = 1; break; + case 'r': flaghackrecip = 1; break; + case 'm': flaghackmess = 1; break; + } + + mailhost = env_get("QMAILHOST"); + if (!mailhost) mailhost = env_get("MAILHOST"); + mailrhost = env_get("QMAILSHOST"); + if (!mailrhost) mailrhost = mailhost; + + mailuser = env_get("QMAILUSER"); + if (!mailuser) mailuser = env_get("MAILUSER"); + if (!mailuser) mailuser = env_get("USER"); + if (!mailuser) mailuser = env_get("LOGNAME"); + if (!mailuser) mailuser = "anonymous"; + mailusertokentype = TOKEN822_ATOM; + if (quote_need(mailuser,str_len(mailuser))) mailusertokentype = TOKEN822_QUOTE; + mailruser = env_get("QMAILSUSER"); + if (!mailruser) mailruser = mailuser; + + for (i = 0;i < H_NUM;++i) htypeseen[i] = 0; + + recipstrategy = RECIP_DEFAULT; + flagqueue = 1; + + if (chdir(auto_qmail) == -1) + die_chdir(); + getcontrols(); + + if (!saa_readyplus(&hrlist,1)) die_nomem(); + if (!saa_readyplus(&hrrlist,1)) die_nomem(); + if (!saa_readyplus(&reciplist,1)) die_nomem(); + + while ((opt = getopt(argc,argv,"aAhHnNf:")) != opteof) + switch(opt) + { + case 'a': recipstrategy = RECIP_ARGS; break; + case 'A': recipstrategy = RECIP_DEFAULT; break; + case 'h': recipstrategy = RECIP_HEADER; break; + case 'H': recipstrategy = RECIP_AH; break; + case 'n': flagqueue = 0; break; + case 'N': flagqueue = 1; break; + case 'f': + if (!quote2(&sender,optarg)) die_nomem(); + doordie(&sender,token822_parse(&envs,&sender,&envsbuf)); + token822_reverse(&envs); + rwgeneric(&envs); + token822_reverse(&envs); + if (token822_unquote(&sender,&envs) != 1) die_nomem(); + break; + case '?': + default: + perm(); + } + argc -= optind; + argv += optind; + + if (recipstrategy == RECIP_DEFAULT) + recipstrategy = (*argv ? RECIP_ARGS : RECIP_HEADER); + + if (recipstrategy != RECIP_HEADER) + while (*argv) + dorecip(*argv++); + + flagrh = (recipstrategy != RECIP_ARGS); + + if (headerbody(subfdin,doheaderfield,finishheader,dobody) == -1) + die_read(); + exitnicely(); +} diff --git a/qmail-limits.9 b/qmail-limits.9 @@ -0,0 +1,30 @@ +.TH qmail-limits 7 +.SH "NAME" +qmail-limits \- artificial limits in the qmail system +.SH "DESCRIPTION" +The +.B qmail +system is able to handle messages of any size, +addresses of any size, mailing lists of any size, and so on, +except as limited by the available memory and disk space. + +However, it imposes certain artificial limits: +.TP 5 +1. +.B qmail-lspawn +silently limits the number of simultaneous local deliveries to SPAWN. +.B qmail-rspawn +silently limits the number of simultaneous remote deliveries to SPAWN. +.TP 5 +2. +.B qmail-queue +rejects any message with an envelope address longer than 1000 characters. +.TP 5 +3. +.B qmail-lspawn +truncates any overly long error report from a delivery program. +It appends a note saying that it did so. +.SH "SEE ALSO" +qmail-lspawn(8), +qmail-queue(8), +qmail-rspawn(8) diff --git a/qmail-local.8 b/qmail-local.8 @@ -0,0 +1,99 @@ +.TH qmail-local 8 +.SH NAME +qmail-local \- deliver or forward a mail message +.SH SYNOPSIS +.B qmail-local +[ +.B \-nN +] +.I user +.I homedir +.I local +.I dash +.I ext +.I domain +.I sender +.I aliasempty +.SH DESCRIPTION +.B qmail-local +reads a mail message +and delivers it to +.I user +by the procedure described in +.BR dot-qmail(5) . + +The message's envelope recipient is +.IR local@domain . +.B qmail-local +records +.I local@domain +in a new +.B Delivered-To +header field. +If exactly the same +.B Delivered-To: \fIlocal@domain +already appears in the header, +.B qmail-local +bounces the message, +to prevent mail forwarding loops. + +The message's envelope sender is +.IR sender . +.B qmail-local +records +.I sender +in a new +.B Return-Path +header field. + +.I homedir +is the user's home directory. +It must be an absolute directory name. + +.I dash +and +.I ext +identify the +.B .qmail\fIdashext +file used by +.BR qmail-local ; +see +.BR dot-qmail(5) . +Normally +.I dash +is either empty or a lone hyphen. +If it is empty, +.B qmail-local +treats a nonexistent +.B .qmail\fIext +the same way as an empty +.BR .qmail\fIext : +namely, following the delivery instructions in +.IR aliasempty . + +The standard input for +.B qmail-local +must be a seekable file, +so that +.B qmail-local +can read it more than once. +.SH "OPTIONS" +.TP +.B \-n +Instead of reading and delivering the message, +print a description of the delivery instructions. +.TP +.B \-N +(Default.) Read and deliver the message. +.SH "EXIT CODES" +0 if the delivery is completely successful; +nonzero if any delivery instruction failed. +Exit code 111 +indicates temporary failure. +.SH "SEE ALSO" +dot-qmail(5), +envelopes(5), +qmail-command(8), +qmail-queue(8), +qmail-send(8), +qmail-lspawn(8) diff --git a/qmail-local.c b/qmail-local.c @@ -0,0 +1,672 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include "readwrite.h" +#include "sig.h" +#include "env.h" +#include "byte.h" +#include "exit.h" +#include "fork.h" +#include "open.h" +#include "wait.h" +#include "lock.h" +#include "seek.h" +#include "substdio.h" +#include "getln.h" +#include "subfd.h" +#include "sgetopt.h" +#include "alloc.h" +#include "error.h" +#include "stralloc.h" +#include "fmt.h" +#include "str.h" +#include "now.h" +#include "case.h" +#include "quote.h" +#include "qmail.h" +#include "slurpclose.h" +#include "myctime.h" +#include "gfrom.h" +#include "auto_patrn.h" + +void err(s) char *s; { substdio_putsflush(subfderr,s); } +void soft() { _exit(111); } +void hard() { _exit(100); } + +void temp_childcrashed() { err("Aack, child crashed. (#4.3.0)\n"); soft(); } +void temp_rewind() { err("Unable to rewind message. (#4.3.0)\n"); soft(); } +void temp_fork() { err("Unable to fork. (#4.3.0)\n"); soft(); } +void temp_read() { err("Error while reading message. (#4.3.0)\n"); soft(); } +void temp_write() { err("Error while writing message. (#4.3.0)\n"); soft(); } +void temp_child() { err("Temporary error in forwarding message. (#4.3.0)\n"); soft(); } +void temp_maildirtimeout() { err("Timeout on maildir delivery. (#4.3.0)\n"); soft(); } +void temp_maildir() { err("Temporary error on maildir delivery. (#4.3.0)\n"); soft(); } +void temp_nomaildir() { err("Unable to chdir to maildir. (#4.2.1)\n"); soft(); } +void temp_open(fn) char *fn; { err("Unable to open "); err(fn); err(". (#4.2.1)\n"); soft(); } + +void temp_blankline() { err("Uh-oh: first line of .qmail file is blank. (#4.2.1)\n"); soft(); } +void temp_fofile() { err("Uh-oh: .qmail has file delivery but has x bit set. (#4.7.0)\n"); soft(); } +void temp_foprog() { err("Uh-oh: .qmail has prog delivery but has x bit set. (#4.7.0)\n"); soft(); } +void temp_nomem() { err("Out of memory. (#4.3.0)\n"); soft(); } +void temp_chdir() { err("Unable to switch to home directory. (#4.3.0)\n"); soft(); } +void temp_homestat() { err("Unable to stat home directory. (#4.3.0)\n"); soft(); } +void temp_homesticky() { err("Home directory is sticky: user is editing his .qmail file. (#4.2.1)\n"); soft(); } +void temp_homewritable() { err("Uh-oh: home directory is writable. (#4.7.0)\n"); soft(); } +void temp_qmwritable() { err("Uh-oh: .qmail file is writable. (#4.7.0)\n"); soft(); } +void temp_nfsqmail() { err("Temporary error trying to open .qmail file. (#4.3.0)\n"); soft(); } +void temp_denyqmail() { err("Permission error trying to open .qmail file. (#4.3.0)\n"); soft(); } +void temp_slowlock() { err("File has been locked for 30 seconds straight. (#4.3.0)\n"); soft(); } + +void bounce_childperm() { err("Permanent error in forwarding message. (#5.2.4)\n"); hard(); } +void bounce_loop() { err("This message is looping: it already has my Delivered-To line. (#5.4.6)\n"); hard(); } +void bounce_ext() { err("Sorry, no mailbox here by that name. (#5.1.1)\n"); hard(); } +void usage() { err("qmail-local: usage: qmail-local [ -nN ] user homedir local dash ext domain sender aliasempty\n"); hard(); } + +void warn_homesticky() { err("Warning: home directory is sticky.\n"); } + +int flagdoit; +int flag99; + +char *user; +char *homedir; +char *local; +char *dash; +char *ext; +char *host; +char *sender; +char *aliasempty; + +stralloc dashext = {0}; +stralloc ufline = {0}; +stralloc rpline = {0}; +stralloc envrecip = {0}; +stralloc dtline = {0}; +stralloc qme = {0}; +stralloc ueo = {0}; +stralloc cmds = {0}; +stralloc messline = {0}; +stralloc foo = {0}; + +char buf[1024]; +char outbuf[1024]; + +/* child process */ + +char fntmptph[80 + FMT_ULONG * 2]; +char fnnewtph[80 + FMT_ULONG * 2]; +void tryunlinktmp() { unlink(fntmptph); } +void sigalrm() { tryunlinktmp(); _exit(3); } + +void maildir_child(dir) +char *dir; +{ + unsigned long pid; + unsigned long time; + char host[64]; + char *s; + int loop; + struct stat st; + int fd; + substdio ss; + substdio ssout; + + sig_alarmcatch(sigalrm); + if (chdir(dir) == -1) { if (error_temp(errno)) _exit(1); _exit(2); } + pid = getpid(); + host[0] = 0; + gethostname(host,sizeof(host)); + for (loop = 0;;++loop) + { + time = now(); + s = fntmptph; + s += fmt_str(s,"tmp/"); + s += fmt_ulong(s,time); *s++ = '.'; + s += fmt_ulong(s,pid); *s++ = '.'; + s += fmt_strn(s,host,sizeof(host)); *s++ = 0; + if (stat(fntmptph,&st) == -1) if (errno == error_noent) break; + /* really should never get to this point */ + if (loop == 2) _exit(1); + sleep(2); + } + str_copy(fnnewtph,fntmptph); + byte_copy(fnnewtph,3,"new"); + + alarm(86400); + fd = open_excl(fntmptph); + if (fd == -1) _exit(1); + + substdio_fdbuf(&ss,read,0,buf,sizeof(buf)); + substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf)); + if (substdio_put(&ssout,rpline.s,rpline.len) == -1) goto fail; + if (substdio_put(&ssout,dtline.s,dtline.len) == -1) goto fail; + + switch(substdio_copy(&ssout,&ss)) + { + case -2: tryunlinktmp(); _exit(4); + case -3: goto fail; + } + + if (substdio_flush(&ssout) == -1) goto fail; + if (fsync(fd) == -1) goto fail; + if (close(fd) == -1) goto fail; /* NFS dorks */ + + if (link(fntmptph,fnnewtph) == -1) goto fail; + /* if it was error_exist, almost certainly successful; i hate NFS */ + tryunlinktmp(); _exit(0); + + fail: tryunlinktmp(); _exit(1); +} + +/* end child process */ + +void maildir(fn) +char *fn; +{ + int child; + int wstat; + + if (seek_begin(0) == -1) temp_rewind(); + + switch(child = fork()) + { + case -1: + temp_fork(); + case 0: + maildir_child(fn); + soft(); + } + + wait_pid(&wstat,child); + if (wait_crashed(wstat)) + temp_childcrashed(); + switch(wait_exitcode(wstat)) + { + case 0: break; + case 2: temp_nomaildir(); + case 3: temp_maildirtimeout(); + case 4: temp_read(); + default: temp_maildir(); + } +} + +void slowlock() { temp_slowlock(); } + +void mailfile(fn) +char *fn; +{ + int fd; + substdio ss; + substdio ssout; + int match; + seek_pos pos; + int flaglocked; + + if (seek_begin(0) == -1) temp_rewind(); + + fd = open_append(fn); + if (fd == -1) temp_open(fn); + + sig_alarmcatch(slowlock); + alarm(30); + flaglocked = (lock_ex(fd) != -1); + alarm(0); + sig_alarmdefault(); + + seek_end(fd); + pos = seek_cur(fd); + + substdio_fdbuf(&ss,read,0,buf,sizeof(buf)); + substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf)); + if (substdio_put(&ssout,ufline.s,ufline.len)) goto writeerrs; + if (substdio_put(&ssout,rpline.s,rpline.len)) goto writeerrs; + if (substdio_put(&ssout,dtline.s,dtline.len)) goto writeerrs; + for (;;) + { + if (getln(&ss,&messline,&match,'\n') != 0) + { if (flaglocked) seek_trunc(fd,pos); close(fd); temp_read(); } + if (!match && !messline.len) break; + if (gfrom(messline.s,messline.len)) + if (substdio_bput(&ssout,">",1)) goto writeerrs; + if (substdio_bput(&ssout,messline.s,messline.len)) goto writeerrs; + if (!match) + { + if (substdio_bputs(&ssout,"\n")) goto writeerrs; + break; + } + } + if (substdio_bputs(&ssout,"\n")) goto writeerrs; + if (substdio_flush(&ssout)) goto writeerrs; + if (fsync(fd) == -1) goto writeerrs; + close(fd); + return; + + writeerrs: + if (flaglocked) seek_trunc(fd,pos); + close(fd); + temp_write(); +} + +void mailprogram(prog) +char *prog; +{ + int child; + char *(args[4]); + int wstat; + + if (seek_begin(0) == -1) temp_rewind(); + + switch(child = fork()) + { + case -1: + temp_fork(); + case 0: + args[0] = "sh"; args[1] = "-c"; args[2] = prog; args[3] = 0; + sig_pipedefault(); + execvp(*args,args); + if (errno == error_txtbsy) { err("Text busy. (#4.3.0)\n"); soft(); } + if (errno == error_nomem) { err("Out of memory. (#4.3.0)\n"); soft(); } + if (errno == error_io) { err("I/O error. (#4.3.0)\n"); soft(); } + if (error_temp(errno)) { err("Temporary error. (#4.3.0)\n"); soft(); } + err("Unable to execute "); err(*args); err(" (#5.2.4)\n"); + hard(); + } + + wait_pid(&wstat,child); + if (wait_crashed(wstat)) + temp_childcrashed(); + switch(wait_exitcode(wstat)) + { + case 100: + case 64: case 65: case 70: case 76: case 77: case 78: case 112: hard(); + case 0: break; + case 99: flag99 = 1; break; + default: soft(); + } +} + +unsigned long mailforward_qp = 0; + +void mailforward(recips) +char **recips; +{ + struct qmail qqt; + substdio ss; + int match; + + if (seek_begin(0) == -1) temp_rewind(); + substdio_fdbuf(&ss,read,0,buf,sizeof(buf)); + + if (qmail_open(&qqt) == -1) temp_fork(); + mailforward_qp = qmail_qp(&qqt); + qmail_put(&qqt,dtline.s,dtline.len); + do + { + if (getln(&ss,&messline,&match,'\n') != 0) { qmail_fail(&qqt); break; } + qmail_put(&qqt,messline.s,messline.len); + } + while (match); + qmail_from(&qqt,ueo.s); + while (*recips) qmail_to(&qqt,*recips++); + switch(qmail_close(&qqt)) + { + case QMAIL_TOOLONG: bounce_childperm(); + case QMAIL_READ: temp_read(); + case 0: return; + default: temp_child(); + } +} + +void bouncexf() +{ + int match; + substdio ss; + + if (seek_begin(0) == -1) temp_rewind(); + substdio_fdbuf(&ss,read,0,buf,sizeof(buf)); + for (;;) + { + if (getln(&ss,&messline,&match,'\n') != 0) temp_read(); + if (!match) break; + if (messline.len <= 1) + break; + if (messline.len == dtline.len) + if (!str_diffn(messline.s,dtline.s,dtline.len)) + bounce_loop(); + } +} + +void checkhome() +{ + struct stat st; + + if (stat(".",&st) == -1) temp_homestat(); + if (st.st_mode & auto_patrn) temp_homewritable(); + if (st.st_mode & 01000) + if (flagdoit) temp_homesticky(); else warn_homesticky(); +} + +int qmeox(dashowner) +char *dashowner; +{ + struct stat st; + + if (!stralloc_copys(&qme,".qmail")) temp_nomem(); + if (!stralloc_cat(&qme,&dashext)) temp_nomem(); + if (!stralloc_cats(&qme,dashowner)) temp_nomem(); + if (!stralloc_0(&qme)) temp_nomem(); + if (stat(qme.s,&st) == -1) + { + if (error_temp(errno)) temp_nfsqmail(); + return -1; + } + return 0; +} + +int qmeopen(cutable) +int *cutable; +{ + int fd; + struct stat st; + int i; + + i = dashext.len; + for (;;) + { + if (!stralloc_copys(&qme,".qmail")) temp_nomem(); + if (!stralloc_catb(&qme,dashext.s,i)) temp_nomem(); + if (i < dashext.len) if (!stralloc_cats(&qme,"-default")) temp_nomem(); + if (!stralloc_0(&qme)) temp_nomem(); + fd = open_read(qme.s); + if (fd == -1) + { + if (error_temp(errno)) temp_nfsqmail(); + if (errno == error_perm) temp_denyqmail(); + if (errno == error_acces) temp_denyqmail(); + } + else + { + if (fstat(fd,&st) == -1) temp_nfsqmail(); + if ((st.st_mode & S_IFMT) == S_IFREG) + { + if (st.st_mode & auto_patrn) temp_qmwritable(); + *cutable = !!(st.st_mode & 0100); + return fd; + } + close(fd); + } + if (!i) return -1; + do + if (dashext.s[--i] == '-') break; + while (i); + } +} + +unsigned long count_file = 0; +unsigned long count_forward = 0; +unsigned long count_program = 0; +char count_buf[FMT_ULONG]; + +void count_print() +{ + substdio_puts(subfdoutsmall,"did "); + substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,count_file)); + substdio_puts(subfdoutsmall,"+"); + substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,count_forward)); + substdio_puts(subfdoutsmall,"+"); + substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,count_program)); + substdio_puts(subfdoutsmall,"\n"); + if (mailforward_qp) + { + substdio_puts(subfdoutsmall,"qp "); + substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,mailforward_qp)); + substdio_puts(subfdoutsmall,"\n"); + } + substdio_flush(subfdoutsmall); +} + +void sayit(type,cmd,len) +char *type; +char *cmd; +int len; +{ + substdio_puts(subfdoutsmall,type); + substdio_put(subfdoutsmall,cmd,len); + substdio_putsflush(subfdoutsmall,"\n"); +} + +void main(argc,argv) +int argc; +char **argv; +{ + int opt; + int i; + int j; + int k; + int fd; + int numforward; + char **recips; + datetime_sec starttime; + int flagforwardonly; + char *extx; + + umask(077); + sig_pipeignore(); + + if (!env_init()) temp_nomem(); + + flagdoit = 1; + while ((opt = getopt(argc,argv,"nN")) != opteof) + switch(opt) + { + case 'n': flagdoit = 0; break; + case 'N': flagdoit = 1; break; + case '?': + default: + hard(); + } + argc -= optind; + argv += optind; + + if (!(user = *argv++)) usage(); + if (!(homedir = *argv++)) usage(); + if (!(local = *argv++)) usage(); + if (!(dash = *argv++)) usage(); + if (!(ext = *argv++)) usage(); + if (!(host = *argv++)) usage(); + if (!(sender = *argv++)) usage(); + if (!(aliasempty = *argv++)) usage(); + if (*argv) usage(); + + if (homedir[0] != '/') usage(); + if (chdir(homedir) == -1) temp_chdir(); + checkhome(); + + if (!env_put2("HOST",host)) temp_nomem(); + if (!env_put2("HOME",homedir)) temp_nomem(); + if (!env_put2("USER",user)) temp_nomem(); + if (!env_put2("LOCAL",local)) temp_nomem(); + + if (!stralloc_copys(&envrecip,local)) temp_nomem(); + if (!stralloc_cats(&envrecip,"@")) temp_nomem(); + if (!stralloc_cats(&envrecip,host)) temp_nomem(); + + if (!stralloc_copy(&foo,&envrecip)) temp_nomem(); + if (!stralloc_0(&foo)) temp_nomem(); + if (!env_put2("RECIPIENT",foo.s)) temp_nomem(); + + if (!stralloc_copys(&dtline,"Delivered-To: ")) temp_nomem(); + if (!stralloc_cat(&dtline,&envrecip)) temp_nomem(); + for (i = 0;i < dtline.len;++i) if (dtline.s[i] == '\n') dtline.s[i] = '_'; + if (!stralloc_cats(&dtline,"\n")) temp_nomem(); + + if (!stralloc_copy(&foo,&dtline)) temp_nomem(); + if (!stralloc_0(&foo)) temp_nomem(); + if (!env_put2("DTLINE",foo.s)) temp_nomem(); + + if (flagdoit) + bouncexf(); + + if (!env_put2("SENDER",sender)) temp_nomem(); + + if (!quote2(&foo,sender)) temp_nomem(); + if (!stralloc_copys(&rpline,"Return-Path: <")) temp_nomem(); + if (!stralloc_cat(&rpline,&foo)) temp_nomem(); + for (i = 0;i < rpline.len;++i) if (rpline.s[i] == '\n') rpline.s[i] = '_'; + if (!stralloc_cats(&rpline,">\n")) temp_nomem(); + + if (!stralloc_copy(&foo,&rpline)) temp_nomem(); + if (!stralloc_0(&foo)) temp_nomem(); + if (!env_put2("RPLINE",foo.s)) temp_nomem(); + + if (!stralloc_copys(&ufline,"From ")) temp_nomem(); + if (*sender) + { + int len; int i; char ch; + + len = str_len(sender); + if (!stralloc_readyplus(&ufline,len)) temp_nomem(); + for (i = 0;i < len;++i) + { + ch = sender[i]; + if ((ch == ' ') || (ch == '\t') || (ch == '\n')) ch = '-'; + ufline.s[ufline.len + i] = ch; + } + ufline.len += len; + } + else + if (!stralloc_cats(&ufline,"MAILER-DAEMON")) temp_nomem(); + if (!stralloc_cats(&ufline," ")) temp_nomem(); + starttime = now(); + if (!stralloc_cats(&ufline,myctime(starttime))) temp_nomem(); + + if (!stralloc_copy(&foo,&ufline)) temp_nomem(); + if (!stralloc_0(&foo)) temp_nomem(); + if (!env_put2("UFLINE",foo.s)) temp_nomem(); + + if (!stralloc_copys(&dashext,dash)) temp_nomem(); + if (!stralloc_cats(&dashext,ext)) temp_nomem(); + for (i = 0;i < dashext.len;++i) + if (dashext.s[i] == '.') + dashext.s[i] = ':'; + case_lowerb(dashext.s,dashext.len); + + extx = ext; + if (!env_put2("EXT",extx)) temp_nomem(); + extx += str_chr(extx,'-'); if (*extx) ++extx; + if (!env_put2("EXT2",extx)) temp_nomem(); + extx += str_chr(extx,'-'); if (*extx) ++extx; + if (!env_put2("EXT3",extx)) temp_nomem(); + extx += str_chr(extx,'-'); if (*extx) ++extx; + if (!env_put2("EXT4",extx)) temp_nomem(); + + flagforwardonly = 0; + fd = qmeopen(&flagforwardonly); + if (fd == -1) if (*dash) bounce_ext(); + + if (!stralloc_copys(&ueo,sender)) temp_nomem(); + if (str_diff(sender,"")) + if (str_diff(sender,"#@[]")) + if (qmeox("-owner") == 0) + { + if (qmeox("-owner-default") == 0) + { + if (!stralloc_copys(&ueo,local)) temp_nomem(); + if (!stralloc_cats(&ueo,"-owner-@")) temp_nomem(); + if (!stralloc_cats(&ueo,host)) temp_nomem(); + if (!stralloc_cats(&ueo,"-@[]")) temp_nomem(); + } + else + { + if (!stralloc_copys(&ueo,local)) temp_nomem(); + if (!stralloc_cats(&ueo,"-owner@")) temp_nomem(); + if (!stralloc_cats(&ueo,host)) temp_nomem(); + } + } + if (!stralloc_0(&ueo)) temp_nomem(); + if (!env_put2("NEWSENDER",ueo.s)) temp_nomem(); + + if (!stralloc_ready(&cmds,0)) temp_nomem(); + cmds.len = 0; + if (fd != -1) + if (slurpclose(fd,&cmds,256) == -1) temp_nomem(); + + if (!cmds.len) + { + if (!stralloc_copys(&cmds,aliasempty)) temp_nomem(); + flagforwardonly = 0; + } + if (!cmds.len || (cmds.s[cmds.len - 1] != '\n')) + if (!stralloc_cats(&cmds,"\n")) temp_nomem(); + + numforward = 0; + i = 0; + for (j = 0;j < cmds.len;++j) + if (cmds.s[j] == '\n') + { + switch(cmds.s[i]) { case '#': case '.': case '/': case '|': break; + default: ++numforward; } + i = j + 1; + } + + recips = (char **) alloc((numforward + 1) * sizeof(char *)); + if (!recips) temp_nomem(); + numforward = 0; + + flag99 = 0; + + i = 0; + for (j = 0;j < cmds.len;++j) + if (cmds.s[j] == '\n') + { + cmds.s[j] = 0; + k = j; + while ((k > i) && (cmds.s[k - 1] == ' ') || (cmds.s[k - 1] == '\t')) + cmds.s[--k] = 0; + switch(cmds.s[i]) + { + case 0: /* k == i */ + if (i) break; + temp_blankline(); + case '#': + break; + case '.': + case '/': + ++count_file; + if (flagforwardonly) temp_fofile(); + if (cmds.s[k - 1] == '/') + if (flagdoit) maildir(cmds.s + i); + else sayit("maildir ",cmds.s + i,k - i); + else + if (flagdoit) mailfile(cmds.s + i); + else sayit("mbox ",cmds.s + i,k - i); + break; + case '|': + ++count_program; + if (flagforwardonly) temp_foprog(); + if (flagdoit) mailprogram(cmds.s + i + 1); + else sayit("program ",cmds.s + i + 1,k - i - 1); + break; + case '+': + if (str_equal(cmds.s + i + 1,"list")) + flagforwardonly = 1; + break; + case '&': + ++i; + default: + ++count_forward; + if (flagdoit) recips[numforward++] = cmds.s + i; + else sayit("forward ",cmds.s + i,k - i); + break; + } + i = j + 1; + if (flag99) break; + } + + if (numforward) if (flagdoit) + { + recips[numforward] = 0; + mailforward(recips); + } + + count_print(); + _exit(0); +} diff --git a/qmail-log.5 b/qmail-log.5 @@ -0,0 +1,270 @@ +.TH qmail-log 5 +.SH NAME +qmail-log \- the qmail activity record +.SH DESCRIPTION +.B qmail-send +prints a series of lines describing its activities. +Each possible line is described below. +.SH "STARTING AND STOPPING" +.TP +.B running +.B qmail-send +is ready to deliver messages. +.TP +.B local deliveries will be put on hold +The local concurrency limit is 0, so +.B qmail-send +will not perform any local deliveries. +.TP +.B remote deliveries will be put on hold +The remote concurrency limit is 0, so +.B qmail-send +will not perform any remote deliveries. +.TP +.B number of deliveries left before exiting: ... +.B qmail-send +wants to exit as soon as possible, +usually because it was sent a +TERM signal, +but it has to wait for some deliveries to finish. +It will not start any new deliveries. +.TP +.B exiting +.B qmail-send +is done. +.SH "FATAL PROBLEMS" +.TP +.B alert: cannot start: ... +.B qmail-send +is unable to prepare itself for delivering messages; +it is giving up. +This normally indicates a serious configuration error, +but it can be caused by a temporary lack of resources. +.TP +.B alert: oh no! lost ... +One of the other daemons has died. +.B qmail-send +will exit as soon as possible. +.SH "SERIOUS PROBLEMS" +.TP +.B alert: unable to append to bounce message... +.B qmail-send +is unable to record a permanent failure, +usually because the disk is full. +This is a very serious problem; +.B qmail-send +cannot proceed without recording the results. +It will try again in ten seconds. +.TP +.B alert: out of memory... +.B qmail-send +tried to allocate more memory and failed. +It will try again in ten seconds. +.TP +.B alert: unable to opendir... +.B qmail-send +is having trouble reading a file list from disk, +usually because the system's file descriptor table is full, +but possibly because permissions are set incorrectly. +It will try again in ten seconds. +.TP +.B alert: unable to switch back... +.B qmail-send +was sent SIGHUP, +and it is unable to reenter the queue directory. +This is a very serious problem; +.B qmail-send +cannot proceed outside the queue directory. +It will try again in ten seconds. +.TP +.B alert: unable to reread... +.B qmail-send +was sent SIGHUP, +but it is unable to read the new controls. +It will continue operating with the original controls. +.SH "MESSAGES" +.TP +.B new msg \fIm\fB +.B qmail-send +is going to preprocess a queued message. +The message number, +.IR m , +is its disk inode number. +After a message is removed from the queue, +its number can be reused immediately. +.TP +.B info msg \fIm\fB: bytes \fIb\fB from <\fIs\fB> qp \fIq\fB uid \fIu\fB +Message +.I m +contains +.I b +bytes; +its envelope sender is +.IR s ; +it was queued by a user with user ID +.IR u . +.I q +is a long-term queue identifier, +the process ID of the +.B qmail-queue +that queued the message. +.TP +.B bounce msg \fIm\fB qp \fIq\fB +Message +.I m +had some delivery failures. +The long-term queue identifier of the bounce (or double-bounce) message +is +.IR q . +.TP +.B triple bounce: discarding ... +Message +.I m +had some delivery failures, +but it is already a double-bounce message, +so it must be thrown away. +Triple-bounce messages do not exist. +.TP +.B end msg \fIm\fB +.B qmail-send +is about to remove +message +.I m +from the queue. +.SH "DELIVERIES" +.TP +.B starting delivery \fId\fB: msg \fIm\fB to ... +.B qmail-send +is telling +.B qmail-lspawn +or +.B qmail-rspawn +to deliver message +.I m +to one recipient. +The delivery number, +.IR d , +starts at 1 and increases by 1 for each new delivery. +.TP +.B delivery \fId\fB: success: ... +Delivery +.I d +was successful. +.TP +.B delivery \fId\fB: failure: ... +Delivery +.I d +failed permanently. +The message will bounce. +.TP +.B delivery \fId\fB: deferral: ... +Delivery +.I d +failed temporarily. +This recipient will be retried later. +.TP +.B delivery \fId\fB: report mangled, will defer +There is a serious bug in +.B qmail-lspawn +or +.BR qmail-rspawn . +This recipient will be retried later. +.SH "WARNINGS" +.TP +.B internal error: delivery report out of range +.B qmail-lspawn +or +.B qmail-rspawn +has supplied a report on a nonexistent delivery. +This is a serious bug. +.TP +.B qmail-clean unable to clean up ... +For some reason +.B qmail-clean +is unable to remove the indicated file. +It will try again later. +.TP +.B trouble fsyncing ... +.B qmail-send +was unable to write to disk the results of preprocessing a queued message. +It will try again later. +.TP +.B trouble in select +There is an operating system bug. +.TP +.B trouble injecting bounce message... +.B qmail-send +was unable to queue a bounce message, +usually because the disk is full. +It will try again later. +.TP +.B trouble marking ... +.B qmail-send +was unable to record the result of a successful or permanently +unsuccessful delivery. +This means that the delivery will be tried again later. +.TP +.B trouble opening ... +.B qmail-send +was unable to open the list of local or remote recipients +for a message. +It will try again later. +.TP +.B trouble reading ... +Either +.B qmail-send +is unable to read a recipient list, +or it is unable to read the envelope of a queued +message, or it is out of memory. +Whatever it was doing, it will try again later. +.TP +.B trouble writing to ... +.B qmail-send +was unable to preprocess a queued message, +usually because the disk is full. +It will try again later. +.TP +.B unable to create ... +.B qmail-send +was unable to preprocess a queued message, +usually because the disk is out of inodes. +It will try again later. +.TP +.B unable to open ... +.B qmail-send +is unable to read the envelope of a queued message +for preprocessing. +It will try again later. +.TP +.B unable to start qmail-queue... +.B qmail-send +is unable to queue a bounce message, +usually because the machine is almost out of memory. +It will try again later. +.TP +.B unable to stat ... +.B qmail-send +is unable to obtain information about a file that should exist. +It will try again later. +.TP +.B unable to unlink ... +.B qmail-send +is unable to remove a file. +It will try again later. +.TP +.B unable to utime ... +.B qmail-send +is about to exit, +and it is unable to record on disk +the next scheduled delivery time for a message. +The message will be retried as soon as +.B qmail-send +is restarted. +.TP +.B unknown record type in ... +There is a serious bug in either +.B qmail-queue +or +.BR qmail-send . +.SH "SEE ALSO" +qmail-send(8) diff --git a/qmail-lspawn.8 b/qmail-lspawn.8 @@ -0,0 +1,46 @@ +.TH qmail-lspawn 8 +.SH NAME +qmail-lspawn \- schedule local deliveries +.SH SYNOPSIS +.B qmail-lspawn +.I aliasempty +.SH DESCRIPTION +.B qmail-lspawn +reads a series of local delivery commands from descriptor 0, +invokes +.B qmail-local +to perform the deliveries, +and prints the results to descriptor 1. +It passes +.I aliasempty +to +.B qmail-local +as the default delivery instruction. + +.B qmail-lspawn +invokes +.B qmail-local +asynchronously, +so the results may not be in the same order as the commands. + +For each recipient address, +.B qmail-lspawn +finds out which local user controls that address. +It first checks the +.B qmail-users +mechanism; if the address is not listed there, it invokes +.BR qmail-getpw . +.B qmail-lspawn +then runs +.B qmail-local +under the user's uid and gid. +It does not set up any supplementary groups. + +.B qmail-lspawn +treats an empty mailbox name as a trash address. +.SH "SEE ALSO" +envelopes(5), +qmail-users(5), +qmail-getpw(8), +qmail-send(8), +qmail-local(8) diff --git a/qmail-lspawn.c b/qmail-lspawn.c @@ -0,0 +1,234 @@ +#include "fd.h" +#include "wait.h" +#include "prot.h" +#include "substdio.h" +#include "stralloc.h" +#include "scan.h" +#include "exit.h" +#include "fork.h" +#include "error.h" +#include "cdb.h" +#include "case.h" +#include "slurpclose.h" +#include "auto_qmail.h" +#include "auto_uids.h" +#include "qlx.h" + +char *aliasempty; + +void initialize(argc,argv) +int argc; +char **argv; +{ + aliasempty = argv[1]; + if (!aliasempty) _exit(100); +} + +int truncreport = 3000; + +void report(ss,wstat,s,len) +substdio *ss; +int wstat; +char *s; +int len; +{ + int i; + if (wait_crashed(wstat)) + { substdio_puts(ss,"Zqmail-local crashed.\n"); return; } + switch(wait_exitcode(wstat)) + { + case QLX_CDB: + substdio_puts(ss,"ZTrouble reading users/cdb in qmail-lspawn.\n"); return; + case QLX_NOMEM: + substdio_puts(ss,"ZOut of memory in qmail-lspawn.\n"); return; + case QLX_SYS: + substdio_puts(ss,"ZTemporary failure in qmail-lspawn.\n"); return; + case QLX_NOALIAS: + substdio_puts(ss,"ZUnable to find alias user!\n"); return; + case QLX_ROOT: + substdio_puts(ss,"ZNot allowed to perform deliveries as root.\n"); return; + case QLX_USAGE: + substdio_puts(ss,"ZInternal qmail-lspawn bug.\n"); return; + case QLX_NFS: + substdio_puts(ss,"ZNFS failure in qmail-local.\n"); return; + case QLX_EXECHARD: + substdio_puts(ss,"DUnable to run qmail-local.\n"); return; + case QLX_EXECSOFT: + substdio_puts(ss,"ZUnable to run qmail-local.\n"); return; + case QLX_EXECPW: + substdio_puts(ss,"ZUnable to run qmail-getpw.\n"); return; + case 111: case 71: case 74: case 75: + substdio_put(ss,"Z",1); break; + case 0: + substdio_put(ss,"K",1); break; + case 100: + default: + substdio_put(ss,"D",1); break; + } + + for (i = 0;i < len;++i) if (!s[i]) break; + substdio_put(ss,s,i); +} + +stralloc lower = {0}; +stralloc nughde = {0}; +stralloc wildchars = {0}; + +void nughde_get(local) +char *local; +{ + char *(args[3]); + int pi[2]; + int gpwpid; + int gpwstat; + int r; + int fd; + int flagwild; + + if (!stralloc_copys(&lower,"!")) _exit(QLX_NOMEM); + if (!stralloc_cats(&lower,local)) _exit(QLX_NOMEM); + if (!stralloc_0(&lower)) _exit(QLX_NOMEM); + case_lowerb(lower.s,lower.len); + + if (!stralloc_copys(&nughde,"")) _exit(QLX_NOMEM); + + fd = open_read("users/cdb"); + if (fd == -1) + if (errno != error_noent) + _exit(QLX_CDB); + + if (fd != -1) + { + uint32 dlen; + unsigned int i; + + r = cdb_seek(fd,"",0,&dlen); + if (r != 1) _exit(QLX_CDB); + if (!stralloc_ready(&wildchars,(unsigned int) dlen)) _exit(QLX_NOMEM); + wildchars.len = dlen; + if (cdb_bread(fd,wildchars.s,wildchars.len) == -1) _exit(QLX_CDB); + + i = lower.len; + flagwild = 0; + + do + { + /* i > 0 */ + if (!flagwild || (i == 1) || (byte_chr(wildchars.s,wildchars.len,lower.s[i - 1]) < wildchars.len)) + { + r = cdb_seek(fd,lower.s,i,&dlen); + if (r == -1) _exit(QLX_CDB); + if (r == 1) + { + if (!stralloc_ready(&nughde,(unsigned int) dlen)) _exit(QLX_NOMEM); + nughde.len = dlen; + if (cdb_bread(fd,nughde.s,nughde.len) == -1) _exit(QLX_CDB); + if (flagwild) + if (!stralloc_cats(&nughde,local + i - 1)) _exit(QLX_NOMEM); + if (!stralloc_0(&nughde)) _exit(QLX_NOMEM); + close(fd); + return; + } + } + --i; + flagwild = 1; + } + while (i); + + close(fd); + } + + if (pipe(pi) == -1) _exit(QLX_SYS); + args[0] = "bin/qmail-getpw"; + args[1] = local; + args[2] = 0; + switch(gpwpid = vfork()) + { + case -1: + _exit(QLX_SYS); + case 0: + if (prot_gid(auto_gidn) == -1) _exit(QLX_USAGE); + if (prot_uid(auto_uidp) == -1) _exit(QLX_USAGE); + close(pi[0]); + if (fd_move(1,pi[1]) == -1) _exit(QLX_SYS); + execv(*args,args); + _exit(QLX_EXECPW); + } + close(pi[1]); + + if (slurpclose(pi[0],&nughde,128) == -1) _exit(QLX_SYS); + + if (wait_pid(&gpwstat,gpwpid) != -1) + { + if (wait_crashed(gpwstat)) _exit(QLX_SYS); + if (wait_exitcode(gpwstat) != 0) _exit(wait_exitcode(gpwstat)); + } +} + +int spawn(fdmess,fdout,s,r,at) +int fdmess; int fdout; +char *s; char *r; int at; +{ + int f; + + if (!(f = fork())) + { + char *(args[11]); + unsigned long u; + int n; + int uid; + int gid; + char *x; + unsigned int xlen; + + r[at] = 0; + if (!r[0]) _exit(0); /* <> */ + + if (chdir(auto_qmail) == -1) _exit(QLX_USAGE); + + nughde_get(r); + + x = nughde.s; + xlen = nughde.len; + + args[0] = "bin/qmail-local"; + args[1] = "--"; + args[2] = x; + n = byte_chr(x,xlen,0); if (n++ == xlen) _exit(QLX_USAGE); x += n; xlen -= n; + + scan_ulong(x,&u); + uid = u; + n = byte_chr(x,xlen,0); if (n++ == xlen) _exit(QLX_USAGE); x += n; xlen -= n; + + scan_ulong(x,&u); + gid = u; + n = byte_chr(x,xlen,0); if (n++ == xlen) _exit(QLX_USAGE); x += n; xlen -= n; + + args[3] = x; + n = byte_chr(x,xlen,0); if (n++ == xlen) _exit(QLX_USAGE); x += n; xlen -= n; + + args[4] = r; + args[5] = x; + n = byte_chr(x,xlen,0); if (n++ == xlen) _exit(QLX_USAGE); x += n; xlen -= n; + + args[6] = x; + n = byte_chr(x,xlen,0); if (n++ == xlen) _exit(QLX_USAGE); x += n; xlen -= n; + + args[7] = r + at + 1; + args[8] = s; + args[9] = aliasempty; + args[10] = 0; + + if (fd_move(0,fdmess) == -1) _exit(QLX_SYS); + if (fd_move(1,fdout) == -1) _exit(QLX_SYS); + if (fd_copy(2,1) == -1) _exit(QLX_SYS); + if (prot_gid(gid) == -1) _exit(QLX_USAGE); + if (prot_uid(uid) == -1) _exit(QLX_USAGE); + if (!getuid()) _exit(QLX_ROOT); + + execv(*args,args); + if (error_temp(errno)) _exit(QLX_EXECSOFT); + _exit(QLX_EXECHARD); + } + return f; +} diff --git a/qmail-newu.9 b/qmail-newu.9 @@ -0,0 +1,43 @@ +.TH qmail-newu 8 +.SH NAME +qmail-newu \- prepare address assignments for qmail-lspawn +.SH SYNOPSIS +.B qmail-newu +.SH DESCRIPTION +.B qmail-newu +reads the assignments in +.B QMAILHOME/users/assign +and writes them into +.B QMAILHOME/users/cdb +in a binary format suited +for quick access by +.BR qmail-lspawn . + +If there is a problem with +.BR users/assign , +.B qmail-newu +complains and leaves +.B users/cdb +alone. + +.B qmail-newu +ensures that +.B users/cdb +is updated atomically, +so +.B qmail-lspawn +never has to wait for +.B qmail-newu +to finish. +However, +.B qmail-newu +makes no attempt to protect against two simultaneous updates of +.BR users/cdb . + +The binary +.B users/cdb +format is portable across machines. +.SH "SEE ALSO" +qmail-users(5), +qmail-lspawn(8), +qmail-pw2u(8) diff --git a/qmail-newu.c b/qmail-newu.c @@ -0,0 +1,137 @@ +#include "stralloc.h" +#include "subfd.h" +#include "getln.h" +#include "substdio.h" +#include "cdbmss.h" +#include "exit.h" +#include "readwrite.h" +#include "open.h" +#include "error.h" +#include "case.h" +#include "auto_qmail.h" + +void die_temp() { _exit(111); } + +void die_chdir() +{ + substdio_putsflush(subfderr,"qmail-newu: fatal: unable to chdir\n"); + die_temp(); +} +void die_nomem() +{ + substdio_putsflush(subfderr,"qmail-newu: fatal: out of memory\n"); + die_temp(); +} +void die_opena() +{ + substdio_putsflush(subfderr,"qmail-newu: fatal: unable to open users/assign\n"); + die_temp(); +} +void die_reada() +{ + substdio_putsflush(subfderr,"qmail-newu: fatal: unable to read users/assign\n"); + die_temp(); +} +void die_format() +{ + substdio_putsflush(subfderr,"qmail-newu: fatal: bad format in users/assign\n"); + die_temp(); +} +void die_opent() +{ + substdio_putsflush(subfderr,"qmail-newu: fatal: unable to open users/cdb.tmp\n"); + die_temp(); +} +void die_writet() +{ + substdio_putsflush(subfderr,"qmail-newu: fatal: unable to write users/cdb.tmp\n"); + die_temp(); +} +void die_rename() +{ + substdio_putsflush(subfderr,"qmail-newu: fatal: unable to move users/cdb.tmp to users/cdb\n"); + die_temp(); +} + +struct cdbmss cdbmss; +stralloc key = {0}; +stralloc data = {0}; + +char inbuf[1024]; +substdio ssin; + +int fd; +int fdtemp; + +stralloc line = {0}; +int match; + +stralloc wildchars = {0}; + +void main() +{ + int i; + int numcolons; + + umask(033); + if (chdir(auto_qmail) == -1) die_chdir(); + + fd = open_read("users/assign"); + if (fd == -1) die_opena(); + + substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf)); + + fdtemp = open_trunc("users/cdb.tmp"); + if (fdtemp == -1) die_opent(); + + if (cdbmss_start(&cdbmss,fdtemp) == -1) die_writet(); + + if (!stralloc_copys(&wildchars,"")) die_nomem(); + + for (;;) { + if (getln(&ssin,&line,&match,'\n') != 0) die_reada(); + if (line.len && (line.s[0] == '.')) break; + if (!match) die_format(); + + if (byte_chr(line.s,line.len,'\0') < line.len) die_format(); + i = byte_chr(line.s,line.len,':'); + if (i == line.len) die_format(); + if (i == 0) die_format(); + if (!stralloc_copys(&key,"!")) die_nomem(); + if (line.s[0] == '+') { + if (!stralloc_catb(&key,line.s + 1,i - 1)) die_nomem(); + case_lowerb(key.s,key.len); + if (i >= 2) + if (byte_chr(wildchars.s,wildchars.len,line.s[i - 1]) == wildchars.len) + if (!stralloc_append(&wildchars,line.s + i - 1)) die_nomem(); + } + else { + if (!stralloc_catb(&key,line.s + 1,i - 1)) die_nomem(); + if (!stralloc_0(&key)) die_nomem(); + case_lowerb(key.s,key.len); + } + + if (!stralloc_copyb(&data,line.s + i + 1,line.len - i - 1)) die_nomem(); + + numcolons = 0; + for (i = 0;i < data.len;++i) + if (data.s[i] == ':') { + data.s[i] = 0; + if (++numcolons == 6) + break; + } + if (numcolons < 6) die_format(); + data.len = i; + + if (cdbmss_add(&cdbmss,key.s,key.len,data.s,data.len) == -1) die_writet(); + } + + if (cdbmss_add(&cdbmss,"",0,wildchars.s,wildchars.len) == -1) die_writet(); + + if (cdbmss_finish(&cdbmss) == -1) die_writet(); + if (fsync(fdtemp) == -1) die_writet(); + if (close(fdtemp) == -1) die_writet(); /* NFS stupidity */ + if (rename("users/cdb.tmp","users/cdb") == -1) die_rename(); + + _exit(0); +} diff --git a/qmail-pop3d.8 b/qmail-pop3d.8 @@ -0,0 +1,43 @@ +.TH qmail-pop3d 8 +.SH NAME +qmail-pop3d \- distribute mail via POP +.SH SYNOPSIS +.B qmail-pop3d +.I maildirname +.SH DESCRIPTION +.B qmail-pop3d +lets a user read and delete his mail through the network. + +Mail is stored in a +.B maildir +called +.IR maildirname , +normally +.BR Maildir , +in the user's home directory. + +.B qmail-pop3d +is normally invoked +under +.BR qmail-popup , +which reads a username and password, +and +.BR /bin/checkpassword , +which checks the password and sets up environment variables. + +.B qmail-pop3d +has a 20-minute idle timeout. + +.B qmail-pop3d +supports UIDL and TOP. + +.B qmail-pop3d +appends an extra blank line to every message +to work around serious bugs in certain clients. + +.B qmail-pop3d +is based on a program contributed by Russ Nelson. +.SH "SEE ALSO" +maildir(5), +qmail-local(8), +qmail-popup(8) diff --git a/qmail-pop3d.c b/qmail-pop3d.c @@ -0,0 +1,397 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include "direntry.h" +#include "sig.h" +#include "getln.h" +#include "stralloc.h" +#include "substdio.h" +#include "alloc.h" +#include "datetime.h" +#include "prot.h" +#include "open.h" +#include "prioq.h" +#include "scan.h" +#include "fmt.h" +#include "error.h" +#include "str.h" +#include "exit.h" +#include "now.h" +#include "readwrite.h" + +int timeout = 1200; + +char ssoutbuf[1024]; +substdio ssout = SUBSTDIO_FDBUF(write,1,ssoutbuf,sizeof(ssoutbuf)); + +int timeoutread(fd,buf,n) int fd; char *buf; int n; +{ + int r; int saveerrno; + alarm(timeout); + r = read(fd,buf,n); saveerrno = errno; + alarm(0); + errno = saveerrno; return r; +} + +char ssinbuf[128]; +substdio ssin = SUBSTDIO_FDBUF(timeoutread,0,ssinbuf,sizeof(ssinbuf)); + + +void die() { _exit(0); } +void puts(s) char *s; +{ + if (substdio_puts(&ssout,s) == -1) die(); +} +void flush() +{ + if (substdio_flush(&ssout) == -1) die(); +} +void err(s) char *s; +{ + puts("-ERR "); + puts(s); + puts("\r\n"); + if (substdio_flush(&ssout) == -1) die(); +} +void die_nomem() { err("out of memory"); die(); } +void die_prot() { err("protection problem"); die(); } +void die_nomaildir() { err("this user has no $HOME/Maildir"); die(); } + +void err_syntax() { err("syntax error"); } +void err_unimpl() { err("unimplemented"); } +void err_deleted() { err("already deleted"); } +void err_nozero() { err("messages are counted from 1"); } +void err_toobig() { err("not that many messages"); } +void err_nosuch() { err("unable to open that message"); } +void err_nounlink() { err("unable to unlink all deleted messages"); } + +void okay() { puts("+OK \r\n"); flush(); } +void pop3_last() { puts("+OK 0\r\n"); flush(); } + + +stralloc dataline = {0}; + +stralloc filenames = {0}; +prioq pq = {0}; +stralloc newname = {0}; + +struct message + { + int flagdeleted; + unsigned long size; + char *fn; + } +*m; +int numm; + +substdio ssmsg; char ssmsgbuf[1024]; + + +void blast(ssfrom,limit) +substdio *ssfrom; +unsigned long limit; +{ + int match; + int inheaders = 1; + + for (;;) + { + if (getln(ssfrom,&dataline,&match,'\n') != 0) die(); + if (!match && !dataline.len) break; + if (match) --dataline.len; /* no way to pass this info over POP */ + if (limit) if (!inheaders) if (!--limit) break; + if (!dataline.len) + inheaders = 0; + else + if (dataline.s[0] == '.') + substdio_put(&ssout,".",1); + if (substdio_put(&ssout,dataline.s,dataline.len) == -1) die(); + if (substdio_put(&ssout,"\r\n",2) == -1) die(); + if (!match) break; + } + if (substdio_put(&ssout,"\r\n.\r\n",5) == -1) die(); + if (substdio_flush(&ssout) == -1) die(); +} + +void getlist() +{ + unsigned long pos; + datetime_sec time; + DIR *dir; + direntry *d; + struct prioq_elt pe; + struct stat st; + int i; + + numm = 0; + + time = now(); + + if (dir = opendir("tmp")) + { + while (d = readdir(dir)) + { + if (str_equal(d->d_name,".")) continue; + if (str_equal(d->d_name,"..")) continue; + if (!stralloc_copys(&newname,"tmp/")) die_nomem(); + if (!stralloc_cats(&newname,d->d_name)) die_nomem(); + if (!stralloc_0(&newname)) die_nomem(); + if (stat(newname.s,&st) == 0) + if (time > st.st_atime + 129600) + unlink(newname.s); + } + closedir(dir); + } + + if (!stralloc_copys(&filenames,"")) die_nomem(); + + if (dir = opendir("new")) + { + while (d = readdir(dir)) + { + if (str_equal(d->d_name,".")) continue; + if (str_equal(d->d_name,"..")) continue; + pos = filenames.len; + if (!stralloc_cats(&filenames,"new/")) die_nomem(); + if (!stralloc_cats(&filenames,d->d_name)) die_nomem(); + if (!stralloc_0(&filenames)) die_nomem(); + if (stat(filenames.s + pos,&st) == 0) + { + pe.dt = st.st_mtime; + pe.id = pos; + if (!prioq_insert(&pq,&pe)) die_nomem(); + ++numm; + } + } + closedir(dir); + } + + if (dir = opendir("cur")) + { + while (d = readdir(dir)) + { + if (str_equal(d->d_name,".")) continue; + if (str_equal(d->d_name,"..")) continue; + pos = filenames.len; + if (!stralloc_cats(&filenames,"cur/")) die_nomem(); + if (!stralloc_cats(&filenames,d->d_name)) die_nomem(); + if (!stralloc_0(&filenames)) die_nomem(); + if (stat(filenames.s + pos,&st) == 0) + { + pe.dt = st.st_mtime; + pe.id = pos; + if (!prioq_insert(&pq,&pe)) die_nomem(); + ++numm; + } + } + closedir(dir); + } + + m = (struct message *) alloc(numm * sizeof(struct message)); + if (!m) die_nomem(); + + for (i = 0;i < numm;++i) + { + if (!prioq_min(&pq,&pe)) { numm = i; break; } + prioq_delmin(&pq); + m[i].fn = filenames.s + pe.id; + m[i].flagdeleted = 0; + if (stat(m[i].fn,&st) == -1) + m[i].size = 0; + else + m[i].size = st.st_size; + } +} + +char foo[FMT_ULONG]; + +void printint(u) unsigned int u; +{ + foo[fmt_uint(foo,u)] = 0; + puts(foo); + puts(" "); +} + +void printlong(u) unsigned long u; +{ + foo[fmt_uint(foo,u)] = 0; + puts(foo); + puts("\r\n"); +} + +void printfn(fn) char *fn; +{ + puts(fn + 4); + puts("\r\n"); +} + +void pop3_stat() +{ + int i; + unsigned long total; + + total = 0; + for (i = 0;i < numm;++i) if (!m[i].flagdeleted) total += m[i].size; + puts("+OK "); + printint(numm); + printlong(total); + flush(); +} + +void pop3_rset() +{ + int i; + for (i = 0;i < numm;++i) m[i].flagdeleted = 0; + okay(); +} + +void pop3_quit() +{ + int i; + for (i = 0;i < numm;++i) + if (m[i].flagdeleted) + if (unlink(m[i].fn) == -1) err_nounlink(); + okay(); + die(); +} + +int msgno(arg) char *arg; +{ + unsigned long u; + if (!arg) { err_syntax(); return -1; } + if (!scan_ulong(arg,&u)) { err_syntax(); return -1; } + if (!u) { err_nozero(); return -1; } + --u; + if (u >= numm) { err_toobig(); return -1; } + if (m[u].flagdeleted) { err_deleted(); return -1; } + return u; +} + +void pop3_dele(arg) char *arg; +{ + int i; + + i = msgno(arg); + if (i == -1) return; + m[i].flagdeleted = 1; + okay(); +} + +void dolisting(arg,flaguidl) char *arg; int flaguidl; +{ + unsigned int i; + + if (arg) + { + i = msgno(arg); + if (i == -1) return; + puts("+OK "); + printint(i + 1); + if (flaguidl) printfn(m[i].fn); else printlong(m[i].size); + } + else + { + okay(); + + for (i = 0;i < numm;++i) + if (!m[i].flagdeleted) + { + printint(i + 1); + if (flaguidl) printfn(m[i].fn); else printlong(m[i].size); + } + puts(".\r\n"); + } + flush(); +} + +void pop3_uidl(arg) char *arg; { dolisting(arg,1); } +void pop3_list(arg) char *arg; { dolisting(arg,0); } + +void pop3_top(arg) char *arg; +{ + int i; + unsigned long limit; + int fd; + + i = msgno(arg); + if (i == -1) return; + + arg += scan_ulong(arg,&limit); + while (*arg == ' ') ++arg; + if (scan_ulong(arg,&limit)) ++limit; else limit = 0; + + fd = open_read(m[i].fn); + if (fd == -1) { err_nosuch(); return; } + okay(); + substdio_fdbuf(&ssmsg,read,fd,ssmsgbuf,sizeof(ssmsgbuf)); + blast(&ssmsg,limit); + close(fd); +} + +static struct { void (*fun)(); char *text; } pop3cmd[] = { + { pop3_quit, "quit" } +, { pop3_stat, "stat" } +, { pop3_list, "list" } +, { pop3_uidl, "uidl" } +, { pop3_dele, "dele" } +, { pop3_top, "retr" } +, { pop3_rset, "rset" } +, { pop3_last, "last" } +, { pop3_top, "top" } +, { okay, "noop" } +, { 0, 0 } +}; + +void doit(cmd) +char *cmd; +{ + int i; + int j; + char ch; + + for (i = 0;pop3cmd[i].fun;++i) + { + for (j = 0;ch = pop3cmd[i].text[j];++j) + if ((cmd[j] != ch) && (cmd[j] != ch - 32)) + break; + if (!ch) + if (!cmd[j] || (cmd[j] == ' ')) + { + while (cmd[j] == ' ') ++j; + if (!cmd[j]) + pop3cmd[i].fun((char *) 0); + else + pop3cmd[i].fun(cmd + j); + return; + } + } + err_unimpl(); +} + +void main(argc,argv) +int argc; +char **argv; +{ + static stralloc cmd = {0}; + int match; + + sig_alarmcatch(die); + sig_pipeignore(); + + if (!argv[1]) die_nomaildir(); + if (chdir(argv[1]) == -1) die_nomaildir(); + + getlist(); + + okay(); + + for (;;) + { + if (getln(&ssin,&cmd,&match,'\n') == -1) die(); + if (!match) die(); + if (cmd.len == 0) die(); + if (cmd.s[--cmd.len] != '\n') die(); + if ((cmd.len > 0) && (cmd.s[cmd.len - 1] == '\r')) --cmd.len; + cmd.s[cmd.len++] = 0; + doit(cmd.s); + } +} diff --git a/qmail-popup.8 b/qmail-popup.8 @@ -0,0 +1,65 @@ +.TH qmail-popup 8 +.SH NAME +qmail-popup \- read a POP username and password +.SH SYNOPSIS +.B qmail-popup +.I hostname +.I subprogram +.SH DESCRIPTION +.B qmail-popup +reads a POP username and password from the network. +It then runs +.IR subprogram . + +.B qmail-popup +is most commonly invoked from +.B inetd +as + +.EX + qmail-popup CHANGEME checkpassword qmail-pop3d Maildir +.EE + +with +CHANGEME +replaced by the fully qualified domain name of the local host. + +.B qmail-popup +expects descriptor 0 to read from the network +and descriptor 1 to write to the network. +It reads a username and password from descriptor 0 +in POP's USER-PASS style or APOP style. +It invokes +.IR subprogram , +with the same descriptors 0 and 1; +descriptor 2 writing to the network; +and descriptor 3 reading the username, a 0 byte, the password, +another 0 byte, +an APOP timestamp derived from +.IR hostname , +and a final 0 byte. +.B qmail-popup +then waits for +.I subprogram +to finish. +It prints an error message if +.I subprogram +crashes or exits nonzero. + +.B qmail-popup +should be used only within +a secure network. +Otherwise an eavesdropper can steal passwords. +Even if you use APOP, +an active attacker can still take over the connection +and wreak havoc. + +.B qmail-popup +has a 20-minute idle timeout. + +.B qmail-popup +is based on a program contributed by Russ Nelson. +.SH "SEE ALSO" +maildir(5), +qmail-local(8), +qmail-pop3d(8) diff --git a/qmail-popup.c b/qmail-popup.c @@ -0,0 +1,219 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include "fd.h" +#include "sig.h" +#include "getln.h" +#include "stralloc.h" +#include "substdio.h" +#include "subfd.h" +#include "alloc.h" +#include "datetime.h" +#include "error.h" +#include "wait.h" +#include "str.h" +#include "now.h" +#include "fmt.h" +#include "exit.h" +#include "readwrite.h" + +int timeout = 1200; + +int timeoutread(fd,buf,n) int fd; char *buf; int n; +{ + int r; int saveerrno; + alarm(timeout); + r = read(fd,buf,n); saveerrno = errno; + alarm(0); + errno = saveerrno; return r; +} + +char ssinbuf[128]; +substdio ssin = SUBSTDIO_FDBUF(timeoutread,0,ssinbuf,sizeof(ssinbuf)); + + +void die() { _exit(1); } +void out(s) char *s; +{ + if (substdio_puts(subfdoutsmall,s) == -1) die(); +} +void outflush(s) char *s; +{ + out(s); + if (substdio_flush(subfdoutsmall) == -1) die(); +} +void err(s) char *s; +{ + if (substdio_puts(subfdoutsmall,"-ERR ") == -1) die(); + if (substdio_puts(subfdoutsmall,s) == -1) die(); + if (substdio_puts(subfdoutsmall,"\r\n") == -1) die(); + if (substdio_flush(subfdoutsmall) == -1) die(); +} +void die_usage() { err("usage: popup hostname subprogram"); die(); } +void die_nomem() { err("out of memory"); die(); } +void die_pipe() { err("unable to open pipe"); die(); } +void die_write() { err("unable to write pipe"); die(); } +void die_fork() { err("unable to fork"); die(); } +void die_childcrashed() { err("aack, child crashed"); } +void die_badauth() { err("authorization failed"); } + +void err_syntax() { err("syntax error"); } +void err_wantuser() { err("USER first"); } +void err_authoriz() { err("authorization first"); } + +void okay() { outflush("+OK \r\n"); } + + +char unique[FMT_ULONG + FMT_ULONG + 3]; +char *hostname; +stralloc username = {0}; +int seenuser = 0; +char **childargs; +substdio ssup; +char upbuf[128]; + + +void doanddie(user,userlen,pass) +char *user; +unsigned int userlen; /* including 0 byte */ +char *pass; +{ + int child; + int wstat; + int pi[2]; + int i; + + if (fd_copy(2,1) == -1) die_pipe(); + close(3); + if (pipe(pi) == -1) die_pipe(); + if (pi[0] != 3) die_pipe(); + switch(child = fork()) + { + case -1: + die_fork(); + case 0: + close(pi[1]); + sig_pipedefault(); + execvp(*childargs,childargs); + _exit(1); + } + close(pi[0]); + substdio_fdbuf(&ssup,write,pi[1],upbuf,sizeof(upbuf)); + if (substdio_put(&ssup,user,userlen) == -1) die_write(); + if (substdio_put(&ssup,pass,str_len(pass) + 1) == -1) die_write(); + if (substdio_puts(&ssup,"<") == -1) die_write(); + if (substdio_puts(&ssup,unique) == -1) die_write(); + if (substdio_puts(&ssup,hostname) == -1) die_write(); + if (substdio_put(&ssup,">",2) == -1) die_write(); + if (substdio_flush(&ssup) == -1) die_write(); + close(pi[1]); + for (i = 0;pass[i];++i) pass[i] = 0; + for (i = 0;i < sizeof(upbuf);++i) upbuf[i] = 0; + if (wait_pid(&wstat,child) == -1) die(); + if (wait_crashed(wstat)) die_childcrashed(); + if (wait_exitcode(wstat)) die_badauth(); + die(); +} +void pop3_greet() +{ + char *s; + s = unique; + s += fmt_uint(s,getpid()); + *s++ = '.'; + s += fmt_ulong(s,(unsigned long) now()); + *s++ = '@'; + *s++ = 0; + + out("+OK <"); + out(unique); + out(hostname); + outflush(">\r\n"); +} +void pop3_user(arg) char *arg; +{ + if (!arg) { err_syntax(); return; } + okay(); + seenuser = 1; + if (!stralloc_copys(&username,arg)) die_nomem(); + if (!stralloc_0(&username)) die_nomem(); +} +void pop3_pass(arg) char *arg; +{ + if (!seenuser) { err_wantuser(); return; } + if (!arg) { err_syntax(); return; } + doanddie(username.s,username.len,arg); +} +void pop3_apop(arg) char *arg; +{ + char *space; + if (!arg) { err_syntax(); return; } + space = arg + str_chr(arg,' '); + if (!*space) { err_syntax(); return; } + *space++ = 0; + doanddie(arg,space - arg,space); +} + +void pop3_quit() { okay(); die(); } + +static struct { void (*fun)(); char *text; } pop3cmd[] = { + { pop3_user, "user" } +, { pop3_pass, "pass" } +, { pop3_apop, "apop" } +, { pop3_quit, "quit" } +, { okay, "noop" } +, { 0, 0 } +}; + +void doit(cmd) +char *cmd; +{ + int i; + int j; + char ch; + + for (i = 0;pop3cmd[i].fun;++i) + { + for (j = 0;ch = pop3cmd[i].text[j];++j) + if ((cmd[j] != ch) && (cmd[j] != ch - 32)) + break; + if (!ch) + if (!cmd[j] || (cmd[j] == ' ')) + { + while (cmd[j] == ' ') ++j; + if (!cmd[j]) + pop3cmd[i].fun((char *) 0); + else + pop3cmd[i].fun(cmd + j); + return; + } + } + err_authoriz(); +} + +void main(argc,argv) +int argc; +char **argv; +{ + static stralloc cmd = {0}; + int match; + + sig_alarmcatch(die); + sig_pipeignore(); + + hostname = argv[1]; + if (!hostname) die_usage(); + childargs = argv + 2; + if (!*childargs) die_usage(); + + pop3_greet(); + + for (;;) + { + if (getln(&ssin,&cmd,&match,'\n') == -1) die(); + if (!match) die(); + if (cmd.len == 0) die(); + if (cmd.s[--cmd.len] != '\n') die(); + if ((cmd.len > 0) && (cmd.s[cmd.len - 1] == '\r')) --cmd.len; + cmd.s[cmd.len++] = 0; + doit(cmd.s); + } +} diff --git a/qmail-pw2u.9 b/qmail-pw2u.9 @@ -0,0 +1,235 @@ +.TH qmail-pw2u 8 +.SH NAME +qmail-pw2u \- build address assignments from a passwd file +.SH SYNOPSIS +.B qmail-pw2u +[ +.B \-ohHuUC +] +[ +.B \-c\fIchar +] +.SH DESCRIPTION +.B qmail-pw2u +reads a V7-format passwd file from standard input +and prints a +.BR qmail-users -format +assignment file. + +A V7-format passwd file is a series of lines. +Each line has the format + +.EX + user:password:uid:gid:gecos:home:shell +.EE + +where +.I user +is an account name, +.I uid +and +.I gid +are the user id and group id of that account, +and +.I home +is the account's home directory. +.IR password , +.IR gecos , +and +.I shell +are ignored by +.BR qmail-pw2u . + +If you put the output of +.B qmail-pw2u +into +.BR QMAILHOME/users/assign , +and then run +.BR qmail-newu , +.B qmail-lspawn +will obey the assignments printed by +.BR qmail-pw2u . +.B WARNING: +After changing any users, uids, gids, or home directories +in your passwd file, +you must run +.B qmail-pw2u +and +.B qmail-newu +again if you want +.B qmail-lspawn +to see the changes. +.SH RULES +By default, +.B qmail-pw2u +follows the same rules as +.BR qmail-getpw . +It skips +.I user +if (1) +.I uid +is zero, +(2) +.I home +does not exist, +(3) +.I user +does not own +.IR home , +or +(4) +.I user +contains uppercase letters. +It then gives each remaining +.I user +control over the basic +.I user +address and +all addresses of the form +.IR user\fBBREAK\fIanything . +A catch-all user, +.BR alias , +controls all other addresses. + +You may change these rules by setting up files in +.BR QMAILHOME/users : +.TP +.B include +Allowed users, one per line. +If +.B include +exists, and +.I user +is not listed in +.BR include , +.I user +is ignored. +.TP +.B exclude +Ignored users, one per line. +If +.B exclude +exists, and +.I user +is listed in +.BR exclude , +.I user +is ignored. +.TP +.B mailnames +Replacement names for users. +Each line has the form + +.EX + user:mailname1:mailname2:... +.EE + +The addresses +.I mailname1 +and +.I mailname1\fBBREAK\fIext +and +.I mailname2 +and so on will be delivered +to +.IR user . + +.B WARNING: +The addresses +.I user +and +.I user\fBBREAK\fIext +will not be delivered to +.I user +unless +.I user +is listed as one of the +.IR mailname s. + +A line in +.B mailnames +is silently ignored if the user does not exist. +.TP +.B subusers +Extra addresses. +Each line has the form + +.EX + sub:user:pre: +.EE + +.I sub +will be handled by +.IR home\fB/.qmail-\fIpre , +where +.I home +is +.IR user 's +home directory; +.I sub\fBBREAK\fIext +will be handled by +.IR home\fB/.qmail-\fIpre\fB-\fIext . +.TP +.B append +Extra assignments, +printed at the end of +.BR qmail-pw2u 's +output. +.SH OPTIONS +.TP +.B \-o +(Default.) +Skip +.I user +if +.I home +does not exist (or is not visible to +.BR qmail-pw2u ). +Skip +.I user +if +.I home +is not owned by +.IR user . +.TP +.B \-h +Stop if +.I home +does not exist. +This is appropriate if every user is supposed to have a home directory. +Skip +.I user +if +.I home +is not owned by +.IR user . +.TP +.B \-H +Do not check the existence or ownership of +.IR home . +.TP +.B \-U +(Default.) +Skip +.I user +if there are any uppercase letters in +.IR user . +.TP +.B \-u +Allow uppercase letters in +.IR user . +.TP +.B \-c\fIchar +Use +.I char +as the user-extension delimiter +in place of +.BR BREAK . +.TP +.B \-C +Disable the user-extension mechanism. +.SH "SEE ALSO" +qmail-users(5), +qmail-lspawn(8), +qmail-newu(8), +qmail-getpw(8) diff --git a/qmail-pw2u.c b/qmail-pw2u.c @@ -0,0 +1,308 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include "substdio.h" +#include "readwrite.h" +#include "subfd.h" +#include "sgetopt.h" +#include "control.h" +#include "constmap.h" +#include "stralloc.h" +#include "fmt.h" +#include "str.h" +#include "scan.h" +#include "open.h" +#include "error.h" +#include "getln.h" +#include "auto_break.h" +#include "auto_qmail.h" +#include "auto_usera.h" + +void die_chdir() +{ + substdio_putsflush(subfderr,"qmail-pw2u: fatal: unable to chdir\n"); + _exit(111); +} +void die_nomem() +{ + substdio_putsflush(subfderr,"qmail-pw2u: fatal: out of memory\n"); + _exit(111); +} +void die_read() +{ + substdio_putsflush(subfderr,"qmail-pw2u: fatal: unable to read input\n"); + _exit(111); +} +void die_write() +{ + substdio_putsflush(subfderr,"qmail-pw2u: fatal: unable to write output\n"); + _exit(111); +} +void die_control() +{ + substdio_putsflush(subfderr,"qmail-pw2u: fatal: unable to read controls\n"); + _exit(111); +} +void die_alias() +{ + substdio_puts(subfderr,"qmail-pw2u: fatal: unable to find "); + substdio_puts(subfderr,auto_usera); + substdio_puts(subfderr," user\n"); + substdio_flush(subfderr); + _exit(111); +} +void die_home(fn) char *fn; +{ + substdio_puts(subfderr,"qmail-pw2u: fatal: unable to stat "); + substdio_puts(subfderr,fn); + substdio_puts(subfderr,"\n"); + substdio_flush(subfderr); + _exit(111); +} +void die_user(s,len) char *s; unsigned int len; +{ + substdio_puts(subfderr,"qmail-pw2u: fatal: unable to find "); + substdio_put(subfderr,s,len); + substdio_puts(subfderr," user for subuser\n"); + substdio_flush(subfderr); + _exit(111); +} + +int flagalias = 0; +int flagnoupper = 1; +int homestrategy = 2; +/* 2: skip if home does not exist; skip if home is not owned by user */ +/* 1: stop if home does not exist; skip if home is not owned by user */ +/* 0: don't worry about home */ + +int okincl; stralloc incl = {0}; struct constmap mapincl; +int okexcl; stralloc excl = {0}; struct constmap mapexcl; +int okmana; stralloc mana = {0}; struct constmap mapmana; + +stralloc allusers = {0}; struct constmap mapuser; + +stralloc uugh = {0}; +stralloc user = {0}; +stralloc uidstr = {0}; +stralloc gidstr = {0}; +stralloc home = {0}; +unsigned long uid; + +stralloc line = {0}; + +void doaccount() +{ + struct stat st; + int i; + char *mailnames; + char *x; + unsigned int xlen; + + if (byte_chr(line.s,line.len,'\0') < line.len) return; + + x = line.s; xlen = line.len; i = byte_chr(x,xlen,':'); if (i == xlen) return; + if (!stralloc_copyb(&user,x,i)) die_nomem(); + if (!stralloc_0(&user)) die_nomem(); + ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return; + ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return; + if (!stralloc_copyb(&uidstr,x,i)) die_nomem(); + if (!stralloc_0(&uidstr)) die_nomem(); + scan_ulong(uidstr.s,&uid); + ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return; + if (!stralloc_copyb(&gidstr,x,i)) die_nomem(); + if (!stralloc_0(&gidstr)) die_nomem(); + ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return; + ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return; + if (!stralloc_copyb(&home,x,i)) die_nomem(); + if (!stralloc_0(&home)) die_nomem(); + + if (!uid) return; + if (flagnoupper) + for (i = 0;i < user.len;++i) + if ((user.s[i] >= 'A') && (user.s[i] <= 'Z')) + return; + if (okincl) + if (!constmap(&mapincl,user.s,user.len - 1)) + return; + if (okexcl) + if (constmap(&mapexcl,user.s,user.len - 1)) + return; + if (homestrategy) { + if (stat(home.s,&st) == -1) { + if (errno != error_noent) die_home(home.s); + if (homestrategy == 1) die_home(home.s); + return; + } + if (st.st_uid != uid) return; + } + + if (!stralloc_copys(&uugh,":")) die_nomem(); + if (!stralloc_cats(&uugh,user.s)) die_nomem(); + if (!stralloc_cats(&uugh,":")) die_nomem(); + if (!stralloc_cats(&uugh,uidstr.s)) die_nomem(); + if (!stralloc_cats(&uugh,":")) die_nomem(); + if (!stralloc_cats(&uugh,gidstr.s)) die_nomem(); + if (!stralloc_cats(&uugh,":")) die_nomem(); + if (!stralloc_cats(&uugh,home.s)) die_nomem(); + if (!stralloc_cats(&uugh,":")) die_nomem(); + + /* XXX: avoid recording in allusers unless sub actually needs it */ + if (!stralloc_cats(&allusers,user.s)) die_nomem(); + if (!stralloc_cats(&allusers,":")) die_nomem(); + if (!stralloc_catb(&allusers,uugh.s,uugh.len)) die_nomem(); + if (!stralloc_0(&allusers)) die_nomem(); + + if (str_equal(user.s,auto_usera)) { + if (substdio_puts(subfdout,"+") == -1) die_write(); + if (substdio_put(subfdout,uugh.s,uugh.len) == -1) die_write(); + if (substdio_puts(subfdout,"-::\n") == -1) die_write(); + flagalias = 1; + } + + mailnames = 0; + if (okmana) + mailnames = constmap(&mapmana,user.s,user.len - 1); + if (!mailnames) + mailnames = user.s; + + for (;;) { + while (*mailnames == ':') ++mailnames; + if (!*mailnames) break; + + i = str_chr(mailnames,':'); + + if (substdio_puts(subfdout,"=") == -1) die_write(); + if (substdio_put(subfdout,mailnames,i) == -1) die_write(); + if (substdio_put(subfdout,uugh.s,uugh.len) == -1) die_write(); + if (substdio_puts(subfdout,"::\n") == -1) die_write(); + + if (*auto_break) { + if (substdio_puts(subfdout,"+") == -1) die_write(); + if (substdio_put(subfdout,mailnames,i) == -1) die_write(); + if (substdio_put(subfdout,auto_break,1) == -1) die_write(); + if (substdio_put(subfdout,uugh.s,uugh.len) == -1) die_write(); + if (substdio_puts(subfdout,"-::\n") == -1) die_write(); + } + + mailnames += i; + } +} + +stralloc sub = {0}; + +void dosubuser() +{ + int i; + char *x; + unsigned int xlen; + char *uugh; + + x = line.s; xlen = line.len; i = byte_chr(x,xlen,':'); if (i == xlen) return; + if (!stralloc_copyb(&sub,x,i)) die_nomem(); + ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return; + uugh = constmap(&mapuser,x,i); + if (!uugh) die_user(x,i); + ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return; + + if (substdio_puts(subfdout,"=") == -1) die_write(); + if (substdio_put(subfdout,sub.s,sub.len) == -1) die_write(); + if (substdio_puts(subfdout,uugh) == -1) die_write(); + if (substdio_puts(subfdout,"-:") == -1) die_write(); + if (substdio_put(subfdout,x,i) == -1) die_write(); + if (substdio_puts(subfdout,":\n") == -1) die_write(); + + if (*auto_break) { + if (substdio_puts(subfdout,"+") == -1) die_write(); + if (substdio_put(subfdout,sub.s,sub.len) == -1) die_write(); + if (substdio_put(subfdout,auto_break,1) == -1) die_write(); + if (substdio_puts(subfdout,uugh) == -1) die_write(); + if (substdio_puts(subfdout,"-:") == -1) die_write(); + if (substdio_put(subfdout,x,i) == -1) die_write(); + if (substdio_puts(subfdout,"-:\n") == -1) die_write(); + } +} + +int fd; +substdio ss; +char ssbuf[SUBSTDIO_INSIZE]; + +void main(argc,argv) +int argc; +char **argv; +{ + int opt; + int match; + + while ((opt = getopt(argc,argv,"ohHuUc:C")) != opteof) + switch(opt) { + case 'o': homestrategy = 2; break; + case 'h': homestrategy = 1; break; + case 'H': homestrategy = 0; break; + case 'u': flagnoupper = 0; break; + case 'U': flagnoupper = 1; break; + case 'c': *auto_break = *optarg; break; + case 'C': *auto_break = 0; break; + case '?': + default: + _exit(100); + } + + if (chdir(auto_qmail) == -1) die_chdir(); + + /* no need for control_init() */ + + okincl = control_readfile(&incl,"users/include",0); + if (okincl == -1) die_control(); + if (okincl) if (!constmap_init(&mapincl,incl.s,incl.len,0)) die_nomem(); + + okexcl = control_readfile(&excl,"users/exclude",0); + if (okexcl == -1) die_control(); + if (okexcl) if (!constmap_init(&mapexcl,excl.s,excl.len,0)) die_nomem(); + + okmana = control_readfile(&mana,"users/mailnames",0); + if (okmana == -1) die_control(); + if (okmana) if (!constmap_init(&mapmana,mana.s,mana.len,1)) die_nomem(); + + if (!stralloc_copys(&allusers,"")) die_nomem(); + + for (;;) { + if (getln(subfdin,&line,&match,'\n') == -1) die_read(); + doaccount(); + if (!match) break; + } + if (!flagalias) die_alias(); + + fd = open_read("users/subusers"); + if (fd == -1) { + if (errno != error_noent) die_control(); + } + else { + substdio_fdbuf(&ss,read,fd,ssbuf,sizeof(ssbuf)); + + if (!constmap_init(&mapuser,allusers.s,allusers.len,1)) die_nomem(); + + for (;;) { + if (getln(&ss,&line,&match,'\n') == -1) die_read(); + dosubuser(); + if (!match) break; + } + + close(fd); + } + + fd = open_read("users/append"); + if (fd == -1) { + if (errno != error_noent) die_control(); + } + else { + substdio_fdbuf(&ss,read,fd,ssbuf,sizeof(ssbuf)); + for (;;) { + if (getln(&ss,&line,&match,'\n') == -1) die_read(); + if (substdio_put(subfdout,line.s,line.len) == -1) die_write(); + if (!match) break; + } + } + + if (substdio_puts(subfdout,".\n") == -1) die_write(); + if (substdio_flush(subfdout) == -1) die_write(); + _exit(0); +} diff --git a/qmail-qmtpd.8 b/qmail-qmtpd.8 @@ -0,0 +1,29 @@ +.TH qmail-qmtpd 8 +.SH NAME +qmail-qmtpd \- receive mail via QMTP +.SH SYNOPSIS +.B qmail-qmtpd +.SH DESCRIPTION +.B qmail-qmtpd +receives mail messages via the Quick Mail Transfer Protocol (QMTP) +and invokes +.B qmail-queue +to deposit them into the outgoing queue. +.B qmail-qmtpd +must be supplied several environment variables; +see +.BR tcp-environ(5) . + +.B qmail-qmtpd +supports the +.I rcpthosts +and +.B RELAYCLIENT +mechanisms described in +.BR qmail-smtpd(8) . +.SH "SEE ALSO" +tcp-env(1), +tcp-environ(5), +qmail-control(5), +qmail-queue(8), +qmail-smtpd(8) diff --git a/qmail-qmtpd.c b/qmail-qmtpd.c @@ -0,0 +1,281 @@ +#include "stralloc.h" +#include "substdio.h" +#include "subfd.h" +#include "qmail.h" +#include "now.h" +#include "str.h" +#include "fmt.h" +#include "env.h" +#include "sig.h" +#include "auto_qmail.h" +#include "now.h" +#include "datetime.h" +#include "date822fmt.h" +#include "readwrite.h" +#include "control.h" +#include "constmap.h" +#include "received.h" + +struct qmail qqt; + +void dropped() { _exit(0); } +void badproto() { _exit(100); } +void resources() { _exit(111); } +void sigalrm() { _exit(111); } + +unsigned long getlen() +{ + unsigned long len; + char ch; + + len = 0; + for (;;) + { + if (substdio_get(subfdinsmall,&ch,1) < 1) dropped(); + if (ch == ':') return len; + if (len > 200000000) resources(); + len = 10 * len + (ch - '0'); + } +} + +void getcomma() +{ + char ch; + + if (substdio_get(subfdinsmall,&ch,1) < 1) dropped(); + if (ch != ',') badproto(); +} + +struct datetime dt; +char buf[1000]; +char buf2[100]; + +char *remotehost; +char *remoteinfo; +char *remoteip; +char *local; + +stralloc failure = {0}; + +int flagrcpthosts; +stralloc rcpthosts = {0}; +struct constmap maprcpthosts; +char *relayclient; +int relayclientlen; + +int addrallowed(buf,len) char *buf; int len; +{ + int j; + if (!flagrcpthosts) return 1; + j = byte_rchr(buf,len,'@'); + if (j >= len) return 1; + if (constmap(&maprcpthosts,buf + j + 1,len - j - 1)) return 1; + for (;j < len;++j) + if (buf[j] == '.') + if (constmap(&maprcpthosts,buf + j,len - j)) return 1; + return 0; +} + +main() +{ + char ch; + int i; + unsigned long biglen; + unsigned long len; + int flagdos; + int flagsenderok; + unsigned long qp; + char *result; + + sig_pipeignore(); + sig_alarmcatch(sigalrm); + alarm(3600); + + if (chdir(auto_qmail) == -1) resources(); + + if (control_init() == -1) resources(); + flagrcpthosts = control_readfile(&rcpthosts,"control/rcpthosts",0); + if (flagrcpthosts == -1) resources(); + if (flagrcpthosts) + if (!constmap_init(&maprcpthosts,rcpthosts.s,rcpthosts.len,0)) resources(); + relayclient = env_get("RELAYCLIENT"); + relayclientlen = relayclient ? str_len(relayclient) : 0; + + remotehost = env_get("TCPREMOTEHOST"); + if (!remotehost) remotehost = "unknown"; + remoteinfo = env_get("TCPREMOTEINFO"); + remoteip = env_get("TCPREMOTEIP"); + if (!remoteip) remoteip = "unknown"; + local = env_get("TCPLOCALHOST"); + if (!local) local = env_get("TCPLOCALIP"); + if (!local) local = "unknown"; + + for (;;) + { + if (!stralloc_copys(&failure,"")) resources(); + flagsenderok = 1; + + len = getlen(); + if (len == 0) badproto(); + + if (qmail_open(&qqt) == -1) resources(); + qp = qmail_qp(&qqt); + + if (substdio_get(subfdinsmall,&ch,1) < 1) dropped(); + --len; + + if (ch == 10) flagdos = 0; + else if (ch == 13) flagdos = 1; + else badproto(); + + received(&qqt,"QMTP",local,remoteip,remotehost,remoteinfo,(char *) 0); + + /* XXX: check for loops? only if len is big? */ + + if (flagdos) + while (len > 0) + { + if (substdio_get(subfdinsmall,&ch,1) < 1) dropped(); + --len; + while ((ch == 13) && len) + { + if (substdio_get(subfdinsmall,&ch,1) < 1) dropped(); + --len; + if (ch == 10) break; + qmail_put(&qqt,"\015",1); + } + qmail_put(&qqt,&ch,1); + } + else + while (len > 0) /* XXX: could speed this up, obviously */ + { + if (substdio_get(subfdinsmall,&ch,1) < 1) dropped(); + --len; + qmail_put(&qqt,&ch,1); + } + getcomma(); + + len = getlen(); + + if (len >= 1000) + { + buf[0] = 0; + flagsenderok = 0; + for (i = 0;i < len;++i) + if (substdio_get(subfdinsmall,&ch,1) < 1) dropped(); + } + else + { + for (i = 0;i < len;++i) + { + if (substdio_get(subfdinsmall,buf + i,1) < 1) dropped(); + if (!buf[i]) flagsenderok = 0; + } + buf[len] = 0; + } + getcomma(); + + qmail_from(&qqt,buf); + if (!flagsenderok) qmail_fail(&qqt); + + biglen = getlen(); + while (biglen > 0) + { + if (!stralloc_append(&failure,"")) resources(); + + len = 0; + for (;;) + { + if (!biglen) badproto(); + if (substdio_get(subfdinsmall,&ch,1) < 1) dropped(); + --biglen; + if (ch == ':') break; + if (len > 200000000) resources(); + len = 10 * len + (ch - '0'); + } + if (len >= biglen) badproto(); + if (len + relayclientlen >= 1000) + { + failure.s[failure.len - 1] = 'L'; + for (i = 0;i < len;++i) + if (substdio_get(subfdinsmall,&ch,1) < 1) dropped(); + } + else + { + for (i = 0;i < len;++i) + { + if (substdio_get(subfdinsmall,buf + i,1) < 1) dropped(); + if (!buf[i]) failure.s[failure.len - 1] = 'N'; + } + buf[len] = 0; + + if (relayclient) + str_copy(buf + len,relayclient); + else + if (!addrallowed(buf,len)) failure.s[failure.len - 1] = 'D'; + + if (!failure.s[failure.len - 1]) + qmail_to(&qqt,buf); + } + getcomma(); + biglen -= (len + 1); + } + getcomma(); + + switch(qmail_close(&qqt)) + { + case 0: result = 0; break; + case QMAIL_WAITPID: result = "Zqq waitpid surprise (#4.3.0)"; break; + case QMAIL_CRASHED: result = "Zqq crashed (#4.3.0)"; break; + case QMAIL_USAGE: result = "Zqq usage surprise (#4.3.0)"; break; + case QMAIL_SYS: result = "Zqq system error (#4.3.0)"; break; + case QMAIL_READ: result = "Zqq read error (#4.3.0)"; break; + case QMAIL_WRITE: result = "Zqq write error or disk full (#4.3.0)"; break; + case QMAIL_NOMEM: result = "Zqq out of memory (#4.3.0)"; break; + case QMAIL_EXECSOFT: result = "Zcould not exec qq (#4.3.0)"; break; + case QMAIL_TIMEOUT: result = "Zqq timeout (#4.3.0)"; break; + case QMAIL_TOOLONG: result = "Dqq toolong surprise (#5.1.3)"; break; + default: result = "Zqq internal bug (#4.3.0)"; break; + } + + if (!flagsenderok) result = "Dunacceptable sender (#5.1.7)"; + + if (result) + len = str_len(result); + else + { + /* success! */ + len = 0; + len += fmt_str(buf2 + len,"Kok "); + len += fmt_ulong(buf2 + len,(unsigned long) now()); + len += fmt_str(buf2 + len," qp "); + len += fmt_ulong(buf2 + len,qp); + buf2[len] = 0; + result = buf2; + } + + len = fmt_ulong(buf,len); + buf[len++] = ':'; + len += fmt_str(buf + len,result); + buf[len++] = ','; + + for (i = 0;i < failure.len;++i) + switch(failure.s[i]) + { + case 0: + if (substdio_put(subfdoutsmall,buf,len) == -1) + dropped(); + break; + case 'D': + if (substdio_puts(subfdoutsmall,"66:Dsorry, that domain isn't in my list of allowed rcpthosts (#5.7.1),") == -1) + dropped(); + break; + default: + if (substdio_puts(subfdoutsmall,"46:Dsorry, I can't handle that recipient (#5.1.3),") == -1) + dropped(); + break; + } + + /* subfdoutsmall will be flushed when we read from the network again */ + } +} diff --git a/qmail-qread.8 b/qmail-qread.8 @@ -0,0 +1,24 @@ +.TH qmail-qread 8 +.SH NAME +qmail-qread \- list outgoing messages and recipients +.SH SYNOPSIS +.B qmail-qread +.SH DESCRIPTION +.B qmail-qread +scans the outgoing queue of messages. +For each message it prints various human-readable information, +including the date the message entered the queue, +the number of bytes in the message, +the message sender, +and all the recipients still under consideration. + +.B qmail-qread +must be run either as +.B root +or with user id +.B qmails +and group id +.BR qmail . +.SH "SEE ALSO" +qmail-qstat(8), +qmail-send(8) diff --git a/qmail-qread.c b/qmail-qread.c @@ -0,0 +1,175 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include "stralloc.h" +#include "substdio.h" +#include "subfd.h" +#include "fmt.h" +#include "str.h" +#include "getln.h" +#include "fmtqfn.h" +#include "readsubdir.h" +#include "auto_qmail.h" +#include "open.h" +#include "datetime.h" +#include "date822fmt.h" +#include "readwrite.h" +#include "error.h" +#include "exit.h" + +readsubdir rs; + +void die(n) int n; { substdio_flush(subfdout); _exit(n); } + +void warn(s1,s2) char *s1; char *s2; +{ + char *x; + x = error_str(errno); + substdio_puts(subfdout,s1); + substdio_puts(subfdout,s2); + substdio_puts(subfdout,": "); + substdio_puts(subfdout,x); + substdio_puts(subfdout,"\n"); +} + +void die_nomem() { substdio_puts(subfdout,"fatal: out of memory\n"); die(111); } +void die_chdir() { warn("fatal: unable to chdir",""); die(111); } +void die_opendir(fn) char *fn; { warn("fatal: unable to opendir ",fn); die(111); } + +void err(id) unsigned long id; +{ + char foo[FMT_ULONG]; + foo[fmt_ulong(foo,id)] = 0; + warn("warning: trouble with #",foo); +} + +char fnmess[FMTQFN]; +char fninfo[FMTQFN]; +char fnlocal[FMTQFN]; +char fnremote[FMTQFN]; +char fnbounce[FMTQFN]; + +char inbuf[1024]; +stralloc sender = {0}; + +unsigned long id; +datetime_sec qtime; +int flagbounce; +unsigned long size; + +unsigned int fmtstats(s) +char *s; +{ + struct datetime dt; + unsigned int len; + unsigned int i; + + len = 0; + datetime_tai(&dt,qtime); + i = date822fmt(s,&dt) - 7/*XXX*/; len += i; if (s) s += i; + i = fmt_str(s," GMT #"); len += i; if (s) s += i; + i = fmt_ulong(s,id); len += i; if (s) s += i; + i = fmt_str(s," "); len += i; if (s) s += i; + i = fmt_ulong(s,size); len += i; if (s) s += i; + i = fmt_str(s," <"); len += i; if (s) s += i; + i = fmt_str(s,sender.s + 1); len += i; if (s) s += i; + i = fmt_str(s,"> "); len += i; if (s) s += i; + if (flagbounce) + { + i = fmt_str(s," bouncing"); len += i; if (s) s += i; + } + + return len; +} + +stralloc stats = {0}; + +void out(s,n) char *s; unsigned int n; +{ + while (n > 0) + { + substdio_put(subfdout,((*s >= 32) && (*s <= 126)) ? s : "_",1); + --n; + ++s; + } +} +void outs(s) char *s; { out(s,str_len(s)); } +void outok(s) char *s; { substdio_puts(subfdout,s); } + +void putstats() +{ + if (!stralloc_ready(&stats,fmtstats(FMT_LEN))) die_nomem(); + stats.len = fmtstats(stats.s); + out(stats.s,stats.len); + outok("\n"); +} + +stralloc line = {0}; + +void main() +{ + int channel; + int match; + struct stat st; + int fd; + substdio ss; + int x; + + if (chdir(auto_qmail) == -1) die_chdir(); + if (chdir("queue") == -1) die_chdir(); + readsubdir_init(&rs,"info",die_opendir); + + while (x = readsubdir_next(&rs,&id)) + if (x > 0) + { + fmtqfn(fnmess,"mess/",id,1); + fmtqfn(fninfo,"info/",id,1); + fmtqfn(fnlocal,"local/",id,1); + fmtqfn(fnremote,"remote/",id,1); + fmtqfn(fnbounce,"bounce/",id,0); + + if (stat(fnmess,&st) == -1) { err(id); continue; } + size = st.st_size; + flagbounce = !stat(fnbounce,&st); + + fd = open_read(fninfo); + if (fd == -1) { err(id); continue; } + substdio_fdbuf(&ss,read,fd,inbuf,sizeof(inbuf)); + if (getln(&ss,&sender,&match,0) == -1) die_nomem(); + if (fstat(fd,&st) == -1) { close(fd); err(id); continue; } + close(fd); + qtime = st.st_mtime; + + putstats(); + + for (channel = 0;channel < 2;++channel) + { + fd = open_read(channel ? fnremote : fnlocal); + if (fd == -1) + { + if (errno != error_noent) + err(id); + } + else + { + for (;;) + { + if (getln(&ss,&line,&match,0) == -1) die_nomem(); + if (!match) break; + switch(line.s[0]) + { + case 'D': + outok(" done"); + case 'T': + outok(channel ? "\tremote\t" : "\tlocal\t"); + outs(line.s + 1); + outok("\n"); + break; + } + } + close(fd); + } + } + } + + die(0); +} diff --git a/qmail-qstat.8 b/qmail-qstat.8 @@ -0,0 +1,18 @@ +.TH qmail-qstat 8 +.SH NAME +qmail-qstat \- summarize status of mail queue +.SH SYNOPSIS +.B qmail-qstat +.SH DESCRIPTION +.B qmail-qstat +gives a human-readable breakdown +of the number of messages at various spots in the mail queue. + +.B qmail-qstat +must be run either as +.B root +or with group id +.BR qmail . +.SH "SEE ALSO" +qmail-qread(8), +qmail-send(8) diff --git a/qmail-qstat.sh b/qmail-qstat.sh @@ -0,0 +1,3 @@ +cd QMAIL +echo messages in queue: `find queue/mess -type f -print | wc -l` +echo messages in queue but not yet preprocessed: `find queue/todo -type f -print | wc -l` diff --git a/qmail-queue.8 b/qmail-queue.8 @@ -0,0 +1,58 @@ +.TH qmail-queue 8 +.SH NAME +qmail-queue \- queue a mail message for delivery +.SH SYNOPSIS +.B qmail-queue +.SH DESCRIPTION +.B qmail-queue +reads a mail message from descriptor 0. +It then reads envelope information from descriptor 1. +It places the message into the outgoing queue +for future delivery by +.BR qmail-send . + +The envelope information is +an envelope sender address +followed by a list of envelope recipient addresses. +The sender address is preceded by the letter F +and terminated by a 0 byte. +Each recipient address is preceded by the letter T +and terminated by a 0 byte. +The list of recipient addresses is terminated by an extra 0 byte. +If +.B qmail-queue +sees end-of-file before the extra 0 byte, +it aborts without placing the message into the queue. + +Every envelope recipient address +must contain a username, +an @ sign, +and a fully qualified domain name. + +.B qmail-queue +always adds a +.B Received +line to the top of the message. +Other than this, +.B qmail-queue +does not inspect the message +and does not enforce any restrictions on its contents. +However, the recipients probably expect to see a proper header, +as described in +.BR qmail-header(5) . +.SH "EXIT CODES" +0 if +.B qmail-queue +has successfully queued the message, +nonzero if +.B qmail-queue +has failed to queue the message. +.B qmail-queue +does not print diagnostics. +.SH "SEE ALSO" +addresses(5), +envelopes(5), +qmail-header(5), +qmail-inject(8), +qmail-send(8), +qmail-smtpd(8) diff --git a/qmail-queue.c b/qmail-queue.c @@ -0,0 +1,254 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include "readwrite.h" +#include "sig.h" +#include "exit.h" +#include "open.h" +#include "seek.h" +#include "fmt.h" +#include "alloc.h" +#include "substdio.h" +#include "datetime.h" +#include "now.h" +#include "triggerpull.h" +#include "extra.h" +#include "auto_qmail.h" +#include "auto_uids.h" +#include "date822fmt.h" +#include "fmtqfn.h" + +#define DEATH 86400 /* 24 hours; _must_ be below q-s's OSSIFIED (36 hours) */ +#define ADDR 1003 + +char inbuf[2048]; +struct substdio ssin; +char outbuf[256]; +struct substdio ssout; + +datetime_sec starttime; +struct datetime dt; +unsigned long mypid; +unsigned long uid; +char *pidfn; +struct stat pidst; +unsigned long messnum; +char *messfn; +char *todofn; +char *intdfn; +int messfd; +int intdfd; +int flagmademess = 0; +int flagmadeintd = 0; + +void cleanup() +{ + if (flagmadeintd) + { + seek_trunc(intdfd,0); + if (unlink(intdfn) == -1) return; + } + if (flagmademess) + { + seek_trunc(messfd,0); + if (unlink(messfn) == -1) return; + } +} + +void die(e) int e; { _exit(e); } +void die_write() { cleanup(); die(122); } +void die_read() { cleanup(); die(121); } +void sigalrm() { /* thou shalt not clean up here */ die(124); } +void sigbug() { die(101); } + +unsigned int receivedlen; +char *received; +/* "Received: (qmail-queue invoked by alias); 26 Sep 1995 04:46:54 -0000\n" */ + +static unsigned int receivedfmt(s) +char *s; +{ + unsigned int i; + unsigned int len; + len = 0; + i = fmt_str(s,"Received: (qmail "); len += i; if (s) s += i; + i = fmt_ulong(s,mypid); len += i; if (s) s += i; + i = fmt_str(s," invoked "); len += i; if (s) s += i; + if (uid == auto_uida) + { i = fmt_str(s,"by alias"); len += i; if (s) s += i; } + else if (uid == auto_uidd) + { i = fmt_str(s,"from network"); len += i; if (s) s += i; } + else if (uid == auto_uids) + { i = fmt_str(s,"for bounce"); len += i; if (s) s += i; } + else + { + i = fmt_str(s,"by uid "); len += i; if (s) s += i; + i = fmt_ulong(s,uid); len += i; if (s) s += i; + } + i = fmt_str(s,"); "); len += i; if (s) s += i; + i = date822fmt(s,&dt); len += i; if (s) s += i; + return len; +} + +void received_setup() +{ + receivedlen = receivedfmt((char *) 0); + received = alloc(receivedlen + 1); + if (!received) die(123); + receivedfmt(received); +} + +unsigned int pidfmt(s,seq) +char *s; +unsigned long seq; +{ + unsigned int i; + unsigned int len; + + len = 0; + i = fmt_str(s,"pid/"); len += i; if (s) s += i; + i = fmt_ulong(s,mypid); len += i; if (s) s += i; + i = fmt_str(s,"."); len += i; if (s) s += i; + i = fmt_ulong(s,starttime); len += i; if (s) s += i; + i = fmt_str(s,"."); len += i; if (s) s += i; + i = fmt_ulong(s,seq); len += i; if (s) s += i; + ++len; if (s) *s++ = 0; + + return len; +} + +char *fnnum(dirslash,flagsplit) +char *dirslash; +int flagsplit; +{ + char *s; + + s = alloc(fmtqfn((char *) 0,dirslash,messnum,flagsplit)); + if (!s) die(123); + fmtqfn(s,dirslash,messnum,flagsplit); + return s; +} + +void pidopen() +{ + unsigned int len; + unsigned long seq; + + seq = 1; + len = pidfmt((char *) 0,seq); + pidfn = alloc(len); + if (!pidfn) die(123); + + for (seq = 1;seq < 10;++seq) + { + if (pidfmt((char *) 0,seq) > len) die(101); /* paranoia */ + pidfmt(pidfn,seq); + messfd = open_excl(pidfn); + if (messfd != -1) return; + } + + die(103); +} + +char tmp[FMT_ULONG]; + +void main() +{ + unsigned int len; + char ch; + + sig_blocknone(); + umask(033); + if (chdir(auto_qmail) == -1) die(102); + if (chdir("queue") == -1) die(102); + + mypid = getpid(); + uid = getuid(); + starttime = now(); + datetime_tai(&dt,starttime); + + received_setup(); + + sig_pipeignore(); + sig_miscignore(); + sig_alarmcatch(sigalrm); + sig_bugcatch(sigbug); + + alarm(DEATH); + + pidopen(); + if (fstat(messfd,&pidst) == -1) die(104); + + messnum = pidst.st_ino; + messfn = fnnum("mess/",1); + todofn = fnnum("todo/",0); + intdfn = fnnum("intd/",0); + + if (link(pidfn,messfn) == -1) die(105); + if (unlink(pidfn) == -1) die(105); + flagmademess = 1; + + substdio_fdbuf(&ssout,write,messfd,outbuf,sizeof(outbuf)); + substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf)); + + if (substdio_bput(&ssout,received,receivedlen) == -1) die_write(); + + switch(substdio_copy(&ssout,&ssin)) + { + case -2: die_read(); + case -3: die_write(); + } + + if (substdio_flush(&ssout) == -1) die_write(); + if (fsync(messfd) == -1) die_write(); + + intdfd = open_excl(intdfn); + if (intdfd == -1) die(108); + flagmadeintd = 1; + + substdio_fdbuf(&ssout,write,intdfd,outbuf,sizeof(outbuf)); + substdio_fdbuf(&ssin,read,1,inbuf,sizeof(inbuf)); + + if (substdio_bput(&ssout,"u",1) == -1) die_write(); + if (substdio_bput(&ssout,tmp,fmt_ulong(tmp,uid)) == -1) die_write(); + if (substdio_bput(&ssout,"",1) == -1) die_write(); + + if (substdio_bput(&ssout,"p",1) == -1) die_write(); + if (substdio_bput(&ssout,tmp,fmt_ulong(tmp,mypid)) == -1) die_write(); + if (substdio_bput(&ssout,"",1) == -1) die_write(); + + if (substdio_get(&ssin,&ch,1) < 1) die_read(); + if (ch != 'F') die(112); + if (substdio_bput(&ssout,&ch,1) == -1) die_write(); + for (len = 0;len < ADDR;++len) + { + if (substdio_get(&ssin,&ch,1) < 1) die_read(); + if (substdio_put(&ssout,&ch,1) == -1) die_write(); + if (!ch) break; + } + if (len >= ADDR) die(115); + + if (substdio_bput(&ssout,QUEUE_EXTRA,QUEUE_EXTRALEN) == -1) die_write(); + + for (;;) + { + if (substdio_get(&ssin,&ch,1) < 1) die_read(); + if (!ch) break; + if (ch != 'T') die(112); + if (substdio_bput(&ssout,&ch,1) == -1) die_write(); + for (len = 0;len < ADDR;++len) + { + if (substdio_get(&ssin,&ch,1) < 1) die_read(); + if (substdio_bput(&ssout,&ch,1) == -1) die_write(); + if (!ch) break; + } + if (len >= ADDR) die(115); + } + + if (substdio_flush(&ssout) == -1) die_write(); + if (fsync(intdfd) == -1) die_write(); + + if (link(intdfn,todofn) == -1) die(106); + + triggerpull(); + die(0); +} diff --git a/qmail-remote.8 b/qmail-remote.8 @@ -0,0 +1,206 @@ +.TH qmail-remote 8 +.SH NAME +qmail-remote \- send mail via SMTP +.SH SYNOPSIS +.B qmail-remote +.I host +.I sender +.I recip +[ +.I recip ... +] +.SH DESCRIPTION +.B qmail-remote +reads a mail message from its input +and sends the message +to one or more recipients +at a remote host. + +The remote host is +.BR qmail-remote 's +first argument, +.IR host . +.B qmail-remote +sends the message to +.IR host , +or to a mail exchanger for +.I host +listed in the Domain Name System, +via the Simple Mail Transfer Protocol (SMTP). +.I host +can be either a fully-qualified domain name: + +.EX + silverton.berkeley.edu +.EE + +or an IP address enclosed in brackets: + +.EX + [128.32.183.163] +.EE + +The envelope recipient addresses are listed as +.I recip +arguments to +.BR qmail-remote . +The envelope sender address is listed as +.I sender\fP. + +Note that +.B qmail-remote +does not take options +and does not follow the +.B getopt +standard. +.SH TRANSPARENCY +End-of-file in SMTP is encoded as dot CR LF. +A dot at the beginning of a line is encoded as dot dot. +It is impossible in SMTP to send a message that does not end with a newline. +.B qmail-remote +converts the UNIX newline convention into the SMTP newline convention +by inserting CR before each LF. + +It is a violation of the SMTP protocol +to send a message that contains long lines or non-ASCII characters. +However, +.B qmail-remote +will happily send such messages. +It is the user's responsibility to avoid generating illegal messages. +.SH "RESULTS" +.B qmail-remote +prints some number of +.I recipient reports\fP, +followed by a +.I message report\fR. +Each report is terminated by a 0 byte. +Each report begins with a single letter: +.TP 5 +r +Recipient report: acceptance. +.TP 5 +h +Recipient report: permanent rejection. +.TP 5 +s +Recipient report: temporary rejection. +.TP 5 +K +Message report: success. +.I host +has taken responsibility for delivering the message to each +acceptable recipient. +.TP 5 +Z +Message report: temporary failure. +.TP 5 +D +Message report: permanent failure. +.PP +After this letter comes a human-readable description of +what happened. + +The recipient reports will always be printed in the same order as +.BR qmail-remote 's +.I recip +arguments. +Note that in failure cases there may be fewer +recipient reports +than +.I recip +arguments. + +.B qmail-remote +always exits zero. +.SH "CONTROL FILES" +.TP 5 +.I helohost +Current host name, +for use solely in saying hello to the remote SMTP server. +Default: +.IR me , +if that is supplied; +otherwise +.B qmail-remote +refuses to run. +.TP 5 +.I smtproutes +Artificial SMTP routes. +Each route has the form +.IR domain\fB:\fIrelay , +without any extra spaces. +If +.I domain +matches +.IR host , +.B qmail-remote +will connect to +.IR relay , +as if +.I host +had +.I relay +as its only MX. +(It will also avoid doing any CNAME lookups on +.I sender +and +.IR recip .) +.I host +may include a colon and a port number to use instead of the +normal SMTP port, 25: + +.EX + inside.af.mil:firewall.af.mil:26 +.EE + +.I relay +may be empty; +this tells +.B qmail-remote +to look up MX records as usual. +.I smtproutes +may include wildcards: + +.EX + .af.mil: + :heaven.af.mil +.EE + +Here +any address ending with +.B .af.mil +(but not +.B af.mil +itself) +is routed by its MX records; +any other address is artificially routed to +.BR heaven.af.mil . + +The +.B qmail +system does not protect you if you create an artificial +mail loop between machines. +However, +you are always safe using +.I smtproutes +if you do not accept mail from the network. +.TP 5 +.I timeoutconnect +Number of seconds +.B qmail-remote +will wait for the remote SMTP server to accept a connection. +Default: 60. +The kernel normally imposes a 75-second upper limit. +.TP 5 +.I timeoutremote +Number of seconds +.B qmail-remote +will wait for each response from the remote SMTP server. +Default: 1200. +.SH "SEE ALSO" +addresses(5), +envelopes(5), +qmail-control(5), +qmail-send(8), +qmail-smtpd(8), +qmail-tcpto(8) diff --git a/qmail-remote.c b/qmail-remote.c @@ -0,0 +1,480 @@ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include "sig.h" +#include "getln.h" +#include "stralloc.h" +#include "substdio.h" +#include "subfd.h" +#include "scan.h" +#include "case.h" +#include "error.h" +#include "auto_qmail.h" +#include "control.h" +#include "dns.h" +#include "alloc.h" +#include "quote.h" +#include "ip.h" +#include "ipalloc.h" +#include "ipme.h" +#include "gen_alloc.h" +#include "gen_allocdefs.h" +#include "str.h" +#include "now.h" +#include "exit.h" +#include "constmap.h" +#include "tcpto.h" +#include "timeoutconn.h" +#include "timeoutread.h" +#include "timeoutwrite.h" + +#define HUGESMTPTEXT 5000 + +#define PORT_SMTP 25 /* silly rabbit, /etc/services is for users */ +unsigned long port = PORT_SMTP; + +GEN_ALLOC_typedef(saa,stralloc,sa,len,a) +GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus) +static stralloc sauninit = {0}; + +stralloc helohost = {0}; +stralloc routes = {0}; +struct constmap maproutes; +stralloc host = {0}; +stralloc sender = {0}; + +saa reciplist = {0}; + +struct ip_address partner; + +void out(s) char *s; { if (substdio_puts(subfdout,s) == -1) _exit(0); } +void zero() { if (substdio_put(subfdout,"\0",1) == -1) _exit(0); } +void zerodie() { zero(); substdio_flush(subfdout); _exit(0); } + +void outsafe(sa) stralloc *sa; { int i; char ch; +for (i = 0;i < sa->len;++i) { +ch = sa->s[i]; if (ch < 33) ch = '?'; if (ch > 126) ch = '?'; +if (substdio_put(subfdout,&ch,1) == -1) _exit(0); } } + +void temp_nomem() { out("ZOut of memory. (#4.3.0)\n"); zerodie(); } +void temp_oserr() { out("Z\ +System resources temporarily unavailable. (#4.3.0)\n"); zerodie(); } +void temp_noconn() { out("Z\ +Sorry, I wasn't able to establish an SMTP connection. (#4.4.1)\n"); zerodie(); } +void temp_read() { out("ZUnable to read message. (#4.3.0)\n"); zerodie(); } +void temp_dnscanon() { out("Z\ +CNAME lookup failed temporarily. (#4.4.3)\n"); zerodie(); } +void temp_dns() { out("Z\ +Sorry, I couldn't find any host by that name. (#4.1.2)\n"); zerodie(); } +void temp_chdir() { out("Z\ +Unable to switch to home directory. (#4.3.0)\n"); zerodie(); } +void temp_control() { out("Z\ +Unable to read control files. (#4.3.0)\n"); zerodie(); } +void perm_partialline() { out("D\ +SMTP cannot transfer messages with partial final lines. (#5.6.2)\n"); zerodie(); } +void perm_usage() { out("D\ +I (qmail-remote) was invoked improperly. (#5.3.5)\n"); zerodie(); } +void perm_dns() { out("D\ +Sorry, I couldn't find any host named "); +outsafe(&host); +out(". (#5.1.2)\n"); zerodie(); } +void perm_nomx() { out("D\ +Sorry, I couldn't find a mail exchanger or IP address. (#5.4.4)\n"); +zerodie(); } +void perm_ambigmx() { out("D\ +Sorry. Although I'm listed as a best-preference MX or A for that host,\n\ +it isn't in my control/locals file, so I don't treat it as local. (#5.4.6)\n"); +zerodie(); } + +int timeout = 1200; +int timeoutconnect = 60; + +void getcontrols() +{ + int r; + if (control_init() == -1) + { if (errno == error_nomem) temp_nomem(); temp_control(); } + + if (control_readint(&timeout,"control/timeoutremote") == -1) + { if (errno == error_nomem) temp_nomem(); temp_control(); } + if (control_readint(&timeoutconnect,"control/timeoutconnect") == -1) + { if (errno == error_nomem) temp_nomem(); temp_control(); } + + r = control_rldef(&helohost,"control/helohost",1,(char *) 0); + if (r == -1) if (errno == error_nomem) temp_nomem(); + if (r != 1) temp_control(); + + switch(control_readfile(&routes,"control/smtproutes",0)) + { + case -1: + if (errno == error_nomem) temp_nomem(); temp_control(); + case 0: + if (!constmap_init(&maproutes,"",0,1)) temp_nomem(); break; + case 1: + if (!constmap_init(&maproutes,routes.s,routes.len,1)) temp_nomem(); break; + } +} + +char smtptobuf[1024]; +char smtpfrombuf[128]; +stralloc smtpline = {0}; +stralloc smtptext = {0}; + +void outsmtptext() +{ + int i; + if (smtptext.s) if (smtptext.len) if (smtptext.len < HUGESMTPTEXT) + { + if (substdio_puts(subfdout,"Remote host said: ") == -1) _exit(0); + for (i = 0;i < smtptext.len;++i) + if (!smtptext.s[i]) smtptext.s[i] = '?'; + if (substdio_put(subfdout,smtptext.s,smtptext.len) == -1) _exit(0); + smtptext.len = 0; + } +} + +unsigned long smtpcode(ss) +substdio *ss; +{ + int match; + unsigned long code; + + if (!stralloc_copys(&smtptext,"")) return 421; + do + { + if (getln(ss,&smtpline,&match,'\n') != 0) return 421; + if (!match) return 421; + if ((smtpline.len >= 2) && (smtpline.s[smtpline.len - 2] == '\r')) + { + smtpline.s[smtpline.len - 2] = '\n'; + --smtpline.len; + } + if (!stralloc_cat(&smtptext,&smtpline)) return 421; + if (scan_nbblong(smtpline.s,smtpline.len,10,0,&code) != 3) return 421; + if (smtpline.len == 3) return code; + } + while (smtpline.s[3] == '-'); + + return code; +} + +void outhost() +{ + char x[IPFMT]; + + x[ip_fmt(x,&partner)] = 0; + out(x); +} + +void writeerr() +{ + out("ZConnected to "); outhost(); + out(" but communications failed. (#4.4.2)\n"); + zerodie(); +} + +void quit(ssto,ssfrom) +substdio *ssto; +substdio *ssfrom; +{ + outsmtptext(); + if (substdio_putsflush(ssto,"QUIT\r\n") != -1) + smtpcode(ssfrom); /* protocol design stupidity */ + zerodie(); +} + +stralloc dataline = {0}; + +void blast(ssto,ssfrom) +substdio *ssto; +substdio *ssfrom; +{ + int match; + + for (;;) + { + if (getln(ssfrom,&dataline,&match,'\n') != 0) temp_read(); + if (!match && !dataline.len) break; + if (!match) perm_partialline(); + --dataline.len; + if (dataline.len && (dataline.s[0] == '.')) + if (substdio_put(ssto,".",1) == -1) writeerr(); + if (substdio_put(ssto,dataline.s,dataline.len) == -1) writeerr(); + if (substdio_put(ssto,"\r\n",2) == -1) writeerr(); + } + if (substdio_put(ssto,".\r\n",3) == -1) writeerr(); + if (substdio_flush(ssto) == -1) writeerr(); +} + +stralloc recip = {0}; + +void smtp(fd) +int fd; +{ + substdio ssto; + substdio ssfrom; + unsigned long code; + int flaganyrecipok; + int i; + + substdio_fdbuf(&ssto,timeoutwrite,TIMEOUTWRITE(timeout,fd),smtptobuf,sizeof(smtptobuf)); + substdio_fdbuf(&ssfrom,timeoutread,TIMEOUTREAD(timeout,fd),smtpfrombuf,sizeof(smtpfrombuf)); + + if (smtpcode(&ssfrom) != 220) + { + out("ZConnected to "); outhost(); out(" but greeting failed.\n"); + quit(&ssto,&ssfrom); + } + + if (substdio_puts(&ssto,"HELO ") == -1) writeerr(); + if (substdio_put(&ssto,helohost.s,helohost.len) == -1) writeerr(); + if (substdio_puts(&ssto,"\r\n") == -1) writeerr(); + if (substdio_flush(&ssto) == -1) writeerr(); + + if (smtpcode(&ssfrom) != 250) + { + out("ZConnected to "); outhost(); out(" but my name was rejected.\n"); + quit(&ssto,&ssfrom); + } + + if (substdio_puts(&ssto,"MAIL FROM:<") == -1) writeerr(); + if (substdio_put(&ssto,sender.s,sender.len) == -1) writeerr(); + if (substdio_puts(&ssto,">\r\n") == -1) writeerr(); + if (substdio_flush(&ssto) == -1) writeerr(); + + code = smtpcode(&ssfrom); + if (code >= 500) + { + out("DConnected to "); outhost(); out(" but sender was rejected.\n"); + quit(&ssto,&ssfrom); + } + if (code >= 400) + { + out("ZConnected to "); outhost(); out(" but sender was rejected.\n"); + quit(&ssto,&ssfrom); + } + + flaganyrecipok = 0; + for (i = 0;i < reciplist.len;++i) + { + if (substdio_puts(&ssto,"RCPT TO:<") == -1) writeerr(); + if (substdio_put(&ssto,reciplist.sa[i].s,reciplist.sa[i].len) == -1) writeerr(); + if (substdio_puts(&ssto,">\r\n") == -1) writeerr(); + if (substdio_flush(&ssto) == -1) writeerr(); + + code = smtpcode(&ssfrom); + if (code == 421) + { + out("ZConnected to "); outhost(); out(" but connection died.\n"); + quit(&ssto,&ssfrom); + } + if (code >= 500) + { + out("h"); outhost(); out(" does not like recipient.\n"); + outsmtptext(); zero(); + } + else if (code >= 400) + { + out("s"); outhost(); out(" does not like recipient.\n"); + outsmtptext(); zero(); + } + else + { + out("r"); zero(); + flaganyrecipok = 1; + } + } + + if (!flaganyrecipok) + { + out("DGiving up.\n"); + quit(&ssto,&ssfrom); + } + + if (substdio_putsflush(&ssto,"DATA\r\n") == -1) writeerr(); + + code = smtpcode(&ssfrom); + if (code == 421) + { + out("ZConnected to "); outhost(); out(" but connection died.\n"); + quit(&ssto,&ssfrom); + } + if (code >= 500) + { + out("D"); outhost(); out(" failed on DATA command.\n"); + quit(&ssto,&ssfrom); + } + if (code >= 400) + { + out("Z"); outhost(); out(" failed on DATA command.\n"); + quit(&ssto,&ssfrom); + } + + blast(&ssto,subfdin); + + code = smtpcode(&ssfrom); + if (code == 421) + { + out("ZConnected to "); outhost(); out(" but connection died. Possible duplicate!\n"); + quit(&ssto,&ssfrom); + } + if (code >= 500) + { + out("D"); outhost(); out(" failed after I sent the message.\n"); + quit(&ssto,&ssfrom); + } + if (code >= 400) + { + out("Z"); outhost(); out(" failed after I sent the message.\n"); + quit(&ssto,&ssfrom); + } + + out("K"); outhost(); out(" accepted message.\n"); + quit(&ssto,&ssfrom); +} + +stralloc canonhost = {0}; +stralloc canonbox = {0}; + +void addrmangle(saout,s,flagalias,flagcname) +stralloc *saout; /* host has to be canonical, box has to be quoted */ +char *s; +int *flagalias; +int flagcname; +{ + int j; + + *flagalias = flagcname; + + j = str_rchr(s,'@'); + if (!s[j]) + { + if (!stralloc_copys(saout,s)) temp_nomem(); + return; + } + if (!stralloc_copys(&canonbox,s)) temp_nomem(); + canonbox.len = j; + if (!quote(saout,&canonbox)) temp_nomem(); + if (!stralloc_cats(saout,"@")) temp_nomem(); + + if (!stralloc_copys(&canonhost,s + j + 1)) temp_nomem(); + if (flagcname) + switch(dns_cname(&canonhost)) + { + case 0: *flagalias = 0; break; + case DNS_MEM: temp_nomem(); + case DNS_SOFT: temp_dnscanon(); + case DNS_HARD: ; /* alias loop, not our problem */ + } + + if (!stralloc_cat(saout,&canonhost)) temp_nomem(); +} + +void main(argc,argv) +int argc; +char **argv; +{ + static ipalloc ip = {0}; + int i; + unsigned long random; + char **recips; + unsigned long prefme; + int flagallaliases; + int flagalias; + char *relayhost; + + sig_pipeignore(); + if (argc < 4) perm_usage(); + if (chdir(auto_qmail) == -1) temp_chdir(); + getcontrols(); + + + if (!stralloc_copys(&host,argv[1])) temp_nomem(); + + relayhost = 0; + for (i = 0;i <= host.len;++i) + if ((i == 0) || (i == host.len) || (host.s[i] == '.')) + if (relayhost = constmap(&maproutes,host.s + i,host.len - i)) + break; + if (relayhost && !*relayhost) relayhost = 0; + + if (relayhost) + { + i = str_chr(relayhost,':'); + if (relayhost[i]) + { + scan_ulong(relayhost + i + 1,&port); + relayhost[i] = 0; + } + if (!stralloc_copys(&host,relayhost)) temp_nomem(); + } + + + addrmangle(&sender,argv[2],&flagalias,!relayhost); + + if (!saa_readyplus(&reciplist,0)) temp_nomem(); + if (ipme_init() != 1) temp_oserr(); + + flagallaliases = 1; + recips = argv + 3; + while (*recips) + { + if (!saa_readyplus(&reciplist,1)) temp_nomem(); + reciplist.sa[reciplist.len] = sauninit; + addrmangle(reciplist.sa + reciplist.len,*recips,&flagalias,!relayhost); + if (!flagalias) flagallaliases = 0; + ++reciplist.len; + ++recips; + } + + + random = now() + (getpid() << 16); + switch (relayhost ? dns_ip(&ip,&host) : dns_mxip(&ip,&host,random)) + { + case DNS_MEM: temp_nomem(); + case DNS_SOFT: temp_dns(); + case DNS_HARD: perm_dns(); + case 1: + if (ip.len <= 0) temp_dns(); + } + + if (ip.len <= 0) perm_nomx(); + + prefme = 100000; + for (i = 0;i < ip.len;++i) + if (ipme_is(&ip.ix[i].ip)) + if (ip.ix[i].pref < prefme) + prefme = ip.ix[i].pref; + + if (relayhost) prefme = 300000; + if (flagallaliases) prefme = 500000; + + for (i = 0;i < ip.len;++i) + if (ip.ix[i].pref < prefme) + break; + + if (i >= ip.len) + perm_ambigmx(); + + for (i = 0;i < ip.len;++i) if (ip.ix[i].pref < prefme) + { + int s; + + if (tcpto(&ip.ix[i].ip)) continue; + + s = socket(AF_INET,SOCK_STREAM,0); + if (s == -1) temp_oserr(); + + if (timeoutconn(s,&ip.ix[i].ip,(unsigned int) port,timeoutconnect) == 0) + { + tcpto_err(&ip.ix[i].ip,0); + partner = ip.ix[i].ip; + smtp(s); /* does not return */ + } + tcpto_err(&ip.ix[i].ip,errno == error_timeout); + close(s); + } + + temp_noconn(); +} diff --git a/qmail-rspawn.8 b/qmail-rspawn.8 @@ -0,0 +1,21 @@ +.TH qmail-rspawn 8 +.SH NAME +qmail-rspawn \- schedule remote deliveries +.SH SYNOPSIS +.B qmail-rspawn +.SH DESCRIPTION +.B qmail-rspawn +reads a series of remote delivery commands from descriptor 0, +invokes +.B qmail-remote +to perform the deliveries, +and prints the results to descriptor 1. + +.B qmail-rspawn +invokes +.B qmail-remote +asynchronously, +so the results may not be in the same order as the commands. +.SH "SEE ALSO" +qmail-send(8), +qmail-remote(8) diff --git a/qmail-rspawn.c b/qmail-rspawn.c @@ -0,0 +1,103 @@ +#include "fd.h" +#include "wait.h" +#include "substdio.h" +#include "exit.h" +#include "fork.h" +#include "error.h" +#include "tcpto.h" + +void initialize(argc,argv) +int argc; +char **argv; +{ + tcpto_clean(); +} + +int truncreport = 0; + +void report(ss,wstat,s,len) +substdio *ss; +int wstat; +char *s; +int len; +{ + int j; + int k; + int result; + int orr; + + if (wait_crashed(wstat)) + { substdio_puts(ss,"Zqmail-remote crashed.\n"); return; } + switch(wait_exitcode(wstat)) + { + case 0: break; + case 111: substdio_puts(ss,"ZUnable to run qmail-remote.\n"); return; + default: substdio_puts(ss,"DUnable to run qmail-remote.\n"); return; + } + if (!len) + { substdio_puts(ss,"Zqmail-remote produced no output.\n"); return; } + + result = -1; + j = 0; + for (k = 0;k < len;++k) + if (!s[k]) + { + if (s[j] == 'K') { result = 1; break; } + if (s[j] == 'Z') { result = 0; break; } + if (s[j] == 'D') break; + j = k + 1; + } + + orr = result; + switch(s[0]) + { + case 's': orr = 0; break; + case 'h': orr = -1; + } + + switch(orr) + { + case 1: substdio_put(ss,"K",1); break; + case 0: substdio_put(ss,"Z",1); break; + case -1: substdio_put(ss,"D",1); break; + } + + for (k = 1;k < len;) + if (!s[k++]) + { + substdio_puts(ss,s + 1); + if (result <= orr) + if (k < len) + switch(s[k]) + { + case 'Z': case 'D': case 'K': + substdio_puts(ss,s + k + 1); + } + break; + } +} + +int spawn(fdmess,fdout,s,r,at) +int fdmess; int fdout; +char *s; char *r; int at; +{ + int f; + char *(args[5]); + + args[0] = "qmail-remote"; + args[1] = r + at + 1; + args[2] = s; + args[3] = r; + args[4] = 0; + + if (!(f = vfork())) + { + if (fd_move(0,fdmess) == -1) _exit(111); + if (fd_move(1,fdout) == -1) _exit(111); + if (fd_copy(2,1) == -1) _exit(111); + execvp(*args,args); + if (error_temp(errno)) _exit(111); + _exit(100); + } + return f; +} diff --git a/qmail-send.9 b/qmail-send.9 @@ -0,0 +1,264 @@ +.TH qmail-send 8 +.SH NAME +qmail-send \- deliver mail messages from the queue +.SH SYNOPSIS +.B qmail-send +.SH DESCRIPTION +.B qmail-send +handles messages placed into the outgoing queue by +.BR qmail-queue . +It uses +.B qmail-lspawn +to deliver messages to local recipients and +.B qmail-rspawn +to deliver messages to remote recipients. +If a message is temporarily undeliverable to one or more addresses, +.B qmail-send +leaves it in the queue and tries the addresses again later. + +.B qmail-send +prints a readable record of its activities to descriptor 0. +It writes commands to +.BR qmail-lspawn , +.BR qmail-rspawn , +and +.B qmail-clean +on descriptors 1, 3, and 5, +and reads responses from descriptors 2, 4, and 6. +.B qmail-send +is responsible for avoiding deadlock. + +If +.B qmail-send +receives a TERM signal, +it will exit cleanly, after waiting +(possibly more than a minute) +for current delivery attempts to finish. + +If +.B qmail-send +receives an ALRM signal, +it will reschedule every message in the queue for immediate delivery. +.SH "CONTROL FILES" +.B WARNING: +.B qmail-send +reads its control files only when it starts. +If you change the control files, +you must stop and restart +.BR qmail-send . +Exception: +If +.B qmail-send +receives a HUP signal, +it will reread +.I locals +and +.IR virtualdomains . +.TP 5 +.I bouncefrom +Bounce username. +Default: +.BR MAILER-DAEMON . +.TP 5 +.I bouncehost +Bounce host. +Default: +.IR me , +if that is supplied; +otherwise the literal name +.BR bouncehost , +which is probably not what you want. +If a message is permanently undeliverable, +.B qmail-send +sends a +.B single-bounce +notice back to the message's envelope sender. +The notice is +.B From: \fIbouncefrom\fB@\fIbouncehost\fR, +although its envelope sender is empty. +.TP 5 +.I concurrencylocal +Maximum number of simultaneous local delivery attempts. +Default: 10. +If 0, local deliveries will be put on hold. +.I concurrencylocal +is limited at compile time to +SPAWN. +.TP 5 +.I concurrencyremote +Maximum number of simultaneous remote delivery attempts. +Default: 20. +If 0, remote deliveries will be put on hold. +.I concurrencyremote +is limited at compile time to +SPAWN. +.TP 5 +.I doublebouncehost +Double-bounce host. +Default: +.IR me , +if that is supplied; +otherwise the literal name +.BR doublebouncehost , +which is probably not what you want. +.TP 5 +.I doublebounceto +User to receive double-bounces. +Default: +.BR postmaster . +If a single-bounce notice is permanently undeliverable, +.B qmail-send +sends a +.B double-bounce +notice to +.IR doublebounceto\fB@\fIdoublebouncehost . +(If that bounces, +.B qmail-send +gives up.) +.TP 5 +.I envnoathost +Presumed domain name for addresses without @ signs. +Default: +.IR me , +if that is supplied; +otherwise the literal name +.BR envnoathost , +which is probably not what you want. +If +.B qmail-send +sees an envelope recipient address without an @ sign, +it appends +.B @\fIenvnoathost\fR. +.TP 5 +.I locals +List of domain names that the current host +receives mail for, +one per line. +Default: +.IR me , +if that is supplied; +otherwise +.B qmail-send +refuses to run. +An address +.I user@domain +is considered local if +.I domain +is listed in +.IR locals . +.TP 5 +.I percenthack +List of domain names where the percent hack is applied. +If +.I domain +is listed in +.IR percenthack , +any address of the form +.I user%fqdn@domain +is rewritten as +.IR user@fqdn . +.I user +may contain %, +so the percent hack may be applied repeatedly. +.B qmail-send +handles +.I percenthack +before +.IR recipientmap . +.TP 5 +.I queuelifetime +Number of seconds +a message can stay in the queue. +Default: 604800 (one week). +After this time expires, +.B qmail-send +will try the message once more, +but it will treat any temporary delivery failures as +permanent failures. +.TP 5 +.I recipientmap +List of redirections, one per line. +Each redirection has the form +.IR recipient\fB:\fIrewritten , +without any extra spaces. +When +.B qmail-send +sees the address +.IR recipient , +it replaces it with +.IR rewritten . +Both +.I recipient +and +.I rewritten +must include domain names. +.B qmail-send +handles +.I recipientmap +before +.IR locals . +.TP 5 +.I virtualdomains +List of virtual domains, one per line. +Each virtual domain has the form +.IR domain\fB:\fIprepend , +without any extra spaces. +When +.B qmail-send +sees a recipient address at +.IR domain , +say +.IR user\fB@\fIdomain , +it converts it to +.I prepend\fB-\fIuser\fB@\fIdomain +and treats it as local. +For example, if + +.EX + nowhere.mil:joeBREAKfoo +.EE + +is in +.IR virtualdomains , +and a message arrives for +.BR info@nowhere.mil , +.B qmail-send +will rewrite the recipient address as +.B joeBREAKfoo-info@nowhere.mil +and deliver the message locally. +.I virtualdomains +may contain wildcards: + +.EX + .fax:uucpBREAKfax + :aliasBREAKcatchall + .nowhere.mil:joeBREAKfoo-host +.EE + +.I virtualdomains +may also contain exceptions: +an empty +.I prepend +means that +.I domain +is not a virtual domain. + +.B qmail-send +handles +.I virtualdomains +after +.IR locals : +if a domain is listed in +.IR locals , +.I virtualdomains +does not apply. +.SH "SEE ALSO" +nice(1), +addresses(5), +envelopes(5), +qmail-control(5), +qmail-log(5), +qmail-queue(8), +qmail-clean(8), +qmail-lspawn(8), +qmail-rspawn(8) diff --git a/qmail-send.c b/qmail-send.c @@ -0,0 +1,1652 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include "readwrite.h" +#include "sig.h" +#include "direntry.h" +#include "control.h" +#include "select.h" +#include "open.h" +#include "seek.h" +#include "exit.h" +#include "lock.h" +#include "ndelay.h" +#include "now.h" +#include "getln.h" +#include "substdio.h" +#include "alloc.h" +#include "error.h" +#include "stralloc.h" +#include "str.h" +#include "byte.h" +#include "fmt.h" +#include "scan.h" +#include "case.h" +#include "auto_qmail.h" +#include "trigger.h" +#include "newfield.h" +#include "quote.h" +#include "qmail.h" +#include "qsutil.h" +#include "prioq.h" +#include "constmap.h" +#include "fmtqfn.h" +#include "readsubdir.h" + +/* critical timing feature #1: if not triggered, do not busy-loop */ +/* critical timing feature #2: if triggered, respond within fixed time */ +/* important timing feature: when triggered, respond instantly */ +#define SLEEP_TODO 1500 /* check todo/ every 25 minutes in any case */ +#define SLEEP_FUZZ 1 /* slop a bit on sleeps to avoid zeno effect */ +#define SLEEP_FOREVER 86400 /* absolute maximum time spent in select() */ +#define SLEEP_CLEANUP 76431 /* time between cleanups */ +#define SLEEP_SYSFAIL 123 +#define OSSIFIED 129600 /* 36 hours; _must_ exceed q-q's DEATH (24 hours) */ + +int lifetime = 604800; + +stralloc percenthack = {0}; +struct constmap mappercenthack; +stralloc locals = {0}; +struct constmap maplocals; +stralloc redir = {0}; +struct constmap mapredir; +stralloc vdoms = {0}; +struct constmap mapvdoms; +stralloc envnoathost = {0}; +stralloc bouncefrom = {0}; +stralloc bouncehost = {0}; +stralloc doublebounceto = {0}; +stralloc doublebouncehost = {0}; + +char strnum2[FMT_ULONG]; +char strnum3[FMT_ULONG]; + +#define CHANNELS 2 +char *chanaddr[CHANNELS] = { "local/", "remote/" }; +char *channodelmsg[CHANNELS] = { + "local deliveries will be put on hold\n" +, "remote deliveries will be put on hold\n" +}; +char *tochan[CHANNELS] = { " to local ", " to remote " }; +int chanfdout[CHANNELS] = { 1, 3 }; +int chanfdin[CHANNELS] = { 2, 4 }; +int chanskip[CHANNELS] = { 10, 20 }; + +int flagexitasap = 0; void sigterm() { flagexitasap = 1; } +int flagrunasap = 0; void sigalrm() { flagrunasap = 1; } +int flagreadasap = 0; void sighup() { flagreadasap = 1; } + +void cleandied() { log1("alert: oh no! lost qmail-clean connection! dying...\n"); + flagexitasap = 1; } + +int flagspawnalive[CHANNELS]; +void spawndied(c) int c; { log1("alert: oh no! lost spawn connection! dying...\n"); + flagspawnalive[c] = 0; flagexitasap = 1; } + +#define REPORTMAX 10000 + +datetime_sec recent; + + +/* this file is too long ----------------------------------------- FILENAMES */ + +stralloc fn = {0}; +stralloc fn2 = {0}; +char fnmake_strnum[FMT_ULONG]; + +void fnmake_init() +{ + while (!stralloc_ready(&fn,FMTQFN)) nomem(); + while (!stralloc_ready(&fn2,FMTQFN)) nomem(); +} + +void fnmake_info(id) unsigned long id; { fn.len = fmtqfn(fn.s,"info/",id,1); } +void fnmake_todo(id) unsigned long id; { fn.len = fmtqfn(fn.s,"todo/",id,0); } +void fnmake_mess(id) unsigned long id; { fn.len = fmtqfn(fn.s,"mess/",id,1); } +void fnmake_foop(id) unsigned long id; { fn.len = fmtqfn(fn.s,"foop/",id,0); } +void fnmake_split(id) unsigned long id; { fn.len = fmtqfn(fn.s,"",id,1); } +void fnmake2_bounce(id) unsigned long id; +{ fn2.len = fmtqfn(fn2.s,"bounce/",id,0); } +void fnmake_chanaddr(id,c) unsigned long id; int c; +{ fn.len = fmtqfn(fn.s,chanaddr[c],id,1); } + + +/* this file is too long ----------------------------------------- REWRITING */ + +stralloc rwline = {0}; + +/* 1 if by land, 2 if by sea, 0 if out of memory. not allowed to barf. */ +/* may trash recip. must set up rwline, between a T and a \0. */ +int rewrite(recip) +char *recip; +{ + int i; + int j; + char *x; + static stralloc addr = {0}; + static stralloc domain = {0}; + + if (!stralloc_copys(&rwline,"T")) return 0; + if (!stralloc_copys(&addr,recip)) return 0; + + i = byte_rchr(addr.s,addr.len,'@'); + if (i == addr.len) { + if (!stralloc_cats(&addr,"@")) return 0; + if (!stralloc_cat(&addr,&envnoathost)) return 0; + } + + while (constmap(&mappercenthack,addr.s + i + 1,addr.len - i - 1)) { + j = byte_rchr(addr.s,i,'%'); + if (j == i) break; + addr.len = i; + i = j; + addr.s[i] = '@'; + } + + if (x = constmap(&mapredir,addr.s,addr.len)) + if (x[str_chr(x,'@')]) + if (!stralloc_copys(&addr,x)) return 0; + + i = byte_rchr(addr.s,addr.len,'@'); + if (!stralloc_copyb(&domain,addr.s + i + 1,addr.len - i - 1)) return 0; + addr.len = i; + + if (constmap(&maplocals,domain.s,domain.len)) { + if (!stralloc_cat(&rwline,&addr)) return 0; + if (!stralloc_cats(&rwline,"@")) return 0; + if (!stralloc_cat(&rwline,&domain)) return 0; + if (!stralloc_0(&rwline)) return 0; + return 1; + } + + for (i = 0;i <= domain.len;++i) + if ((i == 0) || (i == domain.len) || (domain.s[i] == '.')) + if (x = constmap(&mapvdoms,domain.s + i,domain.len - i)) { + if (!*x) break; + if (!stralloc_cats(&rwline,x)) return 0; + if (!stralloc_cats(&rwline,"-")) return 0; + if (!stralloc_cat(&rwline,&addr)) return 0; + if (!stralloc_cats(&rwline,"@")) return 0; + if (!stralloc_cat(&rwline,&domain)) return 0; + if (!stralloc_0(&rwline)) return 0; + return 1; + } + + if (!stralloc_cat(&rwline,&addr)) return 0; + if (!stralloc_cats(&rwline,"@")) return 0; + if (!stralloc_cat(&rwline,&domain)) return 0; + if (!stralloc_0(&rwline)) return 0; + return 2; +} + +void senderadd(sa,sender,recip) +stralloc *sa; +char *sender; +char *recip; +{ + int i; + int j; + int k; + + i = str_len(sender); + if (i >= 4) + if (str_equal(sender + i - 4,"-@[]")) + { + j = byte_rchr(sender,i - 4,'@'); + k = str_rchr(recip,'@'); + if (recip[k] && (j + 5 <= i)) + { + /* owner-@host-@[] -> owner-recipbox=reciphost@host */ + while (!stralloc_catb(sa,sender,j)) nomem(); + while (!stralloc_catb(sa,recip,k)) nomem(); + while (!stralloc_cats(sa,"=")) nomem(); + while (!stralloc_cats(sa,recip + k + 1)) nomem(); + while (!stralloc_cats(sa,"@")) nomem(); + while (!stralloc_catb(sa,sender + j + 1,i - 5 - j)) nomem(); + return; + } + } + while (!stralloc_cats(sa,sender)) nomem(); +} + + +/* this file is too long ---------------------------------------------- INFO */ + +int getinfo(sa,dt,id) +stralloc *sa; +datetime_sec *dt; +unsigned long id; +{ + int fdinfo; + struct stat st; + static stralloc line = {0}; + int match; + substdio ss; + char buf[128]; + + fnmake_info(id); + fdinfo = open_read(fn.s); + if (fdinfo == -1) return 0; + if (fstat(fdinfo,&st) == -1) { close(fdinfo); return 0; } + substdio_fdbuf(&ss,read,fdinfo,buf,sizeof(buf)); + if (getln(&ss,&line,&match,'\0') == -1) { close(fdinfo); return 0; } + close(fdinfo); + if (!match) return 0; + if (line.s[0] != 'F') return 0; + + *dt = st.st_mtime; + while (!stralloc_copys(sa,line.s + 1)) nomem(); + while (!stralloc_0(sa)) nomem(); + return 1; +} + + +/* this file is too long ------------------------------------- COMMUNICATION */ + +substdio sstoqc; char sstoqcbuf[1024]; +substdio ssfromqc; char ssfromqcbuf[1024]; +stralloc comm_buf[CHANNELS] = { {0}, {0} }; +int comm_pos[CHANNELS]; + +void comm_init() +{ + int c; + substdio_fdbuf(&sstoqc,write,5,sstoqcbuf,sizeof(sstoqcbuf)); + substdio_fdbuf(&ssfromqc,read,6,ssfromqcbuf,sizeof(ssfromqcbuf)); + for (c = 0;c < CHANNELS;++c) + if (ndelay_on(chanfdout[c]) == -1) + /* this is so stupid: NDELAY semantics should be default on write */ + spawndied(c); /* drastic, but better than risking deadlock */ +} + +int comm_canwrite(c) +int c; +{ + /* XXX: could allow a bigger buffer; say 10 recipients */ + if (comm_buf[c].s && comm_buf[c].len) return 0; + return 1; +} + +void comm_write(c,delnum,id,sender,recip) +int c; +int delnum; +unsigned long id; +char *sender; +char *recip; +{ + char ch; + if (comm_buf[c].s && comm_buf[c].len) return; + while (!stralloc_copys(&comm_buf[c],"")) nomem(); + ch = delnum; + while (!stralloc_append(&comm_buf[c],&ch)) nomem(); + fnmake_split(id); + while (!stralloc_cats(&comm_buf[c],fn.s)) nomem(); + while (!stralloc_0(&comm_buf[c])) nomem(); + senderadd(&comm_buf[c],sender,recip); + while (!stralloc_0(&comm_buf[c])) nomem(); + while (!stralloc_cats(&comm_buf[c],recip)) nomem(); + while (!stralloc_0(&comm_buf[c])) nomem(); + comm_pos[c] = 0; +} + +void comm_selprep(nfds,wfds) +int *nfds; +fd_set *wfds; +{ + int c; + for (c = 0;c < CHANNELS;++c) + if (flagspawnalive[c]) + if (comm_buf[c].s && comm_buf[c].len) + { + FD_SET(chanfdout[c],wfds); + if (*nfds <= chanfdout[c]) + *nfds = chanfdout[c] + 1; + } +} + +void comm_do(wfds) +fd_set *wfds; +{ + int c; + for (c = 0;c < CHANNELS;++c) + if (flagspawnalive[c]) + if (comm_buf[c].s && comm_buf[c].len) + if (FD_ISSET(chanfdout[c],wfds)) + { + int w; + int len; + len = comm_buf[c].len; + w = write(chanfdout[c],comm_buf[c].s + comm_pos[c],len - comm_pos[c]); + if (w <= 0) + { + if ((w == -1) && (errno == error_pipe)) + spawndied(c); + else + continue; /* kernel select() bug; can't avoid busy-looping */ + } + else + { + comm_pos[c] += w; + if (comm_pos[c] == len) + comm_buf[c].len = 0; + } + } +} + + +/* this file is too long ------------------------------------------ CLEANUPS */ + +int flagcleanup; /* if 1, cleanupdir is initialized and ready */ +readsubdir cleanupdir; +datetime_sec cleanuptime; + +void cleanup_init() +{ + flagcleanup = 0; + cleanuptime = now(); +} + +void cleanup_selprep(wakeup) +datetime_sec *wakeup; +{ + if (flagcleanup) *wakeup = 0; + if (*wakeup > cleanuptime) *wakeup = cleanuptime; +} + +void cleanup_do() +{ + char ch; + struct stat st; + unsigned long id; + + if (!flagcleanup) + { + if (recent < cleanuptime) return; + readsubdir_init(&cleanupdir,"mess",pausedir); + flagcleanup = 1; + } + + switch(readsubdir_next(&cleanupdir,&id)) + { + case 1: + break; + case 0: + flagcleanup = 0; + cleanuptime = recent + SLEEP_CLEANUP; + default: + return; + } + + fnmake_mess(id); + if (stat(fn.s,&st) == -1) return; /* probably qmail-queue deleted it */ + if (recent <= st.st_atime + OSSIFIED) return; + + fnmake_info(id); + if (stat(fn.s,&st) == 0) return; + if (errno != error_noent) return; + fnmake_todo(id); + if (stat(fn.s,&st) == 0) return; + if (errno != error_noent) return; + + fnmake_foop(id); + if (substdio_putflush(&sstoqc,fn.s,fn.len) == -1) { cleandied(); return; } + if (substdio_get(&ssfromqc,&ch,1) != 1) { cleandied(); return; } + if (ch != '+') + log3("warning: qmail-clean unable to clean up ",fn.s,"\n"); +} + + +/* this file is too long ----------------------------------- PRIORITY QUEUES */ + +prioq pqdone = {0}; /* -todo +info; HOPEFULLY -local -remote */ +prioq pqchan[CHANNELS] = { {0}, {0} }; +/* pqchan 0: -todo +info +local ?remote */ +/* pqchan 1: -todo +info ?local +remote */ +prioq pqfail = {0}; /* stat() failure; has to be pqadded again */ + +void pqadd(id) +unsigned long id; +{ + struct prioq_elt pe; + struct prioq_elt pechan[CHANNELS]; + int flagchan[CHANNELS]; + struct stat st; + int c; + +#define CHECKSTAT if (errno != error_noent) goto fail; + + fnmake_info(id); + if (stat(fn.s,&st) == -1) + { + CHECKSTAT + return; /* someone yanking our chain */ + } + + fnmake_todo(id); + if (stat(fn.s,&st) != -1) return; /* look, ma, dad crashed writing info! */ + CHECKSTAT + + for (c = 0;c < CHANNELS;++c) + { + fnmake_chanaddr(id,c); + if (stat(fn.s,&st) == -1) { flagchan[c] = 0; CHECKSTAT } + else { flagchan[c] = 1; pechan[c].id = id; pechan[c].dt = st.st_mtime; } + } + + for (c = 0;c < CHANNELS;++c) + if (flagchan[c]) + while (!prioq_insert(&pqchan[c],&pechan[c])) nomem(); + + for (c = 0;c < CHANNELS;++c) if (flagchan[c]) break; + if (c == CHANNELS) + { + pe.id = id; pe.dt = now(); + while (!prioq_insert(&pqdone,&pe)) nomem(); + } + + return; + + fail: + log3("warning: unable to stat ",fn.s,"; will try again later\n"); + pe.id = id; pe.dt = now() + SLEEP_SYSFAIL; + while (!prioq_insert(&pqfail,&pe)) nomem(); +} + +void pqstart() +{ + readsubdir rs; + int x; + unsigned long id; + + readsubdir_init(&rs,"info",pausedir); + + while (x = readsubdir_next(&rs,&id)) + if (x > 0) + pqadd(id); +} + +void pqfinish() +{ + int c; + struct prioq_elt pe; + time_t ut[2]; /* XXX: more portable than utimbuf, but still worrisome */ + + for (c = 0;c < CHANNELS;++c) + while (prioq_min(&pqchan[c],&pe)) + { + prioq_delmin(&pqchan[c]); + fnmake_chanaddr(pe.id,c); + ut[0] = ut[1] = pe.dt; + if (utime(fn.s,ut) == -1) + log3("warning: unable to utime ",fn.s,"; message will be retried too soon\n"); + } +} + +void pqrun() +{ + int c; + int i; + for (c = 0;c < CHANNELS;++c) + if (pqchan[c].p) + if (pqchan[c].len) + for (i = 0;i < pqchan[c].len;++i) + pqchan[c].p[i].dt = recent; +} + + +/* this file is too long ---------------------------------------------- JOBS */ + +struct job + { + int refs; /* if 0, this struct is unused */ + unsigned long id; + int channel; + datetime_sec retry; + stralloc sender; + int numtodo; + int flaghiteof; + int flagdying; + } +; + +int numjobs; +struct job *jo; + +void job_init() +{ + int j; + while (!(jo = (struct job *) alloc(numjobs * sizeof(struct job)))) nomem(); + for (j = 0;j < numjobs;++j) + { + jo[j].refs = 0; + jo[j].sender.s = 0; + } +} + +int job_avail() +{ + int j; + for (j = 0;j < numjobs;++j) if (!jo[j].refs) return 1; + return 0; +} + +int job_open(id,channel) +unsigned long id; +int channel; +{ + int j; + for (j = 0;j < numjobs;++j) if (!jo[j].refs) break; + if (j == numjobs) return -1; + jo[j].refs = 1; + jo[j].id = id; + jo[j].channel = channel; + jo[j].numtodo = 0; + jo[j].flaghiteof = 0; + return j; +} + +void job_close(j) +int j; +{ + struct prioq_elt pe; + struct stat st; + + if (0 < --jo[j].refs) return; + + pe.id = jo[j].id; + pe.dt = jo[j].retry; + if (jo[j].flaghiteof && !jo[j].numtodo) + { + fnmake_chanaddr(jo[j].id,jo[j].channel); + if (unlink(fn.s) == -1) + { + log3("warning: unable to unlink ",fn.s,"; will try again later\n"); + pe.dt = now() + SLEEP_SYSFAIL; + } + else + { + int c; + for (c = 0;c < CHANNELS;++c) if (c != jo[j].channel) + { + fnmake_chanaddr(jo[j].id,c); + if (stat(fn.s,&st) == 0) return; /* more channels going */ + if (errno != error_noent) + { + log3("warning: unable to stat ",fn.s,"\n"); + break; /* this is the only reason for HOPEFULLY */ + } + } + pe.dt = now(); + while (!prioq_insert(&pqdone,&pe)) nomem(); + return; + } + } + + while (!prioq_insert(&pqchan[jo[j].channel],&pe)) nomem(); +} + + +/* this file is too long ------------------------------------------- BOUNCES */ + +char *stripvdomprepend(recip) +char *recip; +{ + int i; + char *domain; + int domainlen; + char *prepend; + + i = str_rchr(recip,'@'); + if (!recip[i]) return recip; + domain = recip + i + 1; + domainlen = str_len(domain); + + for (i = 0;i <= domainlen;++i) + if ((i == 0) || (i == domainlen) || (domain[i] == '.')) + if (prepend = constmap(&mapvdoms,domain + i,domainlen - i)) + { + if (!*prepend) break; + i = str_len(prepend); + if (str_diffn(recip,prepend,i)) break; + if (recip[i] != '-') break; + return recip + i + 1; + } + return recip; +} + +stralloc bouncetext = {0}; + +void addbounce(id,recip,report) +unsigned long id; +char *recip; +char *report; +{ + int fd; + int pos; + int w; + while (!stralloc_copys(&bouncetext,"<")) nomem(); + while (!stralloc_cats(&bouncetext,stripvdomprepend(recip))) nomem(); + for (pos = 0;pos < bouncetext.len;++pos) + if (bouncetext.s[pos] == '\n') + bouncetext.s[pos] = '_'; + while (!stralloc_cats(&bouncetext,">:\n")) nomem(); + while (!stralloc_cats(&bouncetext,report)) nomem(); + if (report[0]) + if (report[str_len(report) - 1] != '\n') + while (!stralloc_cats(&bouncetext,"\n")) nomem(); + for (pos = bouncetext.len - 2;pos > 0;--pos) + if (bouncetext.s[pos] == '\n') + if (bouncetext.s[pos - 1] == '\n') + bouncetext.s[pos] = '/'; + while (!stralloc_cats(&bouncetext,"\n")) nomem(); + fnmake2_bounce(id); + for (;;) + { + fd = open_append(fn2.s); + if (fd != -1) break; + log1("alert: unable to append to bounce message; HELP! sleeping...\n"); + sleep(10); + } + pos = 0; + while (pos < bouncetext.len) + { + w = write(fd,bouncetext.s + pos,bouncetext.len - pos); + if (w <= 0) + { + log1("alert: unable to append to bounce message; HELP! sleeping...\n"); + sleep(10); + } + else + pos += w; + } + close(fd); +} + +int injectbounce(id) +unsigned long id; +{ + struct qmail qqt; + struct stat st; + char *bouncesender; + char *bouncerecip; + int r; + int fd; + substdio ssread; + char buf[128]; + char inbuf[128]; + static stralloc sender = {0}; + static stralloc quoted = {0}; + datetime_sec birth; + unsigned long qp; + + if (!getinfo(&sender,&birth,id)) return 0; /* XXX: print warning */ + + /* owner-@host-@[] -> owner-@host */ + if (sender.len >= 5) + if (str_equal(sender.s + sender.len - 5,"-@[]")) + { + sender.len -= 4; + sender.s[sender.len - 1] = 0; + } + + fnmake2_bounce(id); + fnmake_mess(id); + if (stat(fn2.s,&st) == -1) + { + if (errno == error_noent) + return 1; + log3("warning: unable to stat ",fn2.s,"\n"); + return 0; + } + if (str_equal(sender.s,"#@[]")) + log3("triple bounce: discarding ",fn2.s,"\n"); + else + { + if (qmail_open(&qqt) == -1) + { log1("warning: unable to start qmail-queue, will try later\n"); return 0; } + qp = qmail_qp(&qqt); + + if (*sender.s) { bouncesender = ""; bouncerecip = sender.s; } + else { bouncesender = "#@[]"; bouncerecip = doublebounceto.s; } + + while (!newfield_datemake(now())) nomem(); + qmail_put(&qqt,newfield_date.s,newfield_date.len); + qmail_puts(&qqt,"From: "); + while (!quote(&quoted,&bouncefrom)) nomem(); + qmail_put(&qqt,quoted.s,quoted.len); + qmail_puts(&qqt,"@"); + qmail_put(&qqt,bouncehost.s,bouncehost.len); + qmail_puts(&qqt,"\nTo: "); + while (!quote2(&quoted,bouncerecip)) nomem(); + qmail_put(&qqt,quoted.s,quoted.len); + qmail_puts(&qqt,"\n\ +Subject: failure notice\n\ +\n\ +Hi. This is the qmail-send program at "); + qmail_put(&qqt,bouncehost.s,bouncehost.len); + qmail_puts(&qqt,*sender.s ? ".\n\ +I'm afraid I wasn't able to deliver your message to the following addresses.\n\ +This is a permanent error; I've given up. Sorry it didn't work out.\n\ +\n\ +" : ".\n\ +I tried to deliver a bounce message to this address, but the bounce bounced!\n\ +\n\ +"); + + fd = open_read(fn2.s); + if (fd == -1) + qmail_fail(&qqt); + else + { + substdio_fdbuf(&ssread,read,fd,inbuf,sizeof(inbuf)); + while ((r = substdio_get(&ssread,buf,sizeof(buf))) > 0) + qmail_put(&qqt,buf,r); + close(fd); + if (r == -1) + qmail_fail(&qqt); + } + + qmail_puts(&qqt,*sender.s ? "--- Below this line is a copy of the message.\n\n" : "--- Below this line is the original bounce.\n\n"); + qmail_puts(&qqt,"Return-Path: <"); + while (!quote2(&quoted,sender.s)) nomem(); + qmail_put(&qqt,quoted.s,quoted.len); + qmail_puts(&qqt,">\n"); + + fd = open_read(fn.s); + if (fd == -1) + qmail_fail(&qqt); + else + { + substdio_fdbuf(&ssread,read,fd,inbuf,sizeof(inbuf)); + while ((r = substdio_get(&ssread,buf,sizeof(buf))) > 0) + qmail_put(&qqt,buf,r); + close(fd); + if (r == -1) + qmail_fail(&qqt); + } + + qmail_from(&qqt,bouncesender); + qmail_to(&qqt,bouncerecip); + if (qmail_close(&qqt)) + { log1("warning: trouble injecting bounce message, will try later\n"); return 0; } + + strnum2[fmt_ulong(strnum2,id)] = 0; + log2("bounce msg ",strnum2); + strnum2[fmt_ulong(strnum2,qp)] = 0; + log3(" qp ",strnum2,"\n"); + } + if (unlink(fn2.s) == -1) + { + log3("warning: unable to unlink ",fn2.s,"\n"); + return 0; + } + return 1; +} + + +/* this file is too long ---------------------------------------- DELIVERIES */ + +struct del + { + int used; + int j; + unsigned long delid; + seek_pos mpos; + stralloc recip; + } +; + +unsigned long masterdelid = 1; +unsigned int concurrency[CHANNELS] = { 10, 20 }; +struct del *d[CHANNELS]; +stralloc dline[CHANNELS]; +char delbuf[2048]; + +void del_init() +{ + int c; + int i; + for (c = 0;c < CHANNELS;++c) + { + flagspawnalive[c] = 1; + while (!(d[c] = (struct del *) alloc(concurrency[c] * sizeof(struct del)))) + nomem(); + for (i = 0;i < concurrency[c];++i) + { d[c][i].used = 0; d[c][i].recip.s = 0; } + dline[c].s = 0; + while (!stralloc_copys(&dline[c],"")) nomem(); + } +} + +int del_canexit() +{ + int i; + int c; + for (c = 0;c < CHANNELS;++c) + if (flagspawnalive[c]) /* if dead, nothing we can do about its jobs */ + for (i = 0;i < concurrency[c];++i) + if (d[c][i].used) return 0; + return 1; +} + +static int del_lastsaid = 0; + +void del_saywhynoexit() +{ + int i; + int c; + int n; + n = 0; + for (c = 0;c < CHANNELS;++c) + if (flagspawnalive[c]) + for (i = 0;i < concurrency[c];++i) + if (d[c][i].used) + ++n; + if (!del_lastsaid || (n < del_lastsaid)) + { + strnum2[fmt_ulong(strnum2,(unsigned long) n)] = 0; + log3("number of deliveries left before exiting: ",strnum2,"\n"); + del_lastsaid = n; + } +} + +int del_avail(c) +int c; +{ + int i; + + if (!flagspawnalive[c]) return 0; + if (!comm_canwrite(c)) return 0; + for (i = 0;i < concurrency[c];++i) if (!d[c][i].used) return 1; + return 0; +} + +void del_start(j,mpos,recip) +int j; +seek_pos mpos; +char *recip; +{ + int i; + int c; + + c = jo[j].channel; + if (!flagspawnalive[c]) return; + if (!comm_canwrite(c)) return; + + for (i = 0;i < concurrency[c];++i) if (!d[c][i].used) break; + if (i == concurrency[c]) return; + + if (!stralloc_copys(&d[c][i].recip,recip)) { nomem(); return; } + if (!stralloc_0(&d[c][i].recip)) { nomem(); return; } + d[c][i].j = j; ++jo[j].refs; + d[c][i].delid = masterdelid++; + d[c][i].mpos = mpos; + d[c][i].used = 1; + + comm_write(c,i,jo[j].id,jo[j].sender.s,recip); + + strnum2[fmt_ulong(strnum2,d[c][i].delid)] = 0; + strnum3[fmt_ulong(strnum3,jo[j].id)] = 0; + log2("starting delivery ",strnum2); + log3(": msg ",strnum3,tochan[c]); + logsafe(recip); + log1("\n"); +} + +void markdone(c,id,pos) +int c; +unsigned long id; +seek_pos pos; +{ + struct stat st; + int fd; + fnmake_chanaddr(id,c); + for (;;) + { + fd = open_write(fn.s); + if (fd == -1) break; + if (fstat(fd,&st) == -1) { close(fd); break; } + if (seek_set(fd,pos) == -1) { close(fd); break; } + if (write(fd,"D",1) != 1) { close(fd); break; } + /* further errors -> double delivery without us knowing about it, oh well */ + close(fd); + return; + } + log3("warning: trouble marking ",fn.s,"; message will be delivered twice!\n"); +} + +void del_dochan(c) +int c; +{ + int r; + char ch; + int i; + int delnum; + r = read(chanfdin[c],delbuf,sizeof(delbuf)); + if (r == -1) return; + if (r == 0) { spawndied(c); return; } + for (i = 0;i < r;++i) + { + ch = delbuf[i]; + while (!stralloc_append(&dline[c],&ch)) nomem(); + if (dline[c].len > REPORTMAX) + dline[c].len = REPORTMAX; + /* qmail-lspawn and qmail-rspawn are responsible for keeping it short */ + /* but from a security point of view, we don't trust rspawn */ + if (!ch && (dline[c].len > 1)) + { + delnum = (unsigned int) (unsigned char) dline[c].s[0]; + if ((delnum < 0) || (delnum >= concurrency[c]) || !d[c][delnum].used) + log1("warning: internal error: delivery report out of range\n"); + else + { + strnum3[fmt_ulong(strnum3,d[c][delnum].delid)] = 0; + if (dline[c].s[1] == 'Z') + if (jo[d[c][delnum].j].flagdying) + { + dline[c].s[1] = 'D'; + --dline[c].len; + while (!stralloc_cats(&dline[c],"I'm not going to try again; this message has been in the queue too long.\n")) nomem(); + while (!stralloc_0(&dline[c])) nomem(); + } + switch(dline[c].s[1]) + { + case 'K': + log3("delivery ",strnum3,": success: "); + logsafe(dline[c].s + 2); + log1("\n"); + markdone(c,jo[d[c][delnum].j].id,d[c][delnum].mpos); + --jo[d[c][delnum].j].numtodo; + break; + case 'Z': + log3("delivery ",strnum3,": deferral: "); + logsafe(dline[c].s + 2); + log1("\n"); + break; + case 'D': + log3("delivery ",strnum3,": failure: "); + logsafe(dline[c].s + 2); + log1("\n"); + addbounce(jo[d[c][delnum].j].id,d[c][delnum].recip.s,dline[c].s + 2); + markdone(c,jo[d[c][delnum].j].id,d[c][delnum].mpos); + --jo[d[c][delnum].j].numtodo; + break; + default: + log3("delivery ",strnum3,": report mangled, will defer\n"); + } + job_close(d[c][delnum].j); + d[c][delnum].used = 0; + } + dline[c].len = 0; + } + } +} + +void del_selprep(nfds,rfds) +int *nfds; +fd_set *rfds; +{ + int c; + for (c = 0;c < CHANNELS;++c) + if (flagspawnalive[c]) + { + FD_SET(chanfdin[c],rfds); + if (*nfds <= chanfdin[c]) + *nfds = chanfdin[c] + 1; + } +} + +void del_do(rfds) +fd_set *rfds; +{ + int c; + for (c = 0;c < CHANNELS;++c) + if (flagspawnalive[c]) + if (FD_ISSET(chanfdin[c],rfds)) + del_dochan(c); +} + + +/* this file is too long -------------------------------------------- PASSES */ + +struct + { + unsigned long id; /* if 0, need a new pass */ + int j; /* defined if id; job number */ + int fd; /* defined if id; reading from {local,remote} */ + seek_pos mpos; /* defined if id; mark position */ + substdio ss; + char buf[128]; + } +pass[CHANNELS]; + +void pass_init() +{ + int c; + for (c = 0;c < CHANNELS;++c) pass[c].id = 0; +} + +void pass_selprep(wakeup) +datetime_sec *wakeup; +{ + int c; + struct prioq_elt pe; + if (flagexitasap) return; + for (c = 0;c < CHANNELS;++c) + if (pass[c].id) + if (del_avail(c)) + { *wakeup = 0; return; } + if (job_avail()) + for (c = 0;c < CHANNELS;++c) + if (!pass[c].id) + if (prioq_min(&pqchan[c],&pe)) + if (*wakeup > pe.dt) + *wakeup = pe.dt; + if (prioq_min(&pqfail,&pe)) + if (*wakeup > pe.dt) + *wakeup = pe.dt; + if (prioq_min(&pqdone,&pe)) + if (*wakeup > pe.dt) + *wakeup = pe.dt; +} + +static datetime_sec squareroot(x) /* result^2 <= x < (result + 1)^2 */ +datetime_sec x; /* assuming: >= 0 */ +{ + datetime_sec y; + datetime_sec yy; + datetime_sec y21; + int j; + + y = 0; yy = 0; + for (j = 15;j >= 0;--j) + { + y21 = (y << (j + 1)) + (1 << (j + j)); + if (y21 <= x - yy) { y += (1 << j); yy += y21; } + } + return y; +} + +datetime_sec nextretry(birth,c) +datetime_sec birth; +int c; +{ + int n; + + if (birth > recent) n = 0; + else n = squareroot(recent - birth); /* no need to add fuzz to recent */ + n += chanskip[c]; + return birth + n * n; +} + +void pass_dochan(c) +int c; +{ + datetime_sec birth; + struct prioq_elt pe; + static stralloc line = {0}; + int match; + + if (flagexitasap) return; + + if (!pass[c].id) + { + if (!job_avail()) return; + if (!prioq_min(&pqchan[c],&pe)) return; + if (pe.dt > recent) return; + fnmake_chanaddr(pe.id,c); + + prioq_delmin(&pqchan[c]); + pass[c].mpos = 0; + pass[c].fd = open_read(fn.s); + if (pass[c].fd == -1) goto trouble; + if (!getinfo(&line,&birth,pe.id)) { close(pass[c].fd); goto trouble; } + pass[c].id = pe.id; + substdio_fdbuf(&pass[c].ss,read,pass[c].fd,pass[c].buf,sizeof(pass[c].buf)); + pass[c].j = job_open(pe.id,c); + jo[pass[c].j].retry = nextretry(birth,c); + jo[pass[c].j].flagdying = (recent > birth + lifetime); + while (!stralloc_copy(&jo[pass[c].j].sender,&line)) nomem(); + } + + if (!del_avail(c)) return; + + if (getln(&pass[c].ss,&line,&match,'\0') == -1) + { + fnmake_chanaddr(pass[c].id,c); + log3("warning: trouble reading ",fn.s,"; will try again later\n"); + close(pass[c].fd); + job_close(pass[c].j); + pass[c].id = 0; + return; + } + if (!match) + { + close(pass[c].fd); + jo[pass[c].j].flaghiteof = 1; + job_close(pass[c].j); + pass[c].id = 0; + return; + } + switch(line.s[0]) + { + case 'T': + ++jo[pass[c].j].numtodo; + del_start(pass[c].j,pass[c].mpos,line.s + 1); + break; + case 'D': + break; + default: + fnmake_chanaddr(pass[c].id,c); + log3("warning: unknown record type in ",fn.s,"!\n"); + close(pass[c].fd); + job_close(pass[c].j); + pass[c].id = 0; + return; + } + + pass[c].mpos += line.len; + return; + + trouble: + log3("warning: trouble opening ",fn.s,"; will try again later\n"); + pe.dt = recent + SLEEP_SYSFAIL; + while (!prioq_insert(&pqchan[c],&pe)) nomem(); +} + +void messdone(id) +unsigned long id; +{ + char ch; + int c; + struct prioq_elt pe; + struct stat st; + + for (c = 0;c < CHANNELS;++c) + { + fnmake_chanaddr(id,c); + if (stat(fn.s,&st) == 0) return; /* false alarm; consequence of HOPEFULLY */ + if (errno != error_noent) + { + log3("warning: unable to stat ",fn.s,"; will try again later\n"); + goto fail; + } + } + + fnmake_todo(id); + if (stat(fn.s,&st) == 0) return; + if (errno != error_noent) + { + log3("warning: unable to stat ",fn.s,"; will try again later\n"); + goto fail; + } + + fnmake_info(id); + if (stat(fn.s,&st) == -1) + { + if (errno == error_noent) return; + log3("warning: unable to stat ",fn.s,"; will try again later\n"); + goto fail; + } + + /* -todo +info -local -remote ?bounce */ + if (!injectbounce(id)) + goto fail; /* injectbounce() produced error message */ + + strnum3[fmt_ulong(strnum3,id)] = 0; + log3("end msg ",strnum3,"\n"); + + /* -todo +info -local -remote -bounce */ + fnmake_info(id); + if (unlink(fn.s) == -1) + { + log3("warning: unable to unlink ",fn.s,"; will try again later\n"); + goto fail; + } + + /* -todo -info -local -remote -bounce; we can relax */ + fnmake_foop(id); + if (substdio_putflush(&sstoqc,fn.s,fn.len) == -1) { cleandied(); return; } + if (substdio_get(&ssfromqc,&ch,1) != 1) { cleandied(); return; } + if (ch != '+') + log3("warning: qmail-clean unable to clean up ",fn.s,"\n"); + + return; + + fail: + pe.id = id; pe.dt = now() + SLEEP_SYSFAIL; + while (!prioq_insert(&pqdone,&pe)) nomem(); +} + +void pass_do() +{ + int c; + struct prioq_elt pe; + + for (c = 0;c < CHANNELS;++c) pass_dochan(c); + if (prioq_min(&pqfail,&pe)) + if (pe.dt <= recent) + { + prioq_delmin(&pqfail); + pqadd(pe.id); + } + if (prioq_min(&pqdone,&pe)) + if (pe.dt <= recent) + { + prioq_delmin(&pqdone); + messdone(pe.id); + } +} + + +/* this file is too long ---------------------------------------------- TODO */ + +datetime_sec nexttodorun; +DIR *tododir; /* if 0, have to opendir again */ +stralloc todoline = {0}; +char todobuf[SUBSTDIO_INSIZE]; +char todobufinfo[512]; +char todobufchan[CHANNELS][1024]; + +void todo_init() +{ + tododir = 0; + nexttodorun = now(); + trigger_set(); +} + +void todo_selprep(nfds,rfds,wakeup) +int *nfds; +fd_set *rfds; +datetime_sec *wakeup; +{ + if (flagexitasap) return; + trigger_selprep(nfds,rfds); + if (tododir) *wakeup = 0; + if (*wakeup > nexttodorun) *wakeup = nexttodorun; +} + +void todo_do(rfds) +fd_set *rfds; +{ + struct stat st; + substdio ss; int fd; + substdio ssinfo; int fdinfo; + substdio sschan[CHANNELS]; + int fdchan[CHANNELS]; + int flagchan[CHANNELS]; + struct prioq_elt pe; + char ch; + int match; + unsigned long id; + unsigned int len; + direntry *d; + int c; + unsigned long uid; + unsigned long pid; + + fd = -1; + fdinfo = -1; + for (c = 0;c < CHANNELS;++c) fdchan[c] = -1; + + if (flagexitasap) return; + + if (!tododir) + { + if (!trigger_pulled(rfds)) + if (recent < nexttodorun) + return; + trigger_set(); + tododir = opendir("todo"); + if (!tododir) + { + pausedir("todo"); + return; + } + nexttodorun = recent + SLEEP_TODO; + } + + d = readdir(tododir); + if (!d) + { + closedir(tododir); + tododir = 0; + return; + } + if (str_equal(d->d_name,".")) return; + if (str_equal(d->d_name,"..")) return; + len = scan_ulong(d->d_name,&id); + if (!len || d->d_name[len]) return; + + fnmake_todo(id); + + fd = open_read(fn.s); + if (fd == -1) { log3("warning: unable to open ",fn.s,"\n"); return; } + + fnmake_mess(id); + /* just for the statistics */ + if (stat(fn.s,&st) == -1) + { log3("warning: unable to stat ",fn.s,"\n"); goto fail; } + + for (c = 0;c < CHANNELS;++c) + { + fnmake_chanaddr(id,c); + if (unlink(fn.s) == -1) if (errno != error_noent) + { log3("warning: unable to unlink ",fn.s,"\n"); goto fail; } + } + + fnmake_info(id); + if (unlink(fn.s) == -1) if (errno != error_noent) + { log3("warning: unable to unlink ",fn.s,"\n"); goto fail; } + + fdinfo = open_excl(fn.s); + if (fdinfo == -1) + { log3("warning: unable to create ",fn.s,"\n"); goto fail; } + + strnum3[fmt_ulong(strnum3,id)] = 0; + log3("new msg ",strnum3,"\n"); + + for (c = 0;c < CHANNELS;++c) flagchan[c] = 0; + + substdio_fdbuf(&ss,read,fd,todobuf,sizeof(todobuf)); + substdio_fdbuf(&ssinfo,write,fdinfo,todobufinfo,sizeof(todobufinfo)); + + uid = 0; + pid = 0; + + for (;;) + { + if (getln(&ss,&todoline,&match,'\0') == -1) + { + /* perhaps we're out of memory, perhaps an I/O error */ + fnmake_todo(id); + log3("warning: trouble reading ",fn.s,"\n"); goto fail; + } + if (!match) break; + + switch(todoline.s[0]) + { + case 'u': + scan_ulong(todoline.s + 1,&uid); + break; + case 'p': + scan_ulong(todoline.s + 1,&pid); + break; + case 'F': + if (substdio_putflush(&ssinfo,todoline.s,todoline.len) == -1) + { + fnmake_info(id); + log3("warning: trouble writing to ",fn.s,"\n"); goto fail; + } + log2("info msg ",strnum3); + strnum2[fmt_ulong(strnum2,(unsigned long) st.st_size)] = 0; + log2(": bytes ",strnum2); + log1(" from <"); logsafe(todoline.s + 1); + strnum2[fmt_ulong(strnum2,pid)] = 0; + log2("> qp ",strnum2); + strnum2[fmt_ulong(strnum2,uid)] = 0; + log2(" uid ",strnum2); + log1("\n"); + break; + case 'T': + switch(rewrite(todoline.s + 1)) + { + case 0: nomem(); goto fail; + case 2: c = 1; break; + default: c = 0; break; + } + if (fdchan[c] == -1) + { + fnmake_chanaddr(id,c); + fdchan[c] = open_excl(fn.s); + if (fdchan[c] == -1) + { log3("warning: unable to create ",fn.s,"\n"); goto fail; } + substdio_fdbuf(&sschan[c] + ,write,fdchan[c],todobufchan[c],sizeof(todobufchan[c])); + flagchan[c] = 1; + } + if (substdio_bput(&sschan[c],rwline.s,rwline.len) == -1) + { + fnmake_chanaddr(id,c); + log3("warning: trouble writing to ",fn.s,"\n"); goto fail; + } + break; + default: + fnmake_todo(id); + log3("warning: unknown record type in ",fn.s,"\n"); goto fail; + } + } + + close(fd); fd = -1; + + fnmake_info(id); + if (substdio_flush(&ssinfo) == -1) + { log3("warning: trouble writing to ",fn.s,"\n"); goto fail; } + if (fsync(fdinfo) == -1) + { log3("warning: trouble fsyncing ",fn.s,"\n"); goto fail; } + close(fdinfo); fdinfo = -1; + + for (c = 0;c < CHANNELS;++c) + if (fdchan[c] != -1) + { + fnmake_chanaddr(id,c); + if (substdio_flush(&sschan[c]) == -1) + { log3("warning: trouble writing to ",fn.s,"\n"); goto fail; } + if (fsync(fdchan[c]) == -1) + { log3("warning: trouble fsyncing ",fn.s,"\n"); goto fail; } + close(fdchan[c]); fdchan[c] = -1; + } + + fnmake_todo(id); + if (substdio_putflush(&sstoqc,fn.s,fn.len) == -1) { cleandied(); return; } + if (substdio_get(&ssfromqc,&ch,1) != 1) { cleandied(); return; } + if (ch != '+') + { + log3("warning: qmail-clean unable to clean up ",fn.s,"\n"); + return; + } + + pe.id = id; pe.dt = now(); + for (c = 0;c < CHANNELS;++c) + if (flagchan[c]) + while (!prioq_insert(&pqchan[c],&pe)) nomem(); + + for (c = 0;c < CHANNELS;++c) if (flagchan[c]) break; + if (c == CHANNELS) + while (!prioq_insert(&pqdone,&pe)) nomem(); + + return; + + fail: + if (fd != -1) close(fd); + if (fdinfo != -1) close(fdinfo); + for (c = 0;c < CHANNELS;++c) + if (fdchan[c] != -1) close(fdchan[c]); +} + + +/* this file is too long ---------------------------------------------- MAIN */ + +int getcontrols() { if (control_init() == -1) return 0; + if (control_readint(&lifetime,"control/queuelifetime") == -1) return 0; + if (control_readint(&concurrency[0],"control/concurrencylocal") == -1) return 0; + if (control_readint(&concurrency[1],"control/concurrencyremote") == -1) return 0; + if (control_rldef(&envnoathost,"control/envnoathost",1,"envnoathost") != 1) return 0; + if (control_rldef(&bouncefrom,"control/bouncefrom",0,"MAILER-DAEMON") != 1) return 0; + if (control_rldef(&bouncehost,"control/bouncehost",1,"bouncehost") != 1) return 0; + if (control_rldef(&doublebouncehost,"control/doublebouncehost",1,"doublebouncehost") != 1) return 0; + if (control_rldef(&doublebounceto,"control/doublebounceto",0,"postmaster") != 1) return 0; + if (!stralloc_cats(&doublebounceto,"@")) return 0; + if (!stralloc_cat(&doublebounceto,&doublebouncehost)) return 0; + if (!stralloc_0(&doublebounceto)) return 0; + if (control_readfile(&locals,"control/locals",1) != 1) return 0; + if (!constmap_init(&maplocals,locals.s,locals.len,0)) return 0; + switch(control_readfile(&percenthack,"control/percenthack",0)) + { + case -1: return 0; + case 0: if (!constmap_init(&mappercenthack,"",0,0)) return 0; break; + case 1: if (!constmap_init(&mappercenthack,percenthack.s,percenthack.len,0)) return 0; break; + } + switch(control_readfile(&redir,"control/recipientmap",0)) + { + case -1: return 0; + case 0: if (!constmap_init(&mapredir,"",0,1)) return 0; break; + case 1: if (!constmap_init(&mapredir,redir.s,redir.len,1)) return 0; break; + } + switch(control_readfile(&vdoms,"control/virtualdomains",0)) + { + case -1: return 0; + case 0: if (!constmap_init(&mapvdoms,"",0,1)) return 0; break; + case 1: if (!constmap_init(&mapvdoms,vdoms.s,vdoms.len,1)) return 0; break; + } + return 1; } + +stralloc newlocals = {0}; +stralloc newvdoms = {0}; + +void regetcontrols() +{ + int r; + + if (control_readfile(&newlocals,"control/locals",1) != 1) + { log1("alert: unable to reread control/locals\n"); return; } + r = control_readfile(&newvdoms,"control/virtualdomains",0); + if (r == -1) + { log1("alert: unable to reread control/virtualdomains\n"); return; } + + constmap_free(&maplocals); + constmap_free(&mapvdoms); + + while (!stralloc_copy(&locals,&newlocals)) nomem(); + while (!constmap_init(&maplocals,locals.s,locals.len,0)) nomem(); + + if (r) + { + while (!stralloc_copy(&vdoms,&newvdoms)) nomem(); + while (!constmap_init(&mapvdoms,vdoms.s,vdoms.len,1)) nomem(); + } + else + while (!constmap_init(&mapvdoms,"",0,1)) nomem(); +} + +void reread() +{ + if (chdir(auto_qmail) == -1) + { + log1("alert: unable to reread controls: unable to switch to home directory\n"); + return; + } + regetcontrols(); + while (chdir("queue") == -1) + { + log1("alert: unable to switch back to queue directory; HELP! sleeping...\n"); + sleep(10); + } +} + +void main() +{ + int fd; + datetime_sec wakeup; + fd_set rfds; + fd_set wfds; + int nfds; + struct timeval tv; + int c; + + if (chdir(auto_qmail) == -1) + { log1("alert: cannot start: unable to switch to home directory\n"); _exit(111); } + if (!getcontrols()) + { log1("alert: cannot start: unable to read controls\n"); _exit(111); } + if (chdir("queue") == -1) + { log1("alert: cannot start: unable to switch to queue directory\n"); _exit(111); } + sig_pipeignore(); + sig_termcatch(sigterm); + sig_alarmcatch(sigalrm); + sig_hangupcatch(sighup); + sig_childdefault(); + umask(077); + + fd = open_write("lock/sendmutex"); + if (fd == -1) + { log1("alert: cannot start: unable to open mutex\n"); _exit(111); } + if (lock_exnb(fd) == -1) + { log1("alert: cannot start: qmail-send is already running\n"); _exit(111); } + + numjobs = 0; + for (c = 0;c < CHANNELS;++c) + { + char ch; + int u; + int r; + do + r = read(chanfdin[c],&ch,1); + while ((r == -1) && (errno == error_intr)); + if (r < 1) + { log1("alert: cannot start: hath the daemon spawn no fire?\n"); _exit(111); } + u = (unsigned int) (unsigned char) ch; + if (concurrency[c] > u) concurrency[c] = u; + numjobs += concurrency[c]; + } + + log1("running\n"); + + fnmake_init(); + + for (c = 0;c < CHANNELS;++c) + if (!concurrency[c]) + log1(channodelmsg[c]); + + comm_init(); + + pqstart(); + job_init(); + del_init(); + pass_init(); + todo_init(); + cleanup_init(); + + while (!flagexitasap || !del_canexit()) + { + if (flagexitasap) del_saywhynoexit(); + + recent = now(); + + if (flagrunasap) { flagrunasap = 0; pqrun(); } + if (flagreadasap) { flagreadasap = 0; reread(); } + + wakeup = recent + SLEEP_FOREVER; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + nfds = 1; + + comm_selprep(&nfds,&wfds); + del_selprep(&nfds,&rfds); + pass_selprep(&wakeup); + todo_selprep(&nfds,&rfds,&wakeup); + cleanup_selprep(&wakeup); + + if (wakeup <= recent) tv.tv_sec = 0; + else tv.tv_sec = wakeup - recent + SLEEP_FUZZ; + tv.tv_usec = 0; + + if (select(nfds,&rfds,&wfds,(fd_set *) 0,&tv) == -1) + if (errno == error_intr) + ; + else + log1("warning: trouble in select\n"); + else + { + recent = now(); + + comm_do(&wfds); + del_do(&rfds); + todo_do(&rfds); + pass_do(); + cleanup_do(); + } + } + pqfinish(); + log1("exiting\n"); + _exit(0); +} diff --git a/qmail-showctl.8 b/qmail-showctl.8 @@ -0,0 +1,12 @@ +.TH qmail-showctl 8 +.SH NAME +qmail-showctl \- analyze the qmail configuration files +.SH SYNOPSIS +.B qmail-showctl +.SH DESCRIPTION +.B qmail-showctl +explains the current +.B qmail +configuration. +.SH "SEE ALSO" +qmail-control(8) diff --git a/qmail-showctl.c b/qmail-showctl.c @@ -0,0 +1,233 @@ +#include "substdio.h" +#include "subfd.h" +#include "exit.h" +#include "fmt.h" +#include "str.h" +#include "control.h" +#include "constmap.h" +#include "stralloc.h" +#include "direntry.h" +#include "auto_qmail.h" + +stralloc me = {0}; +int meok; + +stralloc line = {0}; +char num[FMT_ULONG]; + +void safeput(buf,len) +char *buf; +unsigned int len; +{ + char ch; + + while (len > 0) { + ch = *buf; + if ((ch < 32) || (ch > 126)) ch = '?'; + substdio_put(subfdout,&ch,1); + ++buf; + --len; + } +} + +void do_int(fn,def,pre,post) +char *fn; +char *def; +char *pre; +char *post; +{ + int i; + substdio_puts(subfdout,"\n"); + substdio_puts(subfdout,fn); + substdio_puts(subfdout,": "); + switch(control_readint(&i,fn)) { + case 0: + substdio_puts(subfdout,"(Default.) "); + substdio_puts(subfdout,pre); + substdio_puts(subfdout,def); + substdio_puts(subfdout,post); + substdio_puts(subfdout,".\n"); + break; + case 1: + if (i < 0) i = 0; + substdio_puts(subfdout,pre); + substdio_put(subfdout,num,fmt_uint(num,i)); + substdio_puts(subfdout,post); + substdio_puts(subfdout,".\n"); + break; + default: + substdio_puts(subfdout,"Oops! Trouble reading this file.\n"); + break; + } +} + +void do_str(fn,flagme,def,pre) +char *fn; +int flagme; +char *def; +char *pre; +{ + substdio_puts(subfdout,"\n"); + substdio_puts(subfdout,fn); + substdio_puts(subfdout,": "); + switch(control_readline(&line,fn)) { + case 0: + substdio_puts(subfdout,"(Default.) "); + if (!stralloc_copys(&line,def)) { + substdio_puts(subfdout,"Oops! Out of memory.\n"); + break; + } + if (flagme && meok) + if (!stralloc_copy(&line,&me)) { + substdio_puts(subfdout,"Oops! Out of memory.\n"); + break; + } + case 1: + substdio_puts(subfdout,pre); + safeput(line.s,line.len); + substdio_puts(subfdout,".\n"); + break; + default: + substdio_puts(subfdout,"Oops! Trouble reading this file.\n"); + break; + } +} + +void do_lst(fn,def,pre,post) +char *fn; +char *def; +char *pre; +char *post; +{ + int i; + int j; + + substdio_puts(subfdout,"\n"); + substdio_puts(subfdout,fn); + substdio_puts(subfdout,": "); + switch(control_readfile(&line,fn)) { + case 0: + substdio_puts(subfdout,"(Default.) "); + substdio_puts(subfdout,def); + substdio_puts(subfdout,"\n"); + break; + case 1: + substdio_puts(subfdout,"\n"); + i = 0; + for (j = 0;j < line.len;++j) + if (!line.s[j]) { + substdio_puts(subfdout,pre); + safeput(line.s + i,j - i); + substdio_puts(subfdout,post); + substdio_puts(subfdout,"\n"); + i = j + 1; + } + break; + default: + substdio_puts(subfdout,"Oops! Trouble reading this file.\n"); + break; + } +} + +void main() +{ + DIR *dir; + direntry *d; + + substdio_puts(subfdout,"The qmail control files are stored in "); + substdio_puts(subfdout,auto_qmail); + substdio_puts(subfdout,"/control.\n"); + + if (chdir(auto_qmail) == -1) { + substdio_puts(subfdout,"Oops! Unable to chdir to "); + substdio_puts(subfdout,auto_qmail); + substdio_puts(subfdout,".\n"); + substdio_flush(subfdout); + _exit(111); + } + if (chdir("control") == -1) { + substdio_puts(subfdout,"Oops! Unable to chdir to control.\n"); + substdio_flush(subfdout); + _exit(111); + } + + dir = opendir("."); + if (!dir) { + substdio_puts(subfdout,"Oops! Unable to open current directory.\n"); + substdio_flush(subfdout); + _exit(111); + } + + meok = control_readline(&me,"me"); + if (meok == -1) { + substdio_puts(subfdout,"Oops! Trouble reading control/me."); + substdio_flush(subfdout); + _exit(111); + } + + do_lst("badmailfrom","Any MAIL FROM is allowed.",""," not accepted in MAIL FROM."); + do_str("bouncefrom",0,"MAILER-DAEMON","Bounce user name is "); + do_str("bouncehost",1,"bouncehost","Bounce host name is "); + do_int("concurrencylocal","10","Local concurrency is ",""); + do_int("concurrencyremote","20","Remote concurrency is ",""); + do_str("defaultdomain",1,"defaultdomain","Default domain name is "); + do_str("defaulthost",1,"defaulthost","Default host name is "); + do_str("doublebouncehost",1,"doublebouncehost","2B recipient host: "); + do_str("doublebounceto",0,"postmaster","2B recipient user: "); + do_str("envnoathost",1,"envnoathost","Presumed domain name is "); + do_str("helohost",1,"helohost","SMTP client HELO host name is "); + do_str("idhost",1,"idhost","Message-ID host name is "); + do_str("localiphost",1,"localiphost","Local IP address becomes "); + do_lst("locals","Messages for me are delivered locally.","Messages for "," are delivered locally."); + do_str("me",0,"undefined! Uh-oh","My name is "); + do_lst("percenthack","The percent hack is not allowed.","The percent hack is allowed for user%host@","."); + do_str("plusdomain",1,"plusdomain","Plus domain name is "); + do_int("queuelifetime","604800","Message lifetime in the queue is "," seconds"); + do_lst("rcpthosts","SMTP clients may send messages to any recipient.","SMTP clients may send messages to recipients at ","."); + do_lst("recipientmap","No redirections.","Redirection: ",""); + do_str("smtpgreeting",1,"smtpgreeting","SMTP greeting: 220 "); + do_lst("smtproutes","No artificial SMTP routes.","SMTP route: ",""); + do_int("timeoutconnect","60","SMTP client connection timeout is "," seconds"); + do_int("timeoutremote","1200","SMTP client data timeout is "," seconds"); + do_int("timeoutsmtpd","1200","SMTP server data timeout is "," seconds"); + do_lst("virtualdomains","No virtual domains.","Virtual domain: ",""); + + while (d = readdir(dir)) { + if (str_equal(d->d_name,".")) continue; + if (str_equal(d->d_name,"..")) continue; + if (str_equal(d->d_name,"bouncefrom")) continue; + if (str_equal(d->d_name,"bouncehost")) continue; + if (str_equal(d->d_name,"badmailfrom")) continue; + if (str_equal(d->d_name,"bouncefrom")) continue; + if (str_equal(d->d_name,"bouncehost")) continue; + if (str_equal(d->d_name,"concurrencylocal")) continue; + if (str_equal(d->d_name,"concurrencyremote")) continue; + if (str_equal(d->d_name,"defaultdomain")) continue; + if (str_equal(d->d_name,"defaulthost")) continue; + if (str_equal(d->d_name,"doublebouncehost")) continue; + if (str_equal(d->d_name,"doublebounceto")) continue; + if (str_equal(d->d_name,"envnoathost")) continue; + if (str_equal(d->d_name,"helohost")) continue; + if (str_equal(d->d_name,"idhost")) continue; + if (str_equal(d->d_name,"localiphost")) continue; + if (str_equal(d->d_name,"locals")) continue; + if (str_equal(d->d_name,"me")) continue; + if (str_equal(d->d_name,"percenthack")) continue; + if (str_equal(d->d_name,"plusdomain")) continue; + if (str_equal(d->d_name,"queuelifetime")) continue; + if (str_equal(d->d_name,"rcpthosts")) continue; + if (str_equal(d->d_name,"recipientmap")) continue; + if (str_equal(d->d_name,"smtpgreeting")) continue; + if (str_equal(d->d_name,"smtproutes")) continue; + if (str_equal(d->d_name,"timeoutconnect")) continue; + if (str_equal(d->d_name,"timeoutremote")) continue; + if (str_equal(d->d_name,"timeoutsmtpd")) continue; + if (str_equal(d->d_name,"virtualdomains")) continue; + substdio_puts(subfdout,"\n"); + substdio_puts(subfdout,d->d_name); + substdio_puts(subfdout,": I have no idea what this file does.\n"); + } + + substdio_flush(subfdout); + _exit(0); +} diff --git a/qmail-smtpd.8 b/qmail-smtpd.8 @@ -0,0 +1,125 @@ +.TH qmail-smtpd 8 +.SH NAME +qmail-smtpd \- receive mail via SMTP +.SH SYNOPSIS +.B qmail-smtpd +.SH DESCRIPTION +.B qmail-smtpd +receives mail messages via the Simple Mail Transfer Protocol (SMTP) +and invokes +.B qmail-queue +to deposit them into the outgoing queue. +.B qmail-smtpd +must be supplied several environment variables; +see +.BR tcp-environ(5) . + +.B qmail-smtpd +is responsible for counting hops. +It rejects any message with 100 or more +.B Received +or +.B Delivered-To +header fields. + +.B qmail-smtpd +supports ESMTP, including the 8BITMIME and PIPELINING options. +.SH TRANSPARENCY +.B qmail-smtpd +converts the SMTP newline convention into the UNIX newline convention +by converting CR LF into LF. + +.B qmail-smtpd +accepts messages that contain long lines or non-ASCII characters, +even though such messages violate the SMTP protocol. +.SH "CONTROL FILES" +.TP 5 +.I badmailfrom +Unacceptable envelope sender addresses. +.B qmail-smtpd +will reject every recipient address for a message +if the envelope sender address is listed in +.IR badmailfrom . +A line in +.I badmailfrom +may be of the form +.BR @\fIhost , +meaning every address at +.IR host . +.TP 5 +.I localiphost +Replacement host name for local IP addresses. +Default: +.IR me , +if that is supplied. +.B qmail-smtpd +is responsible for recognizing dotted-decimal addresses for the +current host. +When it sees a recipient address of the form +.IR box@[d.d.d.d] , +where +.I d.d.d.d +is a local IP address, +it replaces +.IR [d.d.d.d] +with +.IR localiphost . +This is done before +.IR rcpthosts . +.TP 5 +.I rcpthosts +Allowed RCPT domains. +If +.I rcpthosts +is supplied, +.B qmail-smtpd +will reject +any envelope recipient address with a domain not listed in +.IR rcpthosts . + +Exception: +If the environment variable +.B RELAYCLIENT +is set, +.B qmail-smtpd +will ignore +.IR rcpthosts , +and will append the value of +.B RELAYCLIENT +to each incoming recipient address. + +.I rcpthosts +may include wildcards: + +.EX + heaven.af.mil + .heaven.af.mil +.EE + +Envelope recipient addresses without @ signs are +always allowed through. +.TP 5 +.I smtpgreeting +SMTP greeting message. +Default: +.IR me , +if that is supplied; +otherwise +.B qmail-smtpd +will refuse to run. +The first word of +.I smtpgreeting +should be the current host's name. +.TP 5 +.I timeoutsmtpd +Number of seconds +.B qmail-smtpd +will wait for each new buffer of data from the remote SMTP client. +Default: 1200. +.SH "SEE ALSO" +tcp-env(1), +tcp-environ(5), +qmail-control(5), +qmail-inject(8), +qmail-queue(8), +qmail-remote(8) diff --git a/qmail-smtpd.c b/qmail-smtpd.c @@ -0,0 +1,449 @@ +#include "sig.h" +#include "readwrite.h" +#include "getln.h" +#include "stralloc.h" +#include "substdio.h" +#include "alloc.h" +#include "auto_qmail.h" +#include "control.h" +#include "received.h" +#include "constmap.h" +#include "error.h" +#include "ipme.h" +#include "ip.h" +#include "qmail.h" +#include "str.h" +#include "fmt.h" +#include "byte.h" +#include "case.h" +#include "env.h" +#include "now.h" +#include "exit.h" + +#define MAXHOPS 100 +int timeout = 1200; + +char ssoutbuf[512]; +substdio ssout = SUBSTDIO_FDBUF(write,1,ssoutbuf,sizeof(ssoutbuf)); + +void die() { substdio_flush(&ssout); _exit(1); } +void flush() { if (substdio_flush(&ssout) == -1) _exit(1); } +void out(s) char *s; { if (substdio_puts(&ssout,s) == -1) die(); } + +int timeoutread(fd,buf,n) int fd; char *buf; int n; +{ + int r; int saveerrno; + flush(); + alarm(timeout); + r = read(fd,buf,n); saveerrno = errno; + alarm(0); + errno = saveerrno; return r; +} + +char ssinbuf[1024]; +substdio ssin = SUBSTDIO_FDBUF(timeoutread,0,ssinbuf,sizeof(ssinbuf)); + + +void outofmem() { out("421 out of memory (#4.3.0)\r\n"); die(); } +void sigalrm() { out("451 timeout (#4.4.2)\r\n"); die(); } + +struct qmail qqt; +stralloc greeting = {0}; +int liphostok = 0; +stralloc liphost = {0}; +int rhok = 0; +stralloc rcpthosts = {0}; +struct constmap maprcpthosts; +int bmfok = 0; +stralloc bmf = {0}; +struct constmap mapbmf; +int flagbarf; /* defined if seenmail */ + +stralloc helohost = {0}; +stralloc mailfrom = {0}; +stralloc rcptto = {0}; +int seenmail = 0; + +stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */ + +char *remoteip; +char *remotehost; +char *remoteinfo; +char *local; +char *relayclient; + +void dohelo(arg) char *arg; +{ + if (!stralloc_copys(&helohost,arg)) outofmem(); + if (!stralloc_0(&helohost)) outofmem(); +} + +void getenvs() +{ + remoteip = env_get("TCPREMOTEIP"); + if (!remoteip) remoteip = "unknown"; + local = env_get("TCPLOCALHOST"); + if (!local) local = env_get("TCPLOCALIP"); + if (!local) local = "unknown"; + remotehost = env_get("TCPREMOTEHOST"); + if (!remotehost) remotehost = "unknown"; + remoteinfo = env_get("TCPREMOTEINFO"); + relayclient = env_get("RELAYCLIENT"); + dohelo(remotehost); +} + +void straynewline() +{ + out("451 \ +Put ,E=\\r\\n at the end of Mether, Mtcp, or Msmtp in sendmail.cf \ +if you are using Solaris 2.5 (fixed in 2.5.1). \ +I cannot accept messages with stray newlines. \ +Many SMTP servers will time out waiting for \\r\\n.\\r\\n.\ +\r\n"); + die(); +} + +void blast(ssfrom,hops) +substdio *ssfrom; +int *hops; +{ + char ch; + int state; + int flaginheader; + int pos; /* number of bytes since most recent \n, if fih */ + int flagmaybex; /* 1 if this line might match RECEIVED, if fih */ + int flagmaybey; /* 1 if this line might match \r\n, if fih */ + int flagmaybez; /* 1 if this line might match DELIVERED, if fih */ + + state = 1; + *hops = 0; + flaginheader = 1; + pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; + for (;;) + { + if (substdio_get(ssfrom,&ch,1) <= 0) die(); + if (flaginheader) + { + if (pos < 9) + { + if (ch != "delivered"[pos]) if (ch != "DELIVERED"[pos]) flagmaybez = 0; + if (flagmaybez) if (pos == 8) ++*hops; + if (pos < 8) + if (ch != "received"[pos]) if (ch != "RECEIVED"[pos]) flagmaybex = 0; + if (flagmaybex) if (pos == 7) ++*hops; + if (pos < 2) if (ch != "\r\n"[pos]) flagmaybey = 0; + if (flagmaybey) if (pos == 1) flaginheader = 0; + } + ++pos; + if (ch == '\n') { pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; } + } + switch(state) + { + case 0: + if (ch == '\n') straynewline(); + if (ch == '\r') { state = 4; continue; } + break; + case 1: /* \r\n */ + if (ch == '\n') straynewline(); + if (ch == '.') { state = 2; continue; } + if (ch == '\r') { state = 4; continue; } + state = 0; + break; + case 2: /* \r\n + . */ + if (ch == '\n') straynewline(); + if (ch == '\r') { state = 3; continue; } + state = 0; + break; + case 3: /* \r\n + .\r */ + if (ch == '\n') return; + qmail_put(&qqt,".\r",2); + if (ch == '\r') { state = 4; continue; } + state = 0; + break; + case 4: /* + \r */ + if (ch == '\n') { state = 1; break; } + if (ch != '\r') { qmail_put(&qqt,"\r",1); state = 0; } + } + qmail_put(&qqt,&ch,1); + } +} + +int addrparse(arg) +char *arg; +{ + int i; + char ch; + struct ip_address ip; + int flagesc; + int flagquoted; + + arg += str_chr(arg,'<'); + if (*arg != '<') return 0; + ++arg; + + /* strip source route */ + if (*arg == '@') while (*arg) if (*arg++ == ':') break; + + if (!*arg) return 0; + if (!stralloc_copys(&addr,"")) outofmem(); + flagesc = 0; + flagquoted = 0; + for (i = 0;ch = arg[i];++i) /* copy arg to addr, stripping quotes */ + { + if (flagesc) + { if (!stralloc_append(&addr,&ch)) outofmem(); flagesc = 0; } + else + { + if (!flagquoted && (ch == '>')) break; + switch(ch) + { + case '\\': flagesc = 1; break; + case '"': flagquoted = !flagquoted; break; + default: if (!stralloc_append(&addr,&ch)) outofmem(); + } + } + } + if (!ch) return 0; + if (!stralloc_append(&addr,"")) outofmem(); + ++i; + while (arg[i]) + { + if (!case_diffs(arg + i," BODY=8BITMIME")) i += 14; + else if (!case_diffs(arg + i," BODY=7BIT")) i += 10; + else return 0; + } + + if (liphostok) + { + i = byte_rchr(addr.s,addr.len,'@'); + if (i < addr.len) /* if not, partner should go read rfc 821 */ + if (addr.s[i + 1] == '[') + if (!addr.s[i + 1 + ip_scanbracket(addr.s + i + 1,&ip)]) + if (ipme_is(&ip)) + { + addr.len = i + 1; + if (!stralloc_cat(&addr,&liphost)) outofmem(); + if (!stralloc_0(&addr)) outofmem(); + } + } + + return 1; +} + +int addrallowed() +{ + int j; + if (!rhok) return 1; + j = byte_rchr(addr.s,addr.len,'@'); + if (j >= addr.len) return 1; /* can be taken care of by envnoathost */ + if (constmap(&maprcpthosts,addr.s + j + 1,addr.len - j - 2)) return 1; + for (;j < addr.len;++j) + if (addr.s[j] == '.') + if (constmap(&maprcpthosts,addr.s + j,addr.len - j - 1)) return 1; + return 0; +} + +void bmfcheck() +{ + int j; + flagbarf = 0; + if (!bmfok) return; + if (constmap(&mapbmf,addr.s,addr.len - 1)) { flagbarf = 1; return; } + j = byte_rchr(addr.s,addr.len,'@'); + if (j < addr.len) + if (constmap(&mapbmf,addr.s + j,addr.len - j - 1)) flagbarf = 1; +} + +void smtp_greet(code) char *code; { + if (substdio_puts(&ssout,code) == -1) die(); + if (substdio_put(&ssout,greeting.s,greeting.len) == -1) die(); } +void smtp_quit() { smtp_greet("221 "); out("\r\n"); die(); } +void smtp_help() { out("214-qmail home page: http://pobox.com/~djb/qmail.html\r\n214 send comments to qmail@pobox.com\r\n"); } +void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); } +void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); } +void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); } +void err_unimpl() { out("502 unimplemented (#5.5.1)\r\n"); } +void err_seenmail() { out("503 one MAIL per message (#5.5.1)\r\n"); } +void err_wantmail() { out("503 MAIL first (#5.5.1)\r\n"); } +void err_wantrcpt() { out("503 RCPT first (#5.5.1)\r\n"); } +void err_noop() { out("250 ok\r\n"); } +void err_vrfy() { out("252 send some mail, i'll try my best\r\n"); } +void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); } +void smtp_helo(arg) char *arg; { + smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n"); + seenmail = 0; + dohelo(arg ? arg : ""); } +void smtp_rset() { + seenmail = 0; + out("250 flushed\r\n"); } +void smtp_mail(arg) char *arg; { + if (seenmail) { err_seenmail(); return; } + if (!arg) { err_syntax(); return; } + if (!addrparse(arg)) { err_syntax(); return; } + bmfcheck(); + seenmail = 1; out("250 ok\r\n"); + if (!stralloc_copys(&rcptto,"")) outofmem(); + if (!stralloc_copys(&mailfrom,addr.s)) outofmem(); + if (!stralloc_0(&mailfrom)) outofmem(); } +void smtp_rcpt(arg) char *arg; { + if (!seenmail) { err_wantmail(); return; } + if (!arg) { err_syntax(); return; } + if (!addrparse(arg)) { err_syntax(); return; } + if (flagbarf) { err_bmf(); return; } + if (relayclient) + { + --addr.len; + if (!stralloc_cats(&addr,relayclient)) outofmem(); + if (!stralloc_0(&addr)) outofmem(); + } + else + if (!addrallowed()) { err_nogateway(); return; } + out("250 ok\r\n"); + if (!stralloc_cats(&rcptto,"T")) outofmem(); + if (!stralloc_cats(&rcptto,addr.s)) outofmem(); + if (!stralloc_0(&rcptto)) outofmem(); } + +char accept_buf[FMT_ULONG]; +void acceptmessage(qp) unsigned long qp; +{ + datetime_sec when; + when = now(); + out("250 ok "); + accept_buf[fmt_ulong(accept_buf,(unsigned long) when)] = 0; + out(accept_buf); + out(" qp "); + accept_buf[fmt_ulong(accept_buf,qp)] = 0; + out(accept_buf); + out("\r\n"); +} + +void smtp_data() { + int hops; int r; unsigned long qp; + if (!seenmail) { err_wantmail(); return; } + if (!rcptto.len) { err_wantrcpt(); return; } + seenmail = 0; + if (qmail_open(&qqt) == -1) { err_qqt(); return; } + qp = qmail_qp(&qqt); + out("354 go ahead\r\n"); + + received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,case_diffs(remotehost,helohost.s) ? helohost.s : 0); + blast(&ssin,&hops); + hops = (hops >= MAXHOPS); + if (hops) qmail_fail(&qqt); + qmail_from(&qqt,mailfrom.s); + qmail_put(&qqt,rcptto.s,rcptto.len); + + r = qmail_close(&qqt); + if (!r) { acceptmessage(qp); return; } + if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; } + switch(r) + { + case QMAIL_TOOLONG: out("554 address too long (#5.1.3)\r\n"); return; + case QMAIL_SYS: out("451 qq system error (#4.3.0)\r\n"); return; + case QMAIL_READ: out("451 qq read error (#4.3.0)\r\n"); return; + case QMAIL_WRITE: out("451 qq write error or disk full (#4.3.0)\r\n"); return; + case QMAIL_NOMEM: out("451 qq out of memory (#4.3.0)\r\n"); return; + case QMAIL_EXECSOFT: out("451 could not exec qq (#4.3.0)\r\n"); return; + case QMAIL_TIMEOUT: out("451 qq timeout (#4.3.0)\r\n"); return; + case QMAIL_WAITPID: out("451 qq waitpid surprise (#4.3.0)\r\n"); return; + case QMAIL_CRASHED: out("451 qq crashed (#4.3.0)\r\n"); return; + case QMAIL_USAGE: out("451 qq usage surprise (#4.3.0)\r\n"); return; + default: out("451 qq internal bug (#4.3.0)\r\n"); return; + } +} + +static struct { void (*fun)(); char *text; int flagflush; } smtpcmd[] = { + { smtp_rcpt, "rcpt", 0 } +, { smtp_mail, "mail", 0 } +, { smtp_data, "data", 1 } +, { smtp_quit, "quit", 1 } +, { smtp_helo, "helo", 1 } +, { smtp_helo, "ehlo", 1 } +, { smtp_rset, "rset", 0 } +, { smtp_help, "help", 1 } +, { err_noop, "noop", 1 } +, { err_vrfy, "vrfy", 1 } +, { 0, 0, 0 } +}; + +void doit(cmd) +char *cmd; +{ + int i; + int j; + char ch; + + for (i = 0;smtpcmd[i].fun;++i) + { + for (j = 0;ch = smtpcmd[i].text[j];++j) + if ((cmd[j] != ch) && (cmd[j] != ch - 32)) + break; + if (!ch) + if (!cmd[j] || (cmd[j] == ' ')) + { + while (cmd[j] == ' ') ++j; + if (!cmd[j]) + smtpcmd[i].fun((char *) 0); + else + smtpcmd[i].fun(cmd + j); + if (smtpcmd[i].flagflush) flush(); + return; + } + } + err_unimpl(); + flush(); +} + +void getcontrols() +{ + if (control_init() == -1) die(); + if (control_rldef(&greeting,"control/smtpgreeting",1,(char *) 0) != 1) die(); + switch(control_rldef(&liphost,"control/localiphost",1,(char *) 0)) + { case -1: die(); case 1: liphostok = 1; } + if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die(); + if (timeout <= 0) timeout = 1; + switch(control_readfile(&rcpthosts,"control/rcpthosts",0)) + { + case -1: die(); + case 1: + rhok = 1; + if (!constmap_init(&maprcpthosts,rcpthosts.s,rcpthosts.len,0)) die(); + } + switch(control_readfile(&bmf,"control/badmailfrom",0)) + { + case -1: die(); + case 1: + bmfok = 1; + if (!constmap_init(&mapbmf,bmf.s,bmf.len,0)) die(); + } +} + +void main() +{ + static stralloc cmd = {0}; + int match; + + sig_alarmcatch(sigalrm); + sig_pipeignore(); + + if (chdir(auto_qmail) == -1) die(); + getcontrols(); + getenvs(); + + if (ipme_init() != 1) die(); + + smtp_greet("220 "); + out(" ESMTP\r\n"); + + for (;;) + { + /* XXX: recipient can contain quoted lf. aargh. */ + if (getln(&ssin,&cmd,&match,'\n') == -1) die(); + if (!match) die(); + if (cmd.len == 0) die(); + if (cmd.s[--cmd.len] != '\n') die(); + if ((cmd.len > 0) && (cmd.s[cmd.len - 1] == '\r')) --cmd.len; + cmd.s[cmd.len++] = 0; + doit(cmd.s); + } +} diff --git a/qmail-start.9 b/qmail-start.9 @@ -0,0 +1,91 @@ +.TH qmail-start 8 +.SH NAME +qmail-start \- turn on mail delivery +.SH SYNOPSIS +.B qmail-start +[ +.I aliasempty +[ +.I logger arg ... +] +] +.SH DESCRIPTION +.B qmail-start +invokes +.BR qmail-send , +.BR qmail-lspawn , +.BR qmail-rspawn , +and +.BR qmail-clean . +These four daemons cooperate to deliver messages from the queue. + +.B qmail-start +arranges for +.BR qmail-send 's +activity record to be sent to +.BR qmail-start 's +output. +See +.B qmail-log(5) +for the format of the activity record. +Other than this, +.B qmail-start +does not print anything, even on failure. + +If +.I aliasempty +is supplied, +.B qmail-start +passes it to +.BR qmail-lspawn . + +If +.I logger +is supplied, +.B qmail-start +invokes +.I logger +with the given arguments, +and feeds +.BR qmail-send 's +activity record through +.IR logger . + +Environment variables given to +.B qmail-start +will eventually be passed on to +.BR qmail-local , +so make sure to clean up the environment if you run +.B qmail-start +manually: + +.EX + # env - PATH="QMAILHOME/bin:$PATH" +.br + qmail-start ./Mailbox splogger qmail & +.br + (all on one line) +.EE + +.B qmail-start +sets the uid and gid of each daemon properly. + +Note that +.B qmail-send +normally juggles several simultaneous deliveries. +To reduce +.BR qmail-send 's +impact on other programs, +you can run +.B qmail-start +with a low priority. +.SH "SEE ALSO" +logger(1), +splogger(1), +nice(1), +qmail-log(5), +qmail-local(8), +qmail-clean(8), +qmail-lspawn(8), +qmail-rspawn(8), +qmail-send(8) diff --git a/qmail-start.c b/qmail-start.c @@ -0,0 +1,120 @@ +#include "fd.h" +#include "prot.h" +#include "exit.h" +#include "fork.h" +#include "auto_uids.h" + +char *(qsargs[]) = { "qmail-send", 0 }; +char *(qcargs[]) = { "qmail-clean", 0 }; +char *(qlargs[]) = { "qmail-lspawn", "./Mailbox", 0 }; +char *(qrargs[]) = { "qmail-rspawn", 0 }; + +void die() { _exit(111); } + +int pi0[2]; +int pi1[2]; +int pi2[2]; +int pi3[2]; +int pi4[2]; +int pi5[2]; +int pi6[2]; + +void close23456() { close(2); close(3); close(4); close(5); close(6); } + +void closepipes() { + close(pi1[0]); close(pi1[1]); close(pi2[0]); close(pi2[1]); + close(pi3[0]); close(pi3[1]); close(pi4[0]); close(pi4[1]); + close(pi5[0]); close(pi5[1]); close(pi6[0]); close(pi6[1]); +} + +void main(argc,argv) +int argc; +char **argv; +{ + if (chdir("/") == -1) die(); + umask(077); + if (prot_gid(auto_gidq) == -1) die(); + + if (fd_copy(2,0) == -1) die(); + if (fd_copy(3,0) == -1) die(); + if (fd_copy(4,0) == -1) die(); + if (fd_copy(5,0) == -1) die(); + if (fd_copy(6,0) == -1) die(); + + if (argv[1]) { + qlargs[1] = argv[1]; + ++argv; + } + + if (argv[1]) { + if (pipe(pi0) == -1) die(); + switch(fork()) { + case -1: + die(); + case 0: + if (prot_gid(auto_gidn) == -1) die(); + if (prot_uid(auto_uidl) == -1) die(); + close(pi0[1]); + if (fd_move(0,pi0[0]) == -1) die(); + close23456(); + execvp(argv[1],argv + 1); + die(); + } + close(pi0[0]); + if (fd_move(1,pi0[1]) == -1) die(); + } + + if (pipe(pi1) == -1) die(); + if (pipe(pi2) == -1) die(); + if (pipe(pi3) == -1) die(); + if (pipe(pi4) == -1) die(); + if (pipe(pi5) == -1) die(); + if (pipe(pi6) == -1) die(); + + switch(fork()) { + case -1: die(); + case 0: + if (fd_copy(0,pi1[0]) == -1) die(); + if (fd_copy(1,pi2[1]) == -1) die(); + close23456(); + closepipes(); + execvp(*qlargs,qlargs); + die(); + } + + switch(fork()) { + case -1: die(); + case 0: + if (prot_uid(auto_uidr) == -1) die(); + if (fd_copy(0,pi3[0]) == -1) die(); + if (fd_copy(1,pi4[1]) == -1) die(); + close23456(); + closepipes(); + execvp(*qrargs,qrargs); + die(); + } + + switch(fork()) { + case -1: die(); + case 0: + if (prot_uid(auto_uidq) == -1) die(); + if (fd_copy(0,pi5[0]) == -1) die(); + if (fd_copy(1,pi6[1]) == -1) die(); + close23456(); + closepipes(); + execvp(*qcargs,qcargs); + die(); + } + + if (prot_uid(auto_uids) == -1) die(); + if (fd_copy(0,1) == -1) die(); + if (fd_copy(1,pi1[1]) == -1) die(); + if (fd_copy(2,pi2[0]) == -1) die(); + if (fd_copy(3,pi3[1]) == -1) die(); + if (fd_copy(4,pi4[0]) == -1) die(); + if (fd_copy(5,pi5[1]) == -1) die(); + if (fd_copy(6,pi6[0]) == -1) die(); + closepipes(); + execvp(*qsargs,qsargs); + die(); +} diff --git a/qmail-tcpto.8 b/qmail-tcpto.8 @@ -0,0 +1,29 @@ +.TH qmail-tcpto 8 +.SH NAME +qmail-tcpto \- print TCP timeout table +.SH SYNOPSIS +.B qmail-tcpto +.SH DESCRIPTION +After an SMTP connection attempt times out, +.B qmail-remote +records the relevant IP address. +If the same address fails again (after at least two minutes with +no intervening successful connections), +.B qmail-remote +assumes that further attempts will fail for at least another hour. + +.B qmail-tcpto +prints +.BR qmail-remote 's +current list of timeouts. + +.B qmail-tcpto +must be run either as +.B root +or with user id +.B qmailr +and group id +.BR qmail . +.SH "SEE ALSO" +qmail-qread(8), +qmail-remote(8) diff --git a/qmail-tcpto.c b/qmail-tcpto.c @@ -0,0 +1,85 @@ +/* XXX: this program knows quite a bit about tcpto's internals */ + +#include "substdio.h" +#include "subfd.h" +#include "auto_qmail.h" +#include "fmt.h" +#include "ip.h" +#include "lock.h" +#include "error.h" +#include "exit.h" +#include "datetime.h" +#include "now.h" + +void die(n) int n; { substdio_flush(subfdout); _exit(n); } + +void warn(s) char *s; +{ + char *x; + x = error_str(errno); + substdio_puts(subfdout,s); + substdio_puts(subfdout,": "); + substdio_puts(subfdout,x); + substdio_puts(subfdout,"\n"); +} + +void die_chdir() { warn("fatal: unable to chdir"); die(111); } +void die_open() { warn("fatal: unable to open tcpto"); die(111); } +void die_lock() { warn("fatal: unable to lock tcpto"); die(111); } +void die_read() { warn("fatal: unable to read tcpto"); die(111); } + +char tcpto_buf[1024]; + +char tmp[FMT_ULONG + IPFMT]; + +void main() +{ + int fdlock; + int fd; + int r; + int i; + char *record; + struct ip_address ip; + datetime_sec when; + datetime_sec start; + + if (chdir(auto_qmail) == -1) die_chdir(); + if (chdir("queue/lock") == -1) die_chdir(); + + fdlock = open_write("tcpto"); + if (fdlock == -1) die_open(); + fd = open_read("tcpto"); + if (fd == -1) die_open(); + if (lock_ex(fdlock) == -1) die_lock(); + r = read(fd,tcpto_buf,sizeof(tcpto_buf)); + close(fd); + close(fdlock); + + if (r == -1) die_read(); + r >>= 4; + + start = now(); + + record = tcpto_buf; + for (i = 0;i < r;++i) + { + if (record[4] >= 1) + { + byte_copy(&ip,4,record); + when = (unsigned long) (unsigned char) record[11]; + when = (when << 8) + (unsigned long) (unsigned char) record[10]; + when = (when << 8) + (unsigned long) (unsigned char) record[9]; + when = (when << 8) + (unsigned long) (unsigned char) record[8]; + + substdio_put(subfdout,tmp,ip_fmt(tmp,&ip)); + substdio_puts(subfdout," timed out "); + substdio_put(subfdout,tmp,fmt_ulong(tmp,(unsigned long) (start - when))); + substdio_puts(subfdout," seconds ago; # recent timeouts: "); + substdio_put(subfdout,tmp,fmt_ulong(tmp,(unsigned long) (unsigned char) record[4])); + substdio_puts(subfdout,"\n"); + } + record += 16; + } + + die(0); +} diff --git a/qmail-upgrade.9 b/qmail-upgrade.9 @@ -0,0 +1,201 @@ +.TH qmail-upgrade 7 +.SH "NAME" +qmail-upgrade \- user-visible differences between qmail and sendmail +.SH "INTRODUCTION" +You will notice some differences +when the system switches from +.B sendmail +to +.BR qmail . +.TP 5 +1. +.B qmail-local +sends incoming mail to +.B ~\fIyou\fB/Mailbox +by default, +not +.BR /usr/spool/mail/\fIyou\fB . +Your system administrator has changed your +.B MAIL +environment variable so that your mail reader looks for +.BR ~\fIyou\fB/Mailbox . +.B \fR(\fB/usr/spool/mail +is a massive security problem.) +.TP 5 +2. +.B qmail-local +pays no attention to +.BR .forward . +It has a much better mechanism, +.BR .qmail , +so that you can handle not only forwarding +but even your own mailing lists. +See below for more details. +.TP 5 +3. +.B qmail-local +pays no attention to +.BR /etc/aliases . +Your system administrator +can use the +.B .qmail +mechanism instead. +See below. +.TP 5 +4. +.B qmail +does not support the +.B \e\fIyou\fB +mechanism +for ignoring aliases. +The +.B .qmail +mechanism is much more flexible; +see below. +.TP 5 +5. +.B qmail-inject +has a completely different philosophy from +.B sendmail +on interpreting non-fully-qualified host names. +It uses fixed rules, not DNS. +Some examples at UIC: + +.EX + russet -> russet.math.uic.edu +.br + newton -> newton.math.uic.edu +.br + ut.ee -> ut.ee (a host in Estonia) +.br + ut.ee+ -> ut.ee.uic.edu +.br + uicvm+ -> uicvm.uic.edu +.EE + +Here the +.I default domain name +(for hosts without dots) +is +.B math.uic.edu\fP, +and the +.I plus domain name +is +.B uic.edu\fP. +.TP 5 +6. +Unlike +.BR sendmail , +.B qmail-inject +doesn't replace host names with canonical names. +Example: +.B qmail-inject +won't change +.B postmaster@ftp.cs.berkeley.edu +in your header to +.BR postmaster@kohler.cs.berkeley.edu . +.TP 5 +7. +.B qmail-local +adds a new field, +.BR Delivered-To , +before every delivery. +It uses the contents of +.B Delivered-To +to prevent mail forwarding loops. +.TP 5 +8. +If you send a message with only +.B Bcc +recipients, +.B qmail-inject +will add +.B Cc: recipient list not shown:;\fR, +rather than +.BR sendmail 's +privacy-invading +.B Apparently-To +header field. +.SH "QMAIL MAILING LISTS" +.B sendmail +deals with aliases, forwarding, and mailing lists +at the very heart of the mail system. + +.B qmail +takes a radically different approach. +It gives you the power to set up your own mailing lists without +pestering your system administrator. + +Under +.BR qmail , +you are in charge of all addresses of the form +.B \fIyou\fBBREAK\fIanything\fR. +The delivery of +.B \fIyou\fBBREAK\fIanything +is controlled by +.B ~\fIyou\fB/.qmail-\fIanything\fR, +a file in your home directory. + +For example, if you want to set up a +bug-of-the-month-club mailing list, +you can put a list of addresses into +.BR ~\fIyou\fB/.qmail-botmc . +Any mail to +.B \fIyou\fBBREAKbotmc +will be forwarded to all of those addresses. +Mail directly to +.B \fIyou\fB +is controlled by +.BR ~\fIyou\fB/.qmail . +You can even set up a catch-all, +.BR ~\fIyou\fB/.qmail-default , +to handle unknown +.B \fIyou\fBBREAK +addresses. + +Your +.B .qmail +files, like your old +.BR .forward , +may list files, +forwarding addresses, +or other programs to run. +(But beware that the syntax is a bit different; +see +.B dot-qmail(5) +for more details.) +.B qmail-local +automatically +detects forwarding loops the instant they occur, +even if they happen indirectly through other hosts. + +As a helpful special case, if a +.B .qmail +file is empty, it refers to +.BR ~\fIyou\fB/Mailbox . +For example, if you touch +.BR ~\fIyou\fB/.qmail-direct , +mail for +.B \fIyou\fBBREAKdirect +will act like +.B \e\fIyou\fB +did under +.BR sendmail . + +Addresses that don't contain a username are handled by the +.B alias +user. +For example, your system administrator has set up +.B ~alias/.qmail-postmaster +to handle mail for +.BR Postmaster . +(Note to administrators: +.B ~alias +doesn't apply to addresses that start with a user name, +with certain exceptions.) +.SH "SEE ALSO" +addresses(5), +dot-qmail(5), +envelopes(5), +qmail-header(8), +qmail-inject(8) diff --git a/qmail-upq.sh b/qmail-upq.sh @@ -0,0 +1,14 @@ +cd QMAIL +cd queue +for dir in mess info local remote +do + ( cd $dir; find . -type f -print ) | ( + cd $dir + while read path + do + id=`basename "$path"` + sub=`expr "$id" % SPLIT` + mv "$path" "$sub"/"$id" + done + ) +done diff --git a/qmail-users.9 b/qmail-users.9 @@ -0,0 +1,113 @@ +.TH qmail-users 5 +.SH NAME +qmail-users \- assign mail addresses to users +.SH OVERVIEW +The file +.B QMAILHOME/users/assign +assigns addresses to users. For example, + +.EX + =joe.shmoe:joe:503:78:/home/joe::: +.EE + +says that mail for +.B joe.shmoe +should be delivered to user +.BR joe , +with uid 503 and gid 78, +as specified by +.BR /home/joe/.qmail . + +Assignments fed to +.B qmail-newu +will be used by +.B qmail-lspawn +to control +.BR qmail-local 's +deliveries. +See +.BR qmail-newu (8). +A change to +.B QMAILHOME/users/assign +will have no effect until +.B qmail-newu +is run. +.SH STRUCTURE +.B QMAILHOME/users/assign +is a series of assignments, one per line. +It ends with a line containing a single dot. +Lines must not contain NUL. +.SH "SIMPLE ASSIGNMENTS" +A simple assignment is a line of the form + +.EX + =local:user:uid:gid:homedir:dash:ext: +.EE + +Here +.I local +is an address; +.IR user , +.IR uid , +and +.I gid +are the account name, uid, and gid +of the user in charge of +.IR local ; +and messages to +.I local +will be controlled by +.IR homedir\fB/.qmail\fIdashext . + +If there are several assignments for the same +.I local +address, +.B qmail-lspawn +will use the first one. + +.I local +is interpreted without regard to case. +.SH "WILDCARD ASSIGNMENTS" +A wildcard assignment is a line of the form + +.EX + +loc:user:uid:gid:homedir:dash:pre: +.EE + +This assignment applies to any address beginning with +.IR loc , +including +.I loc +itself. +It means the same as + +.EX + =locext:user:uid:gid:homedir:dash:preext: +.EE + +for every string +.IR ext . + +A more specific wildcard assignment overrides a less specific +assignment, and a simple assignment overrides any wildcard assignment. +For example: + +.EX + +:alias:7790:2108:QMAILHOME/alias:-:: + +joe-:joe:507:100:/home/joe:-:: + =joe:joe:507:100:/home/joe::: +.EE + +The address +.B joe +is handled by the third line; +the address +.B joe-direct +is handled by the second line; +the address +.B bill +is handled by the first line. +.SH "SEE ALSO" +qmail-pw2u(8), +qmail-newu(8), +qmail-lspawn(8) diff --git a/qmail.7 b/qmail.7 @@ -0,0 +1,67 @@ +.TH qmail 7 +.SH "NAME" +qmail \- overview of qmail documentation +.SH "INTRODUCTION" +.B qmail +is a secure, reliable, efficient, simple message transfer agent. + +Users who want to control incoming messages +should read +.BR dot-qmail (5). +Available commands for the +.B .qmail +file include +.BR qbiff (1), +.BR qlist (1), +.BR qreceipt (1), +.BR forward (1), +and (for advanced users) +.BR condredirect (1). +Other helpful commands include +.BR maildirmake (1), +.BR maildir2mbox (1), +and +.BR maildirwatch (1). + +System administrators who want to control the entire +.B qmail +system should start with +.BR qmail-control (5) +and +.BR qmail-start (8). +There are three queue-monitoring tools: +.BR qmail-qread (8), +.BR qmail-qstat (8), +and +.BR qmail-tcpto (8). +Incoming SMTP connections are handled by +.BR qmail-smtpd (8). + +.B qmail +offers two command-line message-sending interfaces: +.BR qmail-inject (8) +and +.BR mailsubj (1). +For background information on Internet mail messages, +see +.BR addresses (5), +.BR envelopes (5), +.BR qmail-header (5), +and +.BR forgeries (7). + +Miscellaneous documentation includes +.BR qmail-upgrade (7), +.BR qmail-limits (7), +and +.BR qmail-pop3d (8). + +This documentation describes version +1.01 +of +.BR qmail . +See +.B http://pobox.com/~djb/qmail.html +for other +.BR qmail -related +software. diff --git a/qmail.c b/qmail.c @@ -0,0 +1,103 @@ +#include "substdio.h" +#include "readwrite.h" +#include "wait.h" +#include "exit.h" +#include "fork.h" +#include "fd.h" +#include "qmail.h" +#include "auto_qmail.h" + +static char *binqqargs[2] = { "bin/qmail-queue", 0 } ; + +int qmail_open(qq) +struct qmail *qq; +{ + int pim[2]; + int pie[2]; + + if (pipe(pim) == -1) return -1; + if (pipe(pie) == -1) { close(pim[0]); close(pim[1]); return -1; } + + switch(qq->pid = vfork()) { + case -1: + close(pim[0]); close(pim[1]); + close(pie[0]); close(pie[1]); + return -1; + case 0: + close(pim[1]); + close(pie[1]); + if (fd_move(0,pim[0]) == -1) _exit(120); + if (fd_move(1,pie[0]) == -1) _exit(120); + if (chdir(auto_qmail) == -1) _exit(120); + execv(*binqqargs,binqqargs); + _exit(120); + } + + qq->fdm = pim[1]; close(pim[0]); + qq->fde = pie[1]; close(pie[0]); + substdio_fdbuf(&qq->ss,write,qq->fdm,qq->buf,sizeof(qq->buf)); + qq->flagerr = 0; + return 0; +} + +unsigned long qmail_qp(qq) struct qmail *qq; +{ + return qq->pid; +} + +void qmail_fail(qq) struct qmail *qq; +{ + qq->flagerr = 1; +} + +void qmail_put(qq,s,len) struct qmail *qq; char *s; int len; +{ + if (!qq->flagerr) if (substdio_put(&qq->ss,s,len) == -1) qq->flagerr = 1; +} + +void qmail_puts(qq,s) struct qmail *qq; char *s; +{ + if (!qq->flagerr) if (substdio_puts(&qq->ss,s) == -1) qq->flagerr = 1; +} + +void qmail_from(qq,s) struct qmail *qq; char *s; +{ + if (substdio_flush(&qq->ss) == -1) qq->flagerr = 1; + close(qq->fdm); + substdio_fdbuf(&qq->ss,write,qq->fde,qq->buf,sizeof(qq->buf)); + qmail_put(qq,"F",1); + qmail_puts(qq,s); + qmail_put(qq,"",1); +} + +void qmail_to(qq,s) struct qmail *qq; char *s; +{ + qmail_put(qq,"T",1); + qmail_puts(qq,s); + qmail_put(qq,"",1); +} + +int qmail_close(qq) +struct qmail *qq; +{ + int wstat; + + qmail_put(qq,"",1); + if (!qq->flagerr) if (substdio_flush(&qq->ss) == -1) qq->flagerr = 1; + close(qq->fde); + + if (wait_pid(&wstat,qq->pid) != qq->pid) return QMAIL_WAITPID; + if (wait_crashed(wstat)) return QMAIL_CRASHED; + switch(wait_exitcode(wstat)) { + case 0: if (qq->flagerr) return QMAIL_BUG; return 0; + case 112: return QMAIL_USAGE; + case 115: return QMAIL_TOOLONG; + case 103: case 104: case 105: case 106: case 108: return QMAIL_SYS; + case 121: return QMAIL_READ; + case 122: return QMAIL_WRITE; + case 123: return QMAIL_NOMEM; + case 124: return QMAIL_TIMEOUT; + case 120: return QMAIL_EXECSOFT; + default: /* 101 or 102 */ return QMAIL_BUG; + } +} diff --git a/qmail.h b/qmail.h @@ -0,0 +1,36 @@ +#ifndef QMAIL_H +#define QMAIL_H + +#include "substdio.h" + +struct qmail { + int flagerr; + unsigned long pid; + int fdm; + int fde; + substdio ss; + char buf[1024]; +} ; + +extern int qmail_open(); +extern void qmail_put(); +extern void qmail_puts(); +extern void qmail_from(); +extern void qmail_to(); +extern void qmail_fail(); +extern int qmail_close(); +extern unsigned long qmail_qp(); + +#define QMAIL_WAITPID -2 +#define QMAIL_CRASHED -3 +#define QMAIL_USAGE -4 +#define QMAIL_BUG -5 +#define QMAIL_SYS -6 +#define QMAIL_READ -7 +#define QMAIL_WRITE -8 +#define QMAIL_NOMEM -9 +#define QMAIL_EXECSOFT -11 +#define QMAIL_TIMEOUT -13 +#define QMAIL_TOOLONG -14 + +#endif diff --git a/qreceipt.1 b/qreceipt.1 @@ -0,0 +1,33 @@ +.TH qreceipt 1 +.SH NAME +qreceipt \- respond to delivery notice requests +.SH SYNOPSIS +in +.BR .qmail : +.B |qreceipt +.I youraddress +.SH DESCRIPTION +When a mail message arrives with +.I youraddress +listed in a +.B Notice-Requested-Upon-Delivery-To +header field, +.B qreceipt +sends a success notice back to the envelope sender. + +.B WARNING: +If you create a +.B .qmail +file to enable +.BR qreceipt , +make sure to also add a line specifying delivery to your normal mailbox. +For example: + +.EX + /home/joe/Mailbox +.br + |qreceipt joe@nowhere.mil +.EE +.SH "SEE ALSO" +dot-qmail(5), +envelopes(5) diff --git a/qreceipt.c b/qreceipt.c @@ -0,0 +1,131 @@ +#include "sig.h" +#include "env.h" +#include "substdio.h" +#include "stralloc.h" +#include "subfd.h" +#include "getln.h" +#include "alloc.h" +#include "str.h" +#include "hfield.h" +#include "token822.h" +#include "error.h" +#include "gen_alloc.h" +#include "gen_allocdefs.h" +#include "headerbody.h" +#include "exit.h" +#include "open.h" +#include "quote.h" +#include "qmail.h" + +void die_noreceipt() { _exit(0); } +void die() { _exit(100); } +void die_temp() { _exit(111); } +void die_nomem() { + substdio_putsflush(subfderr,"qreceipt: fatal: out of memory\n"); die_temp(); } +void die_fork() { + substdio_putsflush(subfderr,"qreceipt: fatal: unable to fork\n"); die_temp(); } +void die_qqperm() { + substdio_putsflush(subfderr,"qreceipt: fatal: permanent qmail-queue error\n"); die(); } +void die_qqtemp() { + substdio_putsflush(subfderr,"qreceipt: fatal: temporary qmail-queue error\n"); die_temp(); } +void die_usage() { + substdio_putsflush(subfderr, + "qreceipt: usage: qreceipt deliveryaddress\n"); die(); } +void die_read() { + if (errno == error_nomem) die_nomem(); + substdio_putsflush(subfderr,"qreceipt: fatal: read error\n"); die_temp(); } +void doordie(sa,r) stralloc *sa; int r; { + if (r == 1) return; if (r == -1) die_nomem(); + substdio_putsflush(subfderr,"qreceipt: fatal: unable to parse this: "); + substdio_putflush(subfderr,sa->s,sa->len); die(); } + +char *target; + +int flagreceipt = 0; + +char *returnpath; +stralloc messageid = {0}; +stralloc sanotice = {0}; + +int rwnotice(addr) token822_alloc *addr; { token822_reverse(addr); + if (token822_unquote(&sanotice,addr) != 1) die_nomem(); + if (sanotice.len == str_len(target)) + if (!str_diffn(sanotice.s,target,sanotice.len)) + flagreceipt = 1; + token822_reverse(addr); return 1; } + +struct qmail qqt; + +stralloc quoted = {0}; + +void finishheader() +{ + if (!flagreceipt) die_noreceipt(); + if (str_equal(returnpath,"")) die_noreceipt(); + if (str_equal(returnpath,"#@[]")) die_noreceipt(); + + if (!quote2(&quoted,returnpath)) die_nomem(); + + if (qmail_open(&qqt) == -1) die_fork(); + + qmail_puts(&qqt,"From: DELIVERY NOTICE SYSTEM <"); + qmail_put(&qqt,quoted.s,quoted.len); + qmail_puts(&qqt,">\n"); + qmail_puts(&qqt,"To: <"); + qmail_put(&qqt,quoted.s,quoted.len); + qmail_puts(&qqt,">\n"); + qmail_puts(&qqt,"Subject: success notice\n\ +\n\ +Hi! This is the qreceipt program. Your message was delivered to the\n\ +following address: "); + qmail_puts(&qqt,target); + qmail_puts(&qqt,". Thanks for asking.\n"); + if (messageid.s) + { + qmail_puts(&qqt,"Your "); + qmail_put(&qqt,messageid.s,messageid.len); + } + + qmail_from(&qqt,""); + qmail_to(&qqt,returnpath); + + switch(qmail_close(&qqt)) + { + case 0: break; + case QMAIL_TOOLONG: die_qqperm(); + default: die_qqtemp(); + } +} + +stralloc hfbuf = {0}; +token822_alloc hfin = {0}; +token822_alloc hfrewrite = {0}; +token822_alloc hfaddr = {0}; + +void doheaderfield(h) +stralloc *h; +{ + switch(hfield_known(h->s,h->len)) + { + case H_MESSAGEID: + if (!stralloc_copy(&messageid,h)) die_nomem(); + break; + case H_NOTICEREQUESTEDUPONDELIVERYTO: + doordie(h,token822_parse(&hfin,h,&hfbuf)); + doordie(h,token822_addrlist(&hfrewrite,&hfaddr,&hfin,rwnotice)); + break; + } +} + +void dobody(h) stralloc *h; { ; } + +void main(argc,argv) +int argc; +char **argv; +{ + sig_pipeignore(); + if (!(target = argv[1])) die_usage(); + if (!(returnpath = env_get("SENDER"))) die_usage(); + if (headerbody(subfdin,doheaderfield,finishheader,dobody) == -1) die_read(); + die_noreceipt(); +} diff --git a/qsmhook.c b/qsmhook.c @@ -0,0 +1,137 @@ +#include "fd.h" +#include "stralloc.h" +#include "readwrite.h" +#include "sgetopt.h" +#include "wait.h" +#include "env.h" +#include "byte.h" +#include "str.h" +#include "alloc.h" +#include "exit.h" +#include "fork.h" +#include "case.h" +#include "subfd.h" +#include "error.h" +#include "substdio.h" +#include "sig.h" + +void die(e,s) int e; char *s; { substdio_putsflush(subfderr,s); _exit(e); } +void die_usage() { die(100,"qsmhook: fatal: incorrect usage\n"); } +void die_temp() { die(111,"qsmhook: fatal: temporary problem\n"); } +void die_read() { die(111,"qsmhook: fatal: unable to read message\n"); } +void die_badcmd() { die(100,"qsmhook: fatal: command not found\n"); } + +int flagrpline = 0; char *rpline; +int flagufline = 1; char *ufline; +int flagdtline = 0; char *dtline; +char *host; +char *sender; +char *recip; + +stralloc newarg = {0}; + +substdio ssout; +char outbuf[SUBSTDIO_OUTSIZE]; +substdio ssin; +char inbuf[SUBSTDIO_INSIZE]; + +void main(argc,argv) +int argc; +char **argv; +{ + int pid; + int wstat; + int pi[2]; + int opt; + char **arg; + char *x; + int i; + int flagesc; + + sig_pipeignore(); + + if (!(dtline = env_get("DTLINE"))) die_usage(); + if (!(rpline = env_get("RPLINE"))) die_usage(); + if (!(ufline = env_get("UFLINE"))) die_usage(); + if (!(recip = env_get("LOCAL"))) die_usage(); + if (!(host = env_get("HOST"))) die_usage(); + if (!(sender = env_get("SENDER"))) die_usage(); + + while ((opt = getopt(argc,argv,"DFlMmnPsx:")) != opteof) + switch(opt) + { + case 'D': case 'F': case 'M': break; /* be serious */ + case 'l': flagdtline = 1; break; /* also return-receipt-to? blech */ + case 'm': break; /* we only handle one recipient anyway */ + case 'n': flagufline = 0; break; + case 's': break; /* could call quote() otherwise, i suppose... */ + case 'P': flagrpline = 1; break; + case 'x': + if (case_starts(recip,optarg)) + recip += str_len(optarg); + break; + default: + _exit(100); + } + argc -= optind; + argv += optind; + + if (!*argv) die_usage(); + + for (arg = argv;x = *arg;++arg) + { + if (!stralloc_copys(&newarg,"")) die_temp(); + flagesc = 0; + for (i = 0;x[i];++i) + if (flagesc) + { + switch(x[i]) + { + case '%': if (!stralloc_cats(&newarg,"%")) die_temp(); break; + case 'g': if (!stralloc_cats(&newarg,sender)) die_temp(); break; + case 'h': if (!stralloc_cats(&newarg,host)) die_temp(); break; + case 'u': if (!stralloc_cats(&newarg,recip)) die_temp(); break; + } + flagesc = 0; + } + else + if (x[i] == '%') + flagesc = 1; + else + if (!stralloc_append(&newarg,&x[i])) die_temp(); + if (!stralloc_0(&newarg)) die_temp(); + i = str_len(newarg.s) + 1; + if (!(x = alloc(i))) die_temp(); + byte_copy(x,i,newarg.s); + *arg = x; + } + + if (pipe(pi) == -1) die_temp(); + + switch(pid = fork()) + { + case -1: + die_temp(); + case 0: + close(pi[1]); + if (fd_move(0,pi[0]) == -1) die_temp(); + sig_pipedefault(); + execvp(*argv,argv); + if (error_temp(errno)) die_temp(); + die_badcmd(); + } + close(pi[0]); + + substdio_fdbuf(&ssout,write,pi[1],outbuf,sizeof(outbuf)); + substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf)); + if (flagufline) substdio_bputs(&ssout,ufline); + if (flagrpline) substdio_bputs(&ssout,rpline); + if (flagdtline) substdio_bputs(&ssout,dtline); + if (substdio_copy(&ssout,&ssin) == -2) die_read(); + substdio_flush(&ssout); + close(pi[1]); + + if (wait_pid(&wstat,pid) == -1) die_temp(); + if (wait_crashed(wstat)) die_temp(); + _exit(wait_exitcode(wstat)); +} diff --git a/qsutil.c b/qsutil.c @@ -0,0 +1,46 @@ +#include "stralloc.h" +#include "readwrite.h" +#include "substdio.h" +#include "qsutil.h" + +static stralloc foo = {0}; + +static char errbuf[1]; +static struct substdio sserr = SUBSTDIO_FDBUF(write,0,errbuf,1); + +void logsa(sa) stralloc *sa; { + substdio_putflush(&sserr,sa->s,sa->len); } +void log1(s1) char *s1; { + substdio_putsflush(&sserr,s1); } +void log2(s1,s2) char *s1; char *s2; { + substdio_putsflush(&sserr,s1); + substdio_putsflush(&sserr,s2); } +void log3(s1,s2,s3) char *s1; char *s2; char *s3; { + substdio_putsflush(&sserr,s1); + substdio_putsflush(&sserr,s2); + substdio_putsflush(&sserr,s3); } +void nomem() { log1("alert: out of memory, sleeping...\n"); sleep(10); } + +void pausedir(dir) char *dir; +{ log3("alert: unable to opendir ",dir,", sleeping...\n"); sleep(10); } + +static int issafe(ch) char ch; +{ + if (ch == '%') return 0; /* general principle: allman's code is crap */ + if (ch < 33) return 0; + if (ch > 126) return 0; + return 1; +} + +void logsafe(s) char *s; +{ + int i; + while (!stralloc_copys(&foo,s)) nomem(); + for (i = 0;i < foo.len;++i) + if (foo.s[i] == '\n') + foo.s[i] = '/'; + else + if (!issafe(foo.s[i])) + foo.s[i] = '_'; + logsa(&foo); +} diff --git a/qsutil.h b/qsutil.h @@ -0,0 +1,12 @@ +#ifndef QSUTIL_H +#define QSUTIL_H + +extern void log1(); +extern void log2(); +extern void log3(); +extern void logsa(); +extern void nomem(); +extern void pausedir(); +extern void logsafe(); + +#endif diff --git a/quote.c b/quote.c @@ -0,0 +1,82 @@ +#include "stralloc.h" +#include "str.h" +#include "quote.h" + +/* +quote() encodes a box as per rfc 821 and rfc 822, +while trying to do as little quoting as possible. +no, 821 and 822 don't have the same encoding. they're not even close. +no special encoding here for bytes above 127. +*/ + +static char ok[128] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +,0,7,0,7,7,7,7,7,0,0,7,7,0,7,7,7 ,7,7,7,7,7,7,7,7,7,7,0,0,0,7,0,7 +,0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 ,7,7,7,7,7,7,7,7,7,7,7,0,0,0,7,7 +,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 ,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,0 +} ; + +static int doit(saout,sain) +stralloc *saout; +stralloc *sain; +{ + char ch; + int i; + int j; + + if (!stralloc_ready(saout,sain->len * 2 + 2)) return 0; + j = 0; + saout->s[j++] = '"'; + for (i = 0;i < sain->len;++i) + { + ch = sain->s[i]; + if ((ch == '\r') || (ch == '\n') || (ch == '"') || (ch == '\\')) + saout->s[j++] = '\\'; + saout->s[j++] = ch; + } + saout->s[j++] = '"'; + saout->len = j; + return 1; +} + +int quote_need(s,n) +char *s; +unsigned int n; +{ + unsigned char uch; + int i; + if (!n) return 0; + for (i = 0;i < n;++i) + { + uch = s[i]; + if (uch >= 128) return 1; + if (!ok[uch]) return 1; + } + if (s[0] == '.') return 1; + if (s[n - 1] == '.') return 1; + for (i = 0;i < n - 1;++i) if (s[i] == '.') if (s[i + 1] == '.') return 1; + return 0; +} + +int quote(saout,sain) +stralloc *saout; +stralloc *sain; +{ + if (quote_need(sain->s,sain->len)) return doit(saout,sain); + return stralloc_copy(saout,sain); +} + +static stralloc foo = {0}; + +int quote2(sa,s) +stralloc *sa; +char *s; +{ + int j; + j = str_rchr(s,'@'); + if (!stralloc_copys(&foo,s)) return 0; + if (!s[j]) return quote(sa,&foo); + foo.len = j; + if (!quote(sa,&foo)) return 0; + return stralloc_cats(sa,s + j); +} diff --git a/quote.h b/quote.h @@ -0,0 +1,8 @@ +#ifndef QUOTE_H +#define QUOTE_H + +extern int quote_need(); +extern int quote(); +extern int quote2(); + +#endif diff --git a/readsubdir.c b/readsubdir.c @@ -0,0 +1,49 @@ +#include "readsubdir.h" +#include "fmt.h" +#include "scan.h" +#include "str.h" +#include "auto_split.h" + +void readsubdir_init(rs,name,pause) +readsubdir *rs; +char *name; +void (*pause)(); +{ + rs->name = name; + rs->pause = pause; + rs->dir = 0; + rs->pos = 0; +} + +static char namepos[FMT_ULONG + 4 + READSUBDIR_NAMELEN]; + +int readsubdir_next(rs,id) +readsubdir *rs; +unsigned long *id; +{ + direntry *d; + unsigned int len; + + if (!rs->dir) + { + if (rs->pos >= auto_split) return 0; + if (str_len(rs->name) > READSUBDIR_NAMELEN) { rs->pos++; return -1; } + len = 0; + len += fmt_str(namepos + len,rs->name); + namepos[len++] = '/'; + len += fmt_ulong(namepos + len,(unsigned long) rs->pos); + namepos[len] = 0; + while (!(rs->dir = opendir(namepos))) rs->pause(namepos); + rs->pos++; + return -1; + } + + d = readdir(rs->dir); + if (!d) { closedir(rs->dir); rs->dir = 0; return -1; } + + if (str_equal(d->d_name,".")) return -1; + if (str_equal(d->d_name,"..")) return -1; + len = scan_ulong(d->d_name,id); + if (!len || d->d_name[len]) return -2; + return 1; +} diff --git a/readsubdir.h b/readsubdir.h @@ -0,0 +1,20 @@ +#ifndef READSUBDIR_H +#define READSUBDIR_H + +#include "direntry.h" + +typedef struct readsubdir + { + DIR *dir; + int pos; + char *name; + void (*pause)(); + } +readsubdir; + +extern void readsubdir_init(); +extern int readsubdir_next(); + +#define READSUBDIR_NAMELEN 10 + +#endif diff --git a/readwrite.h b/readwrite.h @@ -0,0 +1,7 @@ +#ifndef READWRITE_H +#define READWRITE_H + +extern int read(); +extern int write(); + +#endif diff --git a/received.c b/received.c @@ -0,0 +1,71 @@ +#include "fmt.h" +#include "qmail.h" +#include "now.h" +#include "datetime.h" +#include "date822fmt.h" +#include "received.h" + +static int issafe(ch) char ch; +{ + if (ch == '.') return 1; + if (ch == '@') return 1; + if (ch == '%') return 1; + if (ch == '+') return 1; + if (ch == '/') return 1; + if (ch == '=') return 1; + if (ch == ':') return 1; + if (ch == '-') return 1; + if ((ch >= 'a') && (ch <= 'z')) return 1; + if ((ch >= 'A') && (ch <= 'Z')) return 1; + if ((ch >= '0') && (ch <= '9')) return 1; + return 0; +} + +void safeput(qqt,s) +struct qmail *qqt; +char *s; +{ + char ch; + while (ch = *s++) { + if (!issafe(ch)) ch = '?'; + qmail_put(qqt,&ch,1); + } +} + +static char buf[DATE822FMT]; + +/* "Received: from relay1.uu.net (HELO uunet.uu.net) (7@192.48.96.5)\n" */ +/* " by silverton.berkeley.edu with SMTP; 26 Sep 1995 04:46:54 -0000\n" */ + +void received(qqt,protocol,local,remoteip,remotehost,remoteinfo,helo) +struct qmail *qqt; +char *protocol; +char *local; +char *remoteip; +char *remotehost; +char *remoteinfo; +char *helo; +{ + struct datetime dt; + + qmail_puts(qqt,"Received: from "); + safeput(qqt,remotehost); + if (helo) { + qmail_puts(qqt," (HELO "); + safeput(qqt,helo); + qmail_puts(qqt,")"); + } + qmail_puts(qqt," ("); + if (remoteinfo) { + safeput(qqt,remoteinfo); + qmail_puts(qqt,"@"); + } + safeput(qqt,remoteip); + qmail_puts(qqt,")\n by "); + safeput(qqt,local); + qmail_puts(qqt," with "); + qmail_puts(qqt,protocol); + qmail_puts(qqt,"; "); + datetime_tai(&dt,now()); + qmail_put(qqt,buf,date822fmt(buf,&dt)); +} diff --git a/received.h b/received.h @@ -0,0 +1,6 @@ +#ifndef RECEIVED_H +#define RECEIVED_H + +extern void received(); + +#endif diff --git a/remoteinfo.c b/remoteinfo.c @@ -0,0 +1,65 @@ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <fcntl.h> +#include "byte.h" +#include "substdio.h" +#include "ip.h" +#include "fmt.h" +#include "timeoutconn.h" +#include "timeoutread.h" +#include "timeoutwrite.h" +#include "remoteinfo.h" + +static char line[999]; + +char *remoteinfo_get(ipr,rp,ipl,lp,timeout) +struct ip_address *ipr; +unsigned long rp; +struct ip_address *ipl; +unsigned long lp; +int timeout; +{ + char *x; + int s; + struct sockaddr_in sin; + substdio ss; + char buf[32]; + unsigned int len; + int numcolons; + char ch; + + s = socket(AF_INET,SOCK_STREAM,0); + if (s == -1) return 0; + + byte_zero(&sin,sizeof(sin)); + sin.sin_family = AF_INET; + byte_copy(&sin.sin_addr,4,ipl); + sin.sin_port = 0; + if (bind(s,(struct sockaddr *) &sin,sizeof(sin)) == -1) { close(s); return 0; } + if (timeoutconn(s,ipr,113,timeout) == -1) { close(s); return 0; } + fcntl(s,F_SETFL,fcntl(s,F_GETFL,0) & ~O_NDELAY); + + len = 0; + len += fmt_ulong(line + len,rp); + len += fmt_str(line + len," , "); + len += fmt_ulong(line + len,lp); + len += fmt_str(line + len,"\r\n"); + + substdio_fdbuf(&ss,timeoutwrite,TIMEOUTWRITE(timeout,s),buf,sizeof(buf)); + if (substdio_putflush(&ss,line,len) == -1) { close(s); return 0; } + + substdio_fdbuf(&ss,timeoutread,TIMEOUTREAD(timeout,s),buf,sizeof(buf)); + x = line; + numcolons = 0; + for (;;) { + if (substdio_get(&ss,&ch,1) != 1) { close(s); return 0; } + if ((ch == ' ') || (ch == '\t') || (ch == '\r')) continue; + if (ch == '\n') break; + if (numcolons < 3) { if (ch == ':') ++numcolons; } + else { *x++ = ch; if (x == line + sizeof(line) - 1) break; } + } + *x = 0; + close(s); + return line; +} diff --git a/remoteinfo.h b/remoteinfo.h @@ -0,0 +1,6 @@ +#ifndef REMOTEINFO_H +#define REMOTEINFO_H + +extern char *remoteinfo_get(); + +#endif diff --git a/scan.h b/scan.h @@ -0,0 +1,27 @@ +#ifndef SCAN_H +#define SCAN_H + +extern unsigned int scan_uint(); +extern unsigned int scan_xint(); +extern unsigned int scan_nbbint(); +extern unsigned int scan_ushort(); +extern unsigned int scan_xshort(); +extern unsigned int scan_nbbshort(); +extern unsigned int scan_ulong(); +extern unsigned int scan_xlong(); +extern unsigned int scan_nbblong(); + +extern unsigned int scan_plusminus(); +extern unsigned int scan_0x(); + +extern unsigned int scan_whitenskip(); +extern unsigned int scan_nonwhitenskip(); +extern unsigned int scan_charsetnskip(); +extern unsigned int scan_noncharsetnskip(); + +extern unsigned int scan_strncmp(); +extern unsigned int scan_memcmp(); + +extern unsigned int scan_long(); + +#endif diff --git a/scan_8long.c b/scan_8long.c @@ -0,0 +1,11 @@ +#include "scan.h" + +unsigned int scan_8long(s,u) register char *s; register unsigned long *u; +{ + register unsigned int pos; register unsigned long result; + register unsigned long c; + pos = 0; result = 0; + while ((c = (unsigned long) (unsigned char) (s[pos] - '0')) < 8) + { result = result * 8 + c; ++pos; } + *u = result; return pos; +} diff --git a/scan_nbblong.c b/scan_nbblong.c @@ -0,0 +1,33 @@ +#include "scan.h" + +unsigned int scan_nbblong(s,n,base,bext,u) +char *s; unsigned int n; unsigned int base; unsigned int bext; unsigned long *u; +/* Note that n == 0 means scan forever. Hopefully this is a good choice. */ +{ + unsigned int pos; unsigned long result; unsigned long c; + pos = 0; result = 0; + while (((c = (unsigned long) (unsigned char) (s[pos] - '0')) < base) + ||(((c = (unsigned long) (unsigned char) (s[pos] - 'a')) < bext) + &&(c = c + base)) + ||(((c = (unsigned long) (unsigned char) (s[pos] - 'A')) < bext) + &&(c = c + base)) + ) /* this gets the job done */ + { result = result * (base + bext) + c; ++pos; if (pos == n) break; } + *u = result; return pos; +} + +unsigned int scan_nbbint(s,n,base,bext,u) +char *s; unsigned int n; unsigned int base; unsigned int bext; unsigned int *u; +{ + unsigned int pos; unsigned long result; + pos = scan_nbblong(s,n,base,bext,&result); + *u = result; return pos; +} + +unsigned int scan_nbbshort(s,n,base,bext,u) +char *s; unsigned int n; unsigned int base; unsigned int bext; unsigned short *u; +{ + unsigned int pos; unsigned long result; + pos = scan_nbblong(s,n,base,bext,&result); + *u = result; return pos; +} diff --git a/scan_ulong.c b/scan_ulong.c @@ -0,0 +1,11 @@ +#include "scan.h" + +unsigned int scan_ulong(s,u) register char *s; register unsigned long *u; +{ + register unsigned int pos; register unsigned long result; + register unsigned long c; + pos = 0; result = 0; + while ((c = (unsigned long) (unsigned char) (s[pos] - '0')) < 10) + { result = result * 10 + c; ++pos; } + *u = result; return pos; +} diff --git a/seek.h b/seek.h @@ -0,0 +1,15 @@ +#ifndef SEEK_H +#define SEEK_H + +typedef unsigned long seek_pos; + +extern seek_pos seek_cur(); + +extern int seek_set(); +extern int seek_end(); + +extern int seek_trunc(); + +#define seek_begin(fd) (seek_set((fd),(seek_pos) 0)) + +#endif diff --git a/seek_cur.c b/seek_cur.c @@ -0,0 +1,7 @@ +#include <sys/types.h> +#include "seek.h" + +#define CUR 1 /* sigh */ + +seek_pos seek_cur(fd) int fd; +{ return lseek(fd,(off_t) 0,CUR); } diff --git a/seek_end.c b/seek_end.c @@ -0,0 +1,7 @@ +#include <sys/types.h> +#include "seek.h" + +#define END 2 /* sigh */ + +int seek_end(fd) int fd; +{ if (lseek(fd,(off_t) 0,END) == -1) return -1; return 0; } diff --git a/seek_set.c b/seek_set.c @@ -0,0 +1,7 @@ +#include <sys/types.h> +#include "seek.h" + +#define SET 0 /* sigh */ + +int seek_set(fd,pos) int fd; seek_pos pos; +{ if (lseek(fd,(off_t) pos,SET) == -1) return -1; return 0; } diff --git a/seek_trunc.c b/seek_trunc.c @@ -0,0 +1,5 @@ +#include <sys/types.h> +#include "seek.h" + +int seek_trunc(fd,pos) int fd; seek_pos pos; +{ return ftruncate(fd,(off_t) pos); } diff --git a/select.h1 b/select.h1 @@ -0,0 +1,8 @@ +#ifndef SELECT_H +#define SELECT_H + +#include <sys/types.h> +#include <sys/time.h> +extern int select(); + +#endif diff --git a/select.h2 b/select.h2 @@ -0,0 +1,9 @@ +#ifndef SELECT_H +#define SELECT_H + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/select.h> +extern int select(); + +#endif diff --git a/sendmail.c b/sendmail.c @@ -0,0 +1,98 @@ +#include "sgetopt.h" +#include "substdio.h" +#include "subfd.h" +#include "alloc.h" +#include "auto_qmail.h" +#include "exit.h" +#include "env.h" +#include "str.h" + +void nomem() +{ + substdio_putsflush(subfderr,"sendmail: fatal: out of memory\n"); + _exit(111); +} + +int flagh; +char *sender; + +void main(argc,argv) +int argc; +char **argv; +{ + int opt; + char **qiargv; + char **arg; + int i; + + if (chdir(auto_qmail) == -1) + { + substdio_putsflush(subfderr,"sendmail: fatal: unable to switch to qmail home directory\n"); + _exit(111); + } + + flagh = 0; + sender = 0; + while ((opt = getopt(argc,argv,"vimte:f:p:o:B:F:EJx")) != opteof) + switch(opt) + { + case 'B': break; + case 't': flagh = 1; break; + case 'f': sender = optarg; break; + case 'F': if (!env_put2("MAILNAME",optarg)) nomem(); break; + case 'p': break; /* could generate a Received line from optarg */ + case 'v': break; + case 'i': break; /* what an absurd concept */ + case 'x': break; /* SVR4 stupidity */ + case 'm': break; /* twisted-paper-path blindness, incompetent design */ + case 'e': break; /* qmail has only one error mode */ + case 'o': + switch(optarg[0]) + { + case 'd': break; /* qmail has only one delivery mode */ + case 'e': break; /* see 'e' above */ + case 'i': break; /* see 'i' above */ + case 'm': break; /* see 'm' above */ + } + break; + case 'E': case 'J': /* Sony NEWS-OS */ + while (argv[optind][optpos]) ++optpos; /* skip optional argument */ + break; + default: + _exit(100); + } + argc -= optind; + argv += optind; + + if (str_equal(optprogname,"mailq")) + { + substdio_putsflush(subfderr,"sendmail: fatal: please use qmail-qread instead\n"); + _exit(100); + } + + if (str_equal(optprogname,"newaliases")) + { + substdio_putsflush(subfderr,"sendmail: fatal: please use the qmsmac newaliases instead\n"); + _exit(100); + } + + qiargv = (char **) alloc((argc + 10) * sizeof(char *)); + if (!qiargv) nomem(); + + arg = qiargv; + *arg++ = "bin/qmail-inject"; + *arg++ = (flagh ? "-H" : "-a"); + if (sender) + { + *arg++ = "-f"; + *arg++ = sender; + } + *arg++ = "--"; + for (i = 0;i < argc;++i) *arg++ = argv[i]; + *arg = 0; + + execv(*qiargv,qiargv); + + substdio_putsflush(subfderr,"sendmail: fatal: unable to run qmail-inject\n"); + _exit(111); +} diff --git a/sgetopt.3 b/sgetopt.3 @@ -0,0 +1,28 @@ +.TH sgetopt 3 +.SH NAME +sgetopt \- get option character from command line +.SH SYNTAX +.B #include <sgetopt.h> +.SH DESCRIPTION +The +.B sgetopt +library is just like the +.B getopt +library, +except that it prints errors using +.B substdio +rather than +.BR stdio . + +See +.B getopt(3) +for interface details. +.SH VERSION +sgetopt version 1.9, 931201. +.SH AUTHOR +Placed into the public domain by Daniel J. Bernstein. +.SH "SEE ALSO" +getopt(3), +subgetopt(3), +subfd(3), +substdio(3) diff --git a/sgetopt.c b/sgetopt.c @@ -0,0 +1,54 @@ +/* sgetopt.c, sgetopt.h: (yet another) improved getopt clone, outer layer +D. J. Bernstein, djb@pobox.com. +Depends on subgetopt.h, substdio.h, subfd.h. +No system requirements. +19970208: Cleanups. +931201: Baseline. +No known patent problems. + +Documentation in sgetopt.3. +*/ + +#include "substdio.h" +#include "subfd.h" +#define SGETOPTNOSHORT +#include "sgetopt.h" +#define SUBGETOPTNOSHORT +#include "subgetopt.h" + +#define getopt sgetoptmine +#define optind subgetoptind +#define opterr sgetopterr +#define optproblem subgetoptproblem +#define optprogname sgetoptprogname + +int opterr = 1; +char *optprogname = 0; + +int getopt(argc,argv,opts) +int argc; +char **argv; +char *opts; +{ + int c; + char *s; + + if (!optprogname) { + optprogname = *argv; + if (!optprogname) optprogname = ""; + for (s = optprogname;*s;++s) if (*s == '/') optprogname = s + 1; + } + c = subgetopt(argc,argv,opts); + if (opterr) + if (c == '?') { + char chp[2]; chp[0] = optproblem; chp[1] = '\n'; + substdio_puts(subfderr,optprogname); + if (argv[optind] && (optind < argc)) + substdio_puts(subfderr,": illegal option -- "); + else + substdio_puts(subfderr,": option requires an argument -- "); + substdio_put(subfderr,chp,2); + substdio_flush(subfderr); + } + return c; +} diff --git a/sgetopt.h b/sgetopt.h @@ -0,0 +1,21 @@ +#ifndef SGETOPT_H +#define SGETOPT_H + +#ifndef SGETOPTNOSHORT +#define getopt sgetoptmine +#define optarg subgetoptarg +#define optind subgetoptind +#define optpos subgetoptpos +#define opterr sgetopterr +#define optproblem subgetoptproblem +#define optprogname sgetoptprogname +#define opteof subgetoptdone +#endif + +#include "subgetopt.h" + +extern int sgetoptmine(); +extern int sgetopterr; +extern char *sgetoptprogname; + +#endif diff --git a/sig.h b/sig.h @@ -0,0 +1,43 @@ +#ifndef SIG_H +#define SIG_H + +extern void sig_catch(); +extern void sig_block(); +extern void sig_unblock(); +extern void sig_blocknone(); +extern void sig_pause(); + +extern void sig_dfl(); + +extern void sig_miscignore(); +extern void sig_bugcatch(); + +extern void sig_pipeignore(); +extern void sig_pipedefault(); + +extern void sig_contblock(); +extern void sig_contunblock(); +extern void sig_contcatch(); +extern void sig_contdefault(); + +extern void sig_termblock(); +extern void sig_termunblock(); +extern void sig_termcatch(); +extern void sig_termdefault(); + +extern void sig_alarmblock(); +extern void sig_alarmunblock(); +extern void sig_alarmcatch(); +extern void sig_alarmdefault(); + +extern void sig_childblock(); +extern void sig_childunblock(); +extern void sig_childcatch(); +extern void sig_childdefault(); + +extern void sig_hangupblock(); +extern void sig_hangupunblock(); +extern void sig_hangupcatch(); +extern void sig_hangupdefault(); + +#endif diff --git a/sig_alarm.c b/sig_alarm.c @@ -0,0 +1,7 @@ +#include <signal.h> +#include "sig.h" + +void sig_alarmblock() { sig_block(SIGALRM); } +void sig_alarmunblock() { sig_unblock(SIGALRM); } +void sig_alarmcatch(f) void (*f)(); { sig_catch(SIGALRM,f); } +void sig_alarmdefault() { sig_catch(SIGALRM,SIG_DFL); } diff --git a/sig_block.c b/sig_block.c @@ -0,0 +1,40 @@ +#include <signal.h> +#include "sig.h" +#include "hassgprm.h" + +void sig_block(sig) +int sig; +{ +#ifdef HASSIGPROCMASK + sigset_t ss; + sigemptyset(&ss); + sigaddset(&ss,sig); + sigprocmask(SIG_BLOCK,&ss,(sigset_t *) 0); +#else + sigblock(1 << (sig - 1)); +#endif +} + +void sig_unblock(sig) +int sig; +{ +#ifdef HASSIGPROCMASK + sigset_t ss; + sigemptyset(&ss); + sigaddset(&ss,sig); + sigprocmask(SIG_UNBLOCK,&ss,(sigset_t *) 0); +#else + sigsetmask(sigsetmask(~0) & ~(1 << (sig - 1))); +#endif +} + +void sig_blocknone() +{ +#ifdef HASSIGPROCMASK + sigset_t ss; + sigemptyset(&ss); + sigprocmask(SIG_SETMASK,&ss,(sigset_t *) 0); +#else + sigsetmask(0); +#endif +} diff --git a/sig_bug.c b/sig_bug.c @@ -0,0 +1,17 @@ +#include <signal.h> +#include "sig.h" + +void sig_bugcatch(f) void (*f)(); +{ + sig_catch(SIGILL,f); + sig_catch(SIGABRT,f); + sig_catch(SIGFPE,f); + sig_catch(SIGBUS,f); + sig_catch(SIGSEGV,f); +#ifdef SIGSYS + sig_catch(SIGSYS,f); +#endif +#ifdef SIGEMT + sig_catch(SIGEMT,f); +#endif +} diff --git a/sig_catch.c b/sig_catch.c @@ -0,0 +1,18 @@ +#include <signal.h> +#include "sig.h" +#include "hassgact.h" + +void sig_catch(sig,f) +int sig; +void (*f)(); +{ +#ifdef HASSIGACTION + struct sigaction sa; + sa.sa_handler = f; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(sig,&sa,(struct sigaction *) 0); +#else + signal(sig,f); /* won't work under System V, even nowadays---dorks */ +#endif +} diff --git a/sig_child.c b/sig_child.c @@ -0,0 +1,7 @@ +#include <signal.h> +#include "sig.h" + +void sig_childblock() { sig_block(SIGCHLD); } +void sig_childunblock() { sig_unblock(SIGCHLD); } +void sig_childcatch(f) void (*f)(); { sig_catch(SIGCHLD,f); } +void sig_childdefault() { sig_catch(SIGCHLD,SIG_DFL); } diff --git a/sig_hup.c b/sig_hup.c @@ -0,0 +1,7 @@ +#include <signal.h> +#include "sig.h" + +void sig_hangupblock() { sig_block(SIGHUP); } +void sig_hangupunblock() { sig_unblock(SIGHUP); } +void sig_hangupcatch(f) void (*f)(); { sig_catch(SIGHUP,f); } +void sig_hangupdefault() { sig_catch(SIGHUP,SIG_DFL); } diff --git a/sig_misc.c b/sig_misc.c @@ -0,0 +1,17 @@ +#include <signal.h> +#include "sig.h" + +void sig_miscignore() +{ + sig_catch(SIGVTALRM,SIG_IGN); + sig_catch(SIGPROF,SIG_IGN); + sig_catch(SIGQUIT,SIG_IGN); + sig_catch(SIGINT,SIG_IGN); + sig_catch(SIGHUP,SIG_IGN); +#ifdef SIGXCPU + sig_catch(SIGXCPU,SIG_IGN); +#endif +#ifdef SIGXFSZ + sig_catch(SIGXFSZ,SIG_IGN); +#endif +} diff --git a/sig_pause.c b/sig_pause.c @@ -0,0 +1,14 @@ +#include <signal.h> +#include "sig.h" +#include "hassgprm.h" + +void sig_pause() +{ +#ifdef HASSIGPROCMASK + sigset_t ss; + sigemptyset(&ss); + sigsuspend(&ss); +#else + sigpause(0); +#endif +} diff --git a/sig_pipe.c b/sig_pipe.c @@ -0,0 +1,5 @@ +#include <signal.h> +#include "sig.h" + +void sig_pipeignore() { sig_catch(SIGPIPE,SIG_IGN); } +void sig_pipedefault() { sig_catch(SIGPIPE,SIG_DFL); } diff --git a/sig_term.c b/sig_term.c @@ -0,0 +1,7 @@ +#include <signal.h> +#include "sig.h" + +void sig_termblock() { sig_block(SIGTERM); } +void sig_termunblock() { sig_unblock(SIGTERM); } +void sig_termcatch(f) void (*f)(); { sig_catch(SIGTERM,f); } +void sig_termdefault() { sig_catch(SIGTERM,SIG_DFL); } diff --git a/slurpclose.c b/slurpclose.c @@ -0,0 +1,17 @@ +#include "stralloc.h" +#include "readwrite.h" +#include "slurpclose.h" + +int slurpclose(fd,sa,bufsize) +int fd; +stralloc *sa; +int bufsize; +{ + int r; + for (;;) { + if (!stralloc_readyplus(sa,bufsize)) { close(fd); return -1; } + r = read(fd,sa->s + sa->len,bufsize); + if (r <= 0) { close(fd); return r; } + sa->len += r; + } +} diff --git a/slurpclose.h b/slurpclose.h @@ -0,0 +1,6 @@ +#ifndef SLURPCLOSE_H +#define SLURPCLOSE_H + +extern int slurpclose(); + +#endif diff --git a/spawn.c b/spawn.c @@ -0,0 +1,259 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include "sig.h" +#include "wait.h" +#include "substdio.h" +#include "byte.h" +#include "str.h" +#include "stralloc.h" +#include "select.h" +#include "exit.h" +#include "coe.h" +#include "open.h" +#include "error.h" +#include "auto_qmail.h" +#include "auto_uids.h" +#include "auto_spawn.h" + +extern int truncreport; +extern int spawn(); +extern void report(); +extern void initialize(); + +struct delivery + { + int used; + int fdin; /* pipe input */ + int pid; /* zero if child is dead */ + int wstat; /* if !pid: status of child */ + int fdout; /* pipe output, -1 if !pid; delays eof until after death */ + stralloc output; + } +; + +struct delivery *d; + +void sigchld() +{ + int wstat; + int pid; + int i; + while ((pid = wait_nohang(&wstat)) > 0) + for (i = 0;i < auto_spawn;++i) if (d[i].used) + if (d[i].pid == pid) + { + close(d[i].fdout); d[i].fdout = -1; + d[i].wstat = wstat; d[i].pid = 0; + } +} + +int flagwriting = 1; + +int okwrite(fd,buf,n) int fd; char *buf; int n; +{ + int w; + if (!flagwriting) return n; + w = write(fd,buf,n); + if (w != -1) return w; + if (errno == error_intr) return -1; + flagwriting = 0; close(fd); + return n; +} + +int flagreading = 1; +char outbuf[1024]; substdio ssout; + +int stage = 0; /* reading 0:delnum 1:messid 2:sender 3:recip */ +int flagabort = 0; /* if 1, everything except delnum is garbage */ +int delnum; +stralloc messid = {0}; +stralloc sender = {0}; +stralloc recip = {0}; + +void err(s) char *s; +{ + char ch; ch = delnum; substdio_put(&ssout,&ch,1); + substdio_puts(&ssout,s); substdio_putflush(&ssout,"",1); +} + +void docmd() +{ + int f; + int i; + int j; + int fdmess; + int pi[2]; + struct stat st; + + if (flagabort) { err("Zqmail-spawn out of memory. (#4.3.0)\n"); return; } + if (delnum < 0) { err("ZInternal error: delnum negative. (#4.3.5)\n"); return; } + if (delnum >= auto_spawn) { err("ZInternal error: delnum too big. (#4.3.5)\n"); return; } + if (d[delnum].used) { err("ZInternal error: delnum in use. (#4.3.5)\n"); return; } + for (i = 0;i < messid.len;++i) + if (messid.s[i]) + if (!i || (messid.s[i] != '/')) + if ((unsigned char) (messid.s[i] - '0') > 9) + { err("DInternal error: messid has nonnumerics. (#5.3.5)\n"); return; } + if (messid.len > 100) { err("DInternal error: messid too long. (#5.3.5)\n"); return; } + if (!messid.s[0]) { err("DInternal error: messid too short. (#5.3.5)\n"); return; } + + if (!stralloc_copys(&d[delnum].output,"")) + { err("Zqmail-spawn out of memory. (#4.3.0)\n"); return; } + + j = byte_rchr(recip.s,recip.len,'@'); + if (j >= recip.len) { err("DSorry, address must include host name. (#5.1.3)\n"); return; } + + fdmess = open_read(messid.s); + if (fdmess == -1) { err("Zqmail-spawn unable to open message. (#4.3.0)\n"); return; } + + if (fstat(fdmess,&st) == -1) + { close(fdmess); err("Zqmail-spawn unable to fstat message. (#4.3.0)\n"); return; } + if ((st.st_mode & S_IFMT) != S_IFREG) + { close(fdmess); err("ZSorry, message has wrong type. (#4.3.5)\n"); return; } + if (st.st_uid != auto_uidq) /* aaack! qmailq has to be trusted! */ + /* your security is already toast at this point. damage control... */ + { close(fdmess); err("ZSorry, message has wrong owner. (#4.3.5)\n"); return; } + + if (pipe(pi) == -1) + { close(fdmess); err("Zqmail-spawn unable to create pipe. (#4.3.0)\n"); return; } + + coe(pi[0]); + + f = spawn(fdmess,pi[1],sender.s,recip.s,j); + close(fdmess); + if (f == -1) + { close(pi[0]); close(pi[1]); err("Zqmail-spawn unable to fork. (#4.3.0)\n"); return; } + + d[delnum].fdin = pi[0]; + d[delnum].fdout = pi[1]; coe(pi[1]); + d[delnum].pid = f; + d[delnum].used = 1; +} + +char cmdbuf[1024]; + +void getcmd() +{ + int i; + int r; + char ch; + + r = read(0,cmdbuf,sizeof(cmdbuf)); + if (r == 0) + { flagreading = 0; return; } + if (r == -1) + { + if (errno != error_intr) + flagreading = 0; + return; + } + + for (i = 0;i < r;++i) + { + ch = cmdbuf[i]; + switch(stage) + { + case 0: + delnum = (unsigned int) (unsigned char) ch; + messid.len = 0; stage = 1; break; + case 1: + if (!stralloc_append(&messid,&ch)) flagabort = 1; + if (ch) break; + sender.len = 0; stage = 2; break; + case 2: + if (!stralloc_append(&sender,&ch)) flagabort = 1; + if (ch) break; + recip.len = 0; stage = 3; break; + case 3: + if (!stralloc_append(&recip,&ch)) flagabort = 1; + if (ch) break; + docmd(); + flagabort = 0; stage = 0; break; + } + } +} + +char inbuf[128]; + +void main(argc,argv) +int argc; +char **argv; +{ + char ch; + int i; + int r; + fd_set rfds; + int nfds; + + if (chdir(auto_qmail) == -1) _exit(111); + if (chdir("queue/mess") == -1) _exit(111); + if (!stralloc_copys(&messid,"")) _exit(111); + if (!stralloc_copys(&sender,"")) _exit(111); + if (!stralloc_copys(&recip,"")) _exit(111); + + d = (struct delivery *) alloc((auto_spawn + 10) * sizeof(struct delivery)); + if (!d) _exit(111); + + substdio_fdbuf(&ssout,okwrite,1,outbuf,sizeof(outbuf)); + + sig_pipeignore(); + sig_childcatch(sigchld); + + initialize(argc,argv); + + ch = auto_spawn; substdio_putflush(&ssout,&ch,1); + + for (i = 0;i < auto_spawn;++i) { d[i].used = 0; d[i].output.s = 0; } + + for (;;) + { + if (!flagreading) + { + for (i = 0;i < auto_spawn;++i) if (d[i].used) break; + if (i >= auto_spawn) _exit(0); + } + sig_childunblock(); + + FD_ZERO(&rfds); + if (flagreading) FD_SET(0,&rfds); + nfds = 1; + for (i = 0;i < auto_spawn;++i) if (d[i].used) + { FD_SET(d[i].fdin,&rfds); if (d[i].fdin >= nfds) nfds = d[i].fdin + 1; } + + r = select(nfds,&rfds,(fd_set *) 0,(fd_set *) 0,(struct timeval *) 0); + sig_childblock(); + + if (r != -1) + { + if (flagreading) + if (FD_ISSET(0,&rfds)) + getcmd(); + for (i = 0;i < auto_spawn;++i) if (d[i].used) + if (FD_ISSET(d[i].fdin,&rfds)) + { + r = read(d[i].fdin,inbuf,128); + if (r == -1) + continue; /* read error on a readable pipe? be serious */ + if (r == 0) + { + ch = i; substdio_put(&ssout,&ch,1); + report(&ssout,d[i].wstat,d[i].output.s,d[i].output.len); + substdio_put(&ssout,"",1); + substdio_flush(&ssout); + close(d[i].fdin); d[i].used = 0; + continue; + } + while (!stralloc_readyplus(&d[i].output,r)) sleep(10); /*XXX*/ + byte_copy(d[i].output.s + d[i].output.len,r,inbuf); + d[i].output.len += r; + if (truncreport > 100) + if (d[i].output.len > truncreport) + { + char *truncmess = "\nError report too long, sorry.\n"; + d[i].output.len = truncreport - str_len(truncmess) - 3; + stralloc_cats(&d[i].output,truncmess); + } + } + } + } +} diff --git a/splogger.8 b/splogger.8 @@ -0,0 +1,60 @@ +.TH splogger 8 +.SH NAME +splogger \- make entries in syslog +.SH SYNOPSIS +.B splogger +[ +.I tag +[ +.I fac +] +] +.SH DESCRIPTION +.B splogger +reads a series of messages and feeds them to +.BR syslog . +At the front of each message it puts +.I tag +(default: +.BR splogger ) +and a numerical timestamp. + +.B splogger +checks for +.B alert: +or +.B warning: +at the beginning of each message. +It selects a priority of +LOG_ALERT, LOG_WARNING, or LOG_INFO accordingly. + +.B splogger +logs messages with facility +.IR fac . +.I fac +(default: 2) +must be numeric. + +.B splogger +converts unprintable characters to question marks. + +.B splogger +does not log blank lines. + +.B splogger +folds messages after 800 characters, +since +.B syslog +can't handle long messages. +.B splogger +uses a + after the timestamp +to mark folded lines. + +Note that the +.B syslog +mechanism is inherently unreliable: +it does not guarantee that messages will be logged. +It is also very slow. +.SH "SEE ALSO" +syslog(3), +logger(8) diff --git a/splogger.c b/splogger.c @@ -0,0 +1,72 @@ +#include <sys/types.h> +#include <sys/time.h> +#include <syslog.h> +#include "error.h" +#include "substdio.h" +#include "subfd.h" +#include "exit.h" +#include "str.h" +#include "scan.h" +#include "fmt.h" + +char buf[800]; /* syslog truncates long lines (or crashes); GPACIC */ +int bufpos = 0; /* 0 <= bufpos < sizeof(buf) */ +int flagcont = 0; +int priority; /* defined if flagcont */ +char stamp[FMT_ULONG + FMT_ULONG + 3]; /* defined if flagcont */ + +void stamp_make() +{ + struct timeval tv; + char *s; + gettimeofday(&tv,(struct timezone *) 0); + s = stamp; + s += fmt_ulong(s,(unsigned long) tv.tv_sec); + *s++ = '.'; + s += fmt_uint0(s,(unsigned int) tv.tv_usec,6); + *s = 0; +} + +void flush() +{ + if (bufpos) { + buf[bufpos] = 0; + if (flagcont) + syslog(priority,"%s+%s",stamp,buf); /* logger folds invisibly; GPACIC */ + else { + stamp_make(); + priority = LOG_INFO; + if (str_start(buf,"warning:")) priority = LOG_WARNING; + if (str_start(buf,"alert:")) priority = LOG_ALERT; + syslog(priority,"%s %s",stamp,buf); + flagcont = 1; + } + } + bufpos = 0; +} + +void main(argc,argv) +int argc; +char **argv; +{ + char ch; + + if (argv[1]) + if (argv[2]) { + unsigned long facility; + scan_ulong(argv[2],&facility); + openlog(argv[1],0,facility << 3); + } + else + openlog(argv[1],0,LOG_MAIL); + else + openlog("splogger",0,LOG_MAIL); + + for (;;) { + if (substdio_get(subfdin,&ch,1) < 1) _exit(0); + if (ch == '\n') { flush(); flagcont = 0; continue; } + if (bufpos == sizeof(buf) - 1) flush(); + if ((ch < 32) || (ch > 126)) ch = '?'; /* logger truncates at 0; GPACIC */ + buf[bufpos++] = ch; + } +} diff --git a/str.h b/str.h @@ -0,0 +1,14 @@ +#ifndef STR_H +#define STR_H + +extern unsigned int str_copy(); +extern int str_diff(); +extern int str_diffn(); +extern unsigned int str_len(); +extern unsigned int str_chr(); +extern unsigned int str_rchr(); +extern int str_start(); + +#define str_equal(s,t) (!str_diff((s),(t))) + +#endif diff --git a/str_chr.c b/str_chr.c @@ -0,0 +1,19 @@ +#include "str.h" + +unsigned int str_chr(s,c) +register char *s; +int c; +{ + register char ch; + register char *t; + + ch = c; + t = s; + for (;;) { + if (!*t) break; if (*t == ch) break; ++t; + if (!*t) break; if (*t == ch) break; ++t; + if (!*t) break; if (*t == ch) break; ++t; + if (!*t) break; if (*t == ch) break; ++t; + } + return t - s; +} diff --git a/str_cpy.c b/str_cpy.c @@ -0,0 +1,16 @@ +#include "str.h" + +unsigned int str_copy(s,t) +register char *s; +register char *t; +{ + register int len; + + len = 0; + for (;;) { + if (!(*s = *t)) return len; ++s; ++t; ++len; + if (!(*s = *t)) return len; ++s; ++t; ++len; + if (!(*s = *t)) return len; ++s; ++t; ++len; + if (!(*s = *t)) return len; ++s; ++t; ++len; + } +} diff --git a/str_diff.c b/str_diff.c @@ -0,0 +1,17 @@ +#include "str.h" + +int str_diff(s,t) +register char *s; +register char *t; +{ + register char x; + + for (;;) { + x = *s; if (x != *t) break; if (!x) break; ++s; ++t; + x = *s; if (x != *t) break; if (!x) break; ++s; ++t; + x = *s; if (x != *t) break; if (!x) break; ++s; ++t; + x = *s; if (x != *t) break; if (!x) break; ++s; ++t; + } + return ((int)(unsigned int)(unsigned char) x) + - ((int)(unsigned int)(unsigned char) *t); +} diff --git a/str_diffn.c b/str_diffn.c @@ -0,0 +1,18 @@ +#include "str.h" + +int str_diffn(s,t,len) +register char *s; +register char *t; +unsigned int len; +{ + register char x; + + for (;;) { + if (!len--) return 0; x = *s; if (x != *t) break; if (!x) break; ++s; ++t; + if (!len--) return 0; x = *s; if (x != *t) break; if (!x) break; ++s; ++t; + if (!len--) return 0; x = *s; if (x != *t) break; if (!x) break; ++s; ++t; + if (!len--) return 0; x = *s; if (x != *t) break; if (!x) break; ++s; ++t; + } + return ((int)(unsigned int)(unsigned char) x) + - ((int)(unsigned int)(unsigned char) *t); +} diff --git a/str_len.c b/str_len.c @@ -0,0 +1,15 @@ +#include "str.h" + +unsigned int str_len(s) +register char *s; +{ + register char *t; + + t = s; + for (;;) { + if (!*t) return t - s; ++t; + if (!*t) return t - s; ++t; + if (!*t) return t - s; ++t; + if (!*t) return t - s; ++t; + } +} diff --git a/str_rchr.c b/str_rchr.c @@ -0,0 +1,22 @@ +#include "str.h" + +unsigned int str_rchr(s,c) +register char *s; +int c; +{ + register char ch; + register char *t; + register char *u; + + ch = c; + t = s; + u = 0; + for (;;) { + if (!*t) break; if (*t == ch) u = t; ++t; + if (!*t) break; if (*t == ch) u = t; ++t; + if (!*t) break; if (*t == ch) u = t; ++t; + if (!*t) break; if (*t == ch) u = t; ++t; + } + if (!u) u = t; + return u - s; +} diff --git a/str_start.c b/str_start.c @@ -0,0 +1,15 @@ +#include "str.h" + +int str_start(s,t) +register char *s; +register char *t; +{ + register char x; + + for (;;) { + x = *t++; if (!x) return 1; if (x != *s++) return 0; + x = *t++; if (!x) return 1; if (x != *s++) return 0; + x = *t++; if (!x) return 1; if (x != *s++) return 0; + x = *t++; if (!x) return 1; if (x != *s++) return 0; + } +} diff --git a/stralloc.3 b/stralloc.3 @@ -0,0 +1,160 @@ +.TH stralloc 3 +.SH NAME +stralloc \- dynamically allocated strings +.SH SYNTAX +.B #include <stralloc.h> + +int \fBstralloc_ready\fP(&\fIsa\fR,\fIlen\fR); +.br +int \fBstralloc_readyplus\fP(&\fIsa\fR,\fIlen\fR); + +int \fBstralloc_copy\fP(&\fIsa\fR,&\fIsa2\fR); +.br +int \fBstralloc_copys\fP(&\fIsa\fR,\fIbuf\fR); +.br +int \fBstralloc_copyb\fP(&\fIsa\fR,\fIbuf\fR,\fIlen\fR); + +int \fBstralloc_cat\fP(&\fIsa\fR,&\fIsa2\fR); +.br +int \fBstralloc_cats\fP(&\fIsa\fR,\fIbuf\fR); +.br +int \fBstralloc_catb\fP(&\fIsa\fR,\fIbuf\fR,\fIlen\fR); + +int \fBstralloc_append\fP(&\fIsa\fR,\fIbuf\fR); +.br +int \fBstralloc_0\fP(&\fIsa\fR); + +int \fBstralloc_starts\fP(&\fIsa\fR,\fIbuf\fR); + +stralloc \fIsa\fR = {0}; +.br +stralloc \fIsa2\fR = {0}; +.br +unsigned int \fIlen\fR; +.br +char *\fIbuf\fR; +.SH DESCRIPTION +A +.B stralloc +variable holds a string in dynamically allocated space. +String length is limited only by memory. +String contents are unrestricted. + +The +.B stralloc +structure has three components: +.I sa\fB.s +is a pointer to the string, or 0 if it is not allocated; +.I sa\fB.len +is the number of bytes in the string, if it is allocated; +.I sa\fB.a +is the number of bytes allocated for the string, if it is allocated. +A +.B stralloc +variable should be initialized to {0}, +meaning unallocated. + +.B stralloc_ready +makes sure that +.I sa +has enough space allocated for +.I len +characters. +It allocates extra space if necessary. + +.B stralloc_readyplus +makes sure that +.I sa +has enough space allocated for +.I len +characters more than its current length. +If +.I sa +is unallocated, +.B stralloc_readyplus +is the same as +.BR stralloc_ready . + +.B stralloc_copy +copies +.I sa2 +to +.IR sa , +allocating space if necessary. +Here +.I sa2 +is an allocated +.B stralloc +variable. + +.B stralloc_copys +copies a 0-terminated string, +.IR buf , +to +.IR sa , +without the 0. + +.B stralloc_copyb +copies +.I len +characters from +.I buf +to +.IR sa . + +.B stralloc_cat +appends +.I sa2 +to +.IR sa , +allocating space if necessary. +If +.I sa +is unallocated, +.B stralloc_cat +is the same as +.BR stralloc_copy . + +.B stralloc_cats +and +.B stralloc_catb +are analogous to +.B stralloc_copys +and +.BR stralloc_copyb . + +.B stralloc_append +adds a single character, +.IR *buf , +to +.IR sa , +allocating space if necessary. + +.B stralloc_0 +adds a single 0 character +to +.IR sa . + +.B stralloc_starts +returns 1 if the 0-terminated string +.IR buf , +without the 0, +is a prefix of +.IR sa . +.SH "ERROR HANDLING" +If a +.B stralloc +routine runs out of memory, +it leaves +.I sa +alone and returns 0, +setting +.B errno +appropriately. +On success it returns 1; +this guarantees that +.I sa +is allocated. +.SH "SEE ALSO" +alloc(3), +error(3) diff --git a/stralloc.h b/stralloc.h @@ -0,0 +1,21 @@ +#ifndef STRALLOC_H +#define STRALLOC_H + +#include "gen_alloc.h" + +GEN_ALLOC_typedef(stralloc,char,s,len,a) + +extern int stralloc_ready(); +extern int stralloc_readyplus(); +extern int stralloc_copy(); +extern int stralloc_cat(); +extern int stralloc_copys(); +extern int stralloc_cats(); +extern int stralloc_copyb(); +extern int stralloc_catb(); +extern int stralloc_append(); /* beware: this takes a pointer to 1 char */ +extern int stralloc_starts(); + +#define stralloc_0(sa) stralloc_append(sa,"") + +#endif diff --git a/stralloc_arts.c b/stralloc_arts.c @@ -0,0 +1,12 @@ +#include "byte.h" +#include "str.h" +#include "stralloc.h" + +int stralloc_starts(sa,s) +stralloc *sa; +char *s; +{ + int len; + len = str_len(s); + return (sa->len >= len) && byte_equal(s,len,sa->s); +} diff --git a/stralloc_cat.c b/stralloc_cat.c @@ -0,0 +1,9 @@ +#include "byte.h" +#include "stralloc.h" + +int stralloc_cat(sato,safrom) +stralloc *sato; +stralloc *safrom; +{ + return stralloc_catb(sato,safrom->s,safrom->len); +} diff --git a/stralloc_catb.c b/stralloc_catb.c @@ -0,0 +1,15 @@ +#include "stralloc.h" +#include "byte.h" + +int stralloc_catb(sa,s,n) +stralloc *sa; +char *s; +unsigned int n; +{ + if (!sa->s) return stralloc_copyb(sa,s,n); + if (!stralloc_readyplus(sa,n + 1)) return 0; + byte_copy(sa->s + sa->len,n,s); + sa->len += n; + sa->s[sa->len] = 'Z'; /* ``offensive programming'' */ + return 1; +} diff --git a/stralloc_cats.c b/stralloc_cats.c @@ -0,0 +1,10 @@ +#include "byte.h" +#include "str.h" +#include "stralloc.h" + +int stralloc_cats(sa,s) +stralloc *sa; +char *s; +{ + return stralloc_catb(sa,s,str_len(s)); +} diff --git a/stralloc_copy.c b/stralloc_copy.c @@ -0,0 +1,9 @@ +#include "byte.h" +#include "stralloc.h" + +int stralloc_copy(sato,safrom) +stralloc *sato; +stralloc *safrom; +{ + return stralloc_copyb(sato,safrom->s,safrom->len); +} diff --git a/stralloc_eady.c b/stralloc_eady.c @@ -0,0 +1,6 @@ +#include "alloc.h" +#include "stralloc.h" +#include "gen_allocdefs.h" + +GEN_ALLOC_ready(stralloc,char,s,len,a,i,n,x,30,stralloc_ready) +GEN_ALLOC_readyplus(stralloc,char,s,len,a,i,n,x,30,stralloc_readyplus) diff --git a/stralloc_opyb.c b/stralloc_opyb.c @@ -0,0 +1,14 @@ +#include "stralloc.h" +#include "byte.h" + +int stralloc_copyb(sa,s,n) +stralloc *sa; +char *s; +unsigned int n; +{ + if (!stralloc_ready(sa,n + 1)) return 0; + byte_copy(sa->s,n,s); + sa->len = n; + sa->s[n] = 'Z'; /* ``offensive programming'' */ + return 1; +} diff --git a/stralloc_opys.c b/stralloc_opys.c @@ -0,0 +1,10 @@ +#include "byte.h" +#include "str.h" +#include "stralloc.h" + +int stralloc_copys(sa,s) +stralloc *sa; +char *s; +{ + return stralloc_copyb(sa,s,str_len(s)); +} diff --git a/stralloc_pend.c b/stralloc_pend.c @@ -0,0 +1,5 @@ +#include "alloc.h" +#include "stralloc.h" +#include "gen_allocdefs.h" + +GEN_ALLOC_append(stralloc,char,s,len,a,i,n,x,30,stralloc_readyplus,stralloc_append) diff --git a/strerr.h b/strerr.h @@ -0,0 +1,80 @@ +#ifndef STRERR_H +#define STRERR_H + +struct strerr + { + struct strerr *who; + char *x; + char *y; + char *z; + } +; + +extern struct strerr strerr_sys; +extern void strerr_sysinit(); + +extern char *strerr(); +extern void strerr_warn(); +extern void strerr_die(); + +#define STRERR(r,se,a) \ +{ se.who = 0; se.x = a; se.y = 0; se.z = 0; return r; } + +#define STRERR_SYS(r,se,a) \ +{ se.who = &strerr_sys; se.x = a; se.y = 0; se.z = 0; return r; } +#define STRERR_SYS3(r,se,a,b,c) \ +{ se.who = &strerr_sys; se.x = a; se.y = b; se.z = c; return r; } + +#define strerr_warn6(x1,x2,x3,x4,x5,x6,se) \ +strerr_warn((x1),(x2),(x3),(x4),(x5),(x6),(struct strerr *) (se)) +#define strerr_warn5(x1,x2,x3,x4,x5,se) \ +strerr_warn((x1),(x2),(x3),(x4),(x5),(char *) 0,(struct strerr *) (se)) +#define strerr_warn4(x1,x2,x3,x4,se) \ +strerr_warn((x1),(x2),(x3),(x4),(char *) 0,(char *) 0,(struct strerr *) (se)) +#define strerr_warn3(x1,x2,x3,se) \ +strerr_warn((x1),(x2),(x3),(char *) 0,(char *) 0,(char *) 0,(struct strerr *) (se)) +#define strerr_warn2(x1,x2,se) \ +strerr_warn((x1),(x2),(char *) 0,(char *) 0,(char *) 0,(char *) 0,(struct strerr *) (se)) +#define strerr_warn1(x1,se) \ +strerr_warn((x1),(char *) 0,(char *) 0,(char *) 0,(char *) 0,(char *) 0,(struct strerr *) (se)) + +#define strerr_die6(e,x1,x2,x3,x4,x5,x6,se) \ +strerr_die((e),(x1),(x2),(x3),(x4),(x5),(x6),(struct strerr *) (se)) +#define strerr_die5(e,x1,x2,x3,x4,x5,se) \ +strerr_die((e),(x1),(x2),(x3),(x4),(x5),(char *) 0,(struct strerr *) (se)) +#define strerr_die4(e,x1,x2,x3,x4,se) \ +strerr_die((e),(x1),(x2),(x3),(x4),(char *) 0,(char *) 0,(struct strerr *) (se)) +#define strerr_die3(e,x1,x2,x3,se) \ +strerr_die((e),(x1),(x2),(x3),(char *) 0,(char *) 0,(char *) 0,(struct strerr *) (se)) +#define strerr_die2(e,x1,x2,se) \ +strerr_die((e),(x1),(x2),(char *) 0,(char *) 0,(char *) 0,(char *) 0,(struct strerr *) (se)) +#define strerr_die1(e,x1,se) \ +strerr_die((e),(x1),(char *) 0,(char *) 0,(char *) 0,(char *) 0,(char *) 0,(struct strerr *) (se)) + +#define strerr_die6sys(e,x1,x2,x3,x4,x5,x6) \ +strerr_die((e),(x1),(x2),(x3),(x4),(x5),(x6),&strerr_sys) +#define strerr_die5sys(e,x1,x2,x3,x4,x5) \ +strerr_die((e),(x1),(x2),(x3),(x4),(x5),(char *) 0,&strerr_sys) +#define strerr_die4sys(e,x1,x2,x3,x4) \ +strerr_die((e),(x1),(x2),(x3),(x4),(char *) 0,(char *) 0,&strerr_sys) +#define strerr_die3sys(e,x1,x2,x3) \ +strerr_die((e),(x1),(x2),(x3),(char *) 0,(char *) 0,(char *) 0,&strerr_sys) +#define strerr_die2sys(e,x1,x2) \ +strerr_die((e),(x1),(x2),(char *) 0,(char *) 0,(char *) 0,(char *) 0,&strerr_sys) +#define strerr_die1sys(e,x1) \ +strerr_die((e),(x1),(char *) 0,(char *) 0,(char *) 0,(char *) 0,(char *) 0,&strerr_sys) + +#define strerr_die6x(e,x1,x2,x3,x4,x5,x6) \ +strerr_die((e),(x1),(x2),(x3),(x4),(x5),(x6),(struct strerr *) 0) +#define strerr_die5x(e,x1,x2,x3,x4,x5) \ +strerr_die((e),(x1),(x2),(x3),(x4),(x5),(char *) 0,(struct strerr *) 0) +#define strerr_die4x(e,x1,x2,x3,x4) \ +strerr_die((e),(x1),(x2),(x3),(x4),(char *) 0,(char *) 0,(struct strerr *) 0) +#define strerr_die3x(e,x1,x2,x3) \ +strerr_die((e),(x1),(x2),(x3),(char *) 0,(char *) 0,(char *) 0,(struct strerr *) 0) +#define strerr_die2x(e,x1,x2) \ +strerr_die((e),(x1),(x2),(char *) 0,(char *) 0,(char *) 0,(char *) 0,(struct strerr *) 0) +#define strerr_die1x(e,x1) \ +strerr_die((e),(x1),(char *) 0,(char *) 0,(char *) 0,(char *) 0,(char *) 0,(struct strerr *) 0) + +#endif diff --git a/strerr_die.c b/strerr_die.c @@ -0,0 +1,37 @@ +#include "substdio.h" +#include "subfd.h" +#include "exit.h" +#include "strerr.h" + +void strerr_warn(x1,x2,x3,x4,x5,x6,se) +char *x1; char *x2; char *x3; char *x4; char *x5; char *x6; +struct strerr *se; +{ + strerr_sysinit(); + + if (x1) substdio_puts(subfderr,x1); + if (x2) substdio_puts(subfderr,x2); + if (x3) substdio_puts(subfderr,x3); + if (x4) substdio_puts(subfderr,x4); + if (x5) substdio_puts(subfderr,x5); + if (x6) substdio_puts(subfderr,x6); + + while(se) { + if (se->x) substdio_puts(subfderr,se->x); + if (se->y) substdio_puts(subfderr,se->y); + if (se->z) substdio_puts(subfderr,se->z); + se = se->who; + } + + substdio_puts(subfderr,"\n"); + substdio_flush(subfderr); +} + +void strerr_die(e,x1,x2,x3,x4,x5,x6,se) +int e; +char *x1; char *x2; char *x3; char *x4; char *x5; char *x6; +struct strerr *se; +{ + strerr_warn(x1,x2,x3,x4,x5,x6,se); + _exit(e); +} diff --git a/strerr_sys.c b/strerr_sys.c @@ -0,0 +1,12 @@ +#include "error.h" +#include "strerr.h" + +struct strerr strerr_sys; + +void strerr_sysinit() +{ + strerr_sys.who = 0; + strerr_sys.x = error_str(errno); + strerr_sys.y = ""; + strerr_sys.z = ""; +} diff --git a/subfd.h b/subfd.h @@ -0,0 +1,15 @@ +#ifndef SUBFD_H +#define SUBFD_H + +#include "substdio.h" + +extern substdio *subfdin; +extern substdio *subfdinsmall; +extern substdio *subfdout; +extern substdio *subfdoutsmall; +extern substdio *subfderr; + +extern int subfd_read(); +extern int subfd_readsmall(); + +#endif diff --git a/subfderr.c b/subfderr.c @@ -0,0 +1,7 @@ +#include "readwrite.h" +#include "substdio.h" +#include "subfd.h" + +char subfd_errbuf[256]; +static substdio it = SUBSTDIO_FDBUF(write,2,subfd_errbuf,256); +substdio *subfderr = &it; diff --git a/subfdin.c b/subfdin.c @@ -0,0 +1,13 @@ +#include "readwrite.h" +#include "substdio.h" +#include "subfd.h" + +int subfd_read(fd,buf,len) int fd; char *buf; int len; +{ + if (substdio_flush(subfdout) == -1) return -1; + return read(fd,buf,len); +} + +char subfd_inbuf[SUBSTDIO_INSIZE]; +static substdio it = SUBSTDIO_FDBUF(subfd_read,0,subfd_inbuf,SUBSTDIO_INSIZE); +substdio *subfdin = &it; diff --git a/subfdins.c b/subfdins.c @@ -0,0 +1,13 @@ +#include "readwrite.h" +#include "substdio.h" +#include "subfd.h" + +int subfd_readsmall(fd,buf,len) int fd; char *buf; int len; +{ + if (substdio_flush(subfdoutsmall) == -1) return -1; + return read(fd,buf,len); +} + +char subfd_inbufsmall[256]; +static substdio it = SUBSTDIO_FDBUF(subfd_readsmall,0,subfd_inbufsmall,256); +substdio *subfdinsmall = &it; diff --git a/subfdout.c b/subfdout.c @@ -0,0 +1,7 @@ +#include "readwrite.h" +#include "substdio.h" +#include "subfd.h" + +char subfd_outbuf[SUBSTDIO_OUTSIZE]; +static substdio it = SUBSTDIO_FDBUF(write,1,subfd_outbuf,SUBSTDIO_OUTSIZE); +substdio *subfdout = &it; diff --git a/subfdouts.c b/subfdouts.c @@ -0,0 +1,7 @@ +#include "readwrite.h" +#include "substdio.h" +#include "subfd.h" + +char subfd_outbufsmall[256]; +static substdio it = SUBSTDIO_FDBUF(write,1,subfd_outbufsmall,256); +substdio *subfdoutsmall = &it; diff --git a/subgetopt.3 b/subgetopt.3 @@ -0,0 +1,357 @@ +.TH subgetopt 3 +.SH NAME +subgetopt \- get option character from command line +.SH SYNTAX +.B #include <subgetopt.h> + +char *\fBsgoptarg\fP; +.br +int \fBsgoptind\fP; +.br +int \fBsgoptpos\fP; +.br +int \fBsgoptdone\fP; +.br +int \fBsgoptproblem\fP; + +int \fBsgopt(\fP\fIargc,argv,opts\fR\fB)\fP; + +int \fIargc\fR; +.br +char **\fIargv\fR; +.br +char *\fIopts\fR; +.SH DESCRIPTION +.B sgopt +returns the next valid command-line option character +from +.IR argv . + +Valid option characters are listed in the +.I opts +string. +.I opts +may be empty. +A character in +.I opts +may be followed by a colon, +in which case it +takes an +.I option argument\fR. +Avoid using the characters ?, :, and \- as option characters. + +Below +.I option argument +is abbreviated +as +.I optarg +and +.I command-line argument +is abbreviated as +.IR cmdarg . + +Options are listed in cmdargs which begin with +a minus sign. +Several options which do not take optargs may be combined +into one cmdarg. + +An option which takes an optarg may be handled in two ways. +If it appears at the very end of a cmdarg, +then the entire next cmdarg is the optarg. +But if there are any characters in the cmdarg +after the option character, +then those characters form the optarg. +The optarg is returned in +.BR sgoptarg . +Next time +.B sgopt +looks at the cmdarg which follows the optarg. + +If a cmdarg does not begin with a hyphen, +or if it is a lone hyphen not followed by any characters, +or if it begins with two hyphens, +then it terminates option processing, +and +.B sgopt +returns an appropriate code. +If there are two hyphens, +.B sgopt +will advance attention to the next cmdarg, +so it can be called again to read further options. +.SH "PROPER USAGE" +.B sgoptproblem +should be used only when +.B sgopt +returns ?. +.B sgoptind +and +.B sgoptpos +are defined all the time. +.B sgoptarg +is defined all the time; +it is null unless +.B sgopt +has just returned an option with optarg. + +.B sgopt +is typically used as follows. + +.EX +#include <subgetopt.h> + +main(argc,argv) int argc; char **argv; { int opt; + +while ((opt = sgopt(argc,argv,"a:s")) != sgoptdone) +.br + switch(opt) { +.br + case 'a': +.br + printf("opt a with optarg %s\\n",sgoptarg); break; +.br + case 's': +.br + printf("opt s with no optarg\\n"); break; +.br + case '?': +.br + if (argv[sgoptind] && (sgoptind < argc)) +.br + printf("illegal opt %c\\n",sgoptproblem); +.br + else +.br + printf("missing arg, opt %c\\n",sgoptproblem); +.br + exit(1); +.br + } + +argv += sgoptind; +.br +while (*argv) printf("argument %s\\n",*argv++); +.br +exit(0); +.br +} +.EE + +The end of the command line is +marked by either +.IR argc , +or a null pointer in +.IR argv , +whichever comes first. +Normally +these two markers coincide, +so it is redundant +to test for +both +.I argv\fB[sgoptind] +and +.B sgoptind < \fIargc\fR. +The above code shows both tests as an illustration. + +.B Multiple option sets: +One useful technique is to call +.B sgopt +with a primary +.I opts +until it returns EOF, +then call +.B sgopt +with a secondary +.I opts +until it returns EOF. +The user can provide primary options, then a double hyphen, +and then secondary options. +No special handling is needed if some or all of the options are +omitted. +The same technique can be used for any number of option sets +in series. + +.B Multiple command lines: +Before parsing a new +.BR argv , +make sure to +set +.B sgoptind +and +.B sgoptpos +back to +1 and 0. +.SH "PARSING STAGES" +.B sgopt +keeps track of its position in +.I argv +with +.B sgoptind +and +.BR sgoptpos , +which are initialized to 1 and 0. +It looks at +.I argv\fB[sgoptind][sgoptpos] +and following characters. + +.B sgopt +indicates +that no more options are available by +returning +.BR sgoptdone , +which is initialized to +.BR SUBGETOPTDONE , +which is defined as \-1. + +.B sgopt +begins by setting +.B optarg +to null. + +.B Ending conditions: +If +.I argv +is null, or +.B sgoptind +is larger than +.IR argc , +or the current cmdarg +.I argv\fB[sgoptind] +is null, +then +.B sgopt +returns +.BR optdone . + +.B Stage one: +If the current character +is zero, +.B sgopt +moves to the beginning of the next cmdarg. +It then checks the ending conditions again. + +.B Stage two: +If +the current position is the begining of the cmdarg, +.B sgopt +checks whether +the current character +is a minus sign. +If not it returns +.BR optdone . +It then +moves +to the next character. +If that character is zero, +.B sgopt +moves +back to the beginning of the cmdarg, +and returns +.BR sgoptdone . +If the character is a minus sign, +.B sgopt +moves to the beginning of the next cmdarg, +and returns +.BR sgoptdone . + +.B Stage three: +.B sgopt +records the current character, +.IR c , +and moves to the next character. +There are three possibilities: +(1) +.I c +is an option character without optarg in +.IR opts , +or +(2) +.I c +is an option character with optarg in +.IR opts , +or +(3) +.I c +does not appear in +.IR opts . + +(1) +If +.I c +appears as an option character without optarg in +.IR opts , +.B sgopt +returns +.IR c . + +(2) +If +.I c +appears as an option character with optarg in +.IR opts , +.B sgopt +sets +.B sgoptarg +to the current position, +and moves to the next cmdarg. +If +.B sgoptarg +is nonempty, +.B sgopt +returns +.IR c . + +Then +.B sgopt +sets +.B sgoptarg +to +the current cmdarg. +If +the current cmdarg is null, +or past +.IR argc , +.B sgopt +sets +.B sgoptproblem +to +.I c +and returns ?. +Otherwise +.B sgopt +moves to the next +argument +and returns +.IR c . + +(2) +If +.I c +does not appear in +.IR opts , +.B sgopt +sets +.B sgoptproblem +to +.I c +and returns ?. +.SH "SYNTAX NOTE" +.B sgopt +is actually a macro abbreviation for +.BR subgetopt . +The external +.B sg +variables are also macros +for +.BR subget . +These macros are defined in +.BR <subgetopt.h> , +unless +.B SUBGETOPTNOSHORT +is defined +when +.B <subgetopt.h> +is included. +.SH VERSION +subgetopt version 0.9, 931129. +.SH AUTHOR +Placed into the public domain by Daniel J. Bernstein. diff --git a/subgetopt.c b/subgetopt.c @@ -0,0 +1,79 @@ +/* subgetopt.c, subgetopt.h: (yet another) improved getopt clone, inner layer +D. J. Bernstein, djb@pobox.com. +No dependencies. +No system requirements. +19970228: Cleanups. +931129: Adapted from getopt.c. +No known patent problems. + +Documentation in subgetopt.3. +*/ + +#define SUBGETOPTNOSHORT +#include "subgetopt.h" + +#define sgopt subgetopt +#define optind subgetoptind +#define optpos subgetoptpos +#define optarg subgetoptarg +#define optproblem subgetoptproblem +#define optdone subgetoptdone + +int optind = 1; +int optpos = 0; +char *optarg = 0; +int optproblem = 0; +int optdone = SUBGETOPTDONE; + +int sgopt(argc,argv,opts) +int argc; +char **argv; +char *opts; +{ + int c; + char *s; + + optarg = 0; + if (!argv || (optind >= argc) || !argv[optind]) return optdone; + if (optpos && !argv[optind][optpos]) { + ++optind; + optpos = 0; + if ((optind >= argc) || !argv[optind]) return optdone; + } + if (!optpos) { + if (argv[optind][0] != '-') return optdone; + ++optpos; + c = argv[optind][1]; + if ((c == '-') || (c == 0)) { + if (c) ++optind; + optpos = 0; + return optdone; + } + /* otherwise c is reassigned below */ + } + c = argv[optind][optpos]; + ++optpos; + s = opts; + while (*s) { + if (c == *s) { + if (s[1] == ':') { + optarg = argv[optind] + optpos; + ++optind; + optpos = 0; + if (!*optarg) { + optarg = argv[optind]; + if ((optind >= argc) || !optarg) { /* argument past end */ + optproblem = c; + return '?'; + } + ++optind; + } + } + return c; + } + ++s; + if (*s == ':') ++s; + } + optproblem = c; + return '?'; +} diff --git a/subgetopt.h b/subgetopt.h @@ -0,0 +1,24 @@ +#ifndef SUBGETOPT_H +#define SUBGETOPT_H + +#ifndef SUBGETOPTNOSHORT +#define sgopt subgetopt +#define sgoptarg subgetoptarg +#define sgoptind subgetoptind +#define sgoptpos subgetoptpos +#define sgoptproblem subgetoptproblem +#define sgoptprogname subgetoptprogname +#define sgoptdone subgetoptdone +#endif + +#define SUBGETOPTDONE -1 + +extern int subgetopt(); +extern char *subgetoptarg; +extern int subgetoptind; +extern int subgetoptpos; +extern int subgetoptproblem; +extern char *subgetoptprogname; +extern int subgetoptdone; + +#endif diff --git a/substdi.c b/substdi.c @@ -0,0 +1,91 @@ +#include "substdio.h" +#include "byte.h" +#include "error.h" + +static int oneread(op,fd,buf,len) +register int (*op)(); +register int fd; +register char *buf; +register int len; +{ + register int r; + + for (;;) { + r = op(fd,buf,len); + if (r == -1) if (errno == error_intr) continue; + return r; + } +} + +static int getthis(s,buf,len) +register substdio *s; +register char *buf; +register int len; +{ + register int r; + register int q; + + r = s->p; + q = r - len; + if (q > 0) { r = len; s->p = q; } else s->p = 0; + byte_copy(buf,r,s->x + s->n); + s->n += r; + return r; +} + +int substdio_feed(s) +register substdio *s; +{ + register int r; + register int q; + + if (s->p) return s->p; + q = s->n; + r = oneread(s->op,s->fd,s->x,q); + if (r <= 0) return r; + s->p = r; + q -= r; + s->n = q; + if (q > 0) /* damn, gotta shift */ byte_copyr(s->x + q,r,s->x); + return r; +} + +int substdio_bget(s,buf,len) +register substdio *s; +register char *buf; +register int len; +{ + register int r; + + if (s->p > 0) return getthis(s,buf,len); + r = s->n; if (r <= len) return oneread(s->op,s->fd,buf,r); + r = substdio_feed(s); if (r <= 0) return r; + return getthis(s,buf,len); +} + +int substdio_get(s,buf,len) +register substdio *s; +register char *buf; +register int len; +{ + register int r; + + if (s->p > 0) return getthis(s,buf,len); + if (s->n <= len) return oneread(s->op,s->fd,buf,len); + r = substdio_feed(s); if (r <= 0) return r; + return getthis(s,buf,len); +} + +char *substdio_peek(s) +register substdio *s; +{ + return s->x + s->n; +} + +void substdio_seek(s,len) +register substdio *s; +register int len; +{ + s->n += len; + s->p -= len; +} diff --git a/substdio.c b/substdio.c @@ -0,0 +1,15 @@ +#include "substdio.h" + +void substdio_fdbuf(s,op,fd,buf,len) +register substdio *s; +register int (*op)(); +register int fd; +register char *buf; +register int len; +{ + s->x = buf; + s->fd = fd; + s->op = op; + s->p = 0; + s->n = len; +} diff --git a/substdio.h b/substdio.h @@ -0,0 +1,41 @@ +#ifndef SUBSTDIO_H +#define SUBSTDIO_H + +typedef struct substdio { + char *x; + int p; + int n; + int fd; + int (*op)(); +} substdio; + +#define SUBSTDIO_FDBUF(op,fd,buf,len) { (buf), 0, (len), (fd), (op) } + +extern void substdio_fdbuf(); + +extern int substdio_flush(); +extern int substdio_put(); +extern int substdio_bput(); +extern int substdio_putflush(); +extern int substdio_puts(); +extern int substdio_bputs(); +extern int substdio_putsflush(); + +extern int substdio_get(); +extern int substdio_bget(); +extern int substdio_feed(); + +extern char *substdio_peek(); +extern void substdio_seek(); + +#define substdio_fileno(s) ((s)->fd) + +#define SUBSTDIO_INSIZE 8192 +#define SUBSTDIO_OUTSIZE 8192 + +#define substdio_PEEK(s) ( (s)->x + (s)->n ) +#define substdio_SEEK(s,len) ( ( (s)->p -= (len) ) , ( (s)->n += (len) ) ) + +extern int substdio_copy(); + +#endif diff --git a/substdio_copy.c b/substdio_copy.c @@ -0,0 +1,18 @@ +#include "substdio.h" + +int substdio_copy(ssout,ssin) +register substdio *ssout; +register substdio *ssin; +{ + register int n; + register char *x; + + for (;;) { + n = substdio_feed(ssin); + if (n < 0) return -2; + if (!n) return 0; + x = substdio_PEEK(ssin); + if (substdio_put(ssout,x,n) == -1) return -3; + substdio_SEEK(ssin,n); + } +} diff --git a/substdo.c b/substdo.c @@ -0,0 +1,108 @@ +#include "substdio.h" +#include "str.h" +#include "byte.h" +#include "error.h" + +static int allwrite(op,fd,buf,len) +register int (*op)(); +register int fd; +register char *buf; +register int len; +{ + register int w; + + while (len) { + w = op(fd,buf,len); + if (w == -1) { + if (errno == error_intr) continue; + return -1; /* note that some data may have been written */ + } + if (w == 0) ; /* luser's fault */ + buf += w; + len -= w; + } + return 0; +} + +int substdio_flush(s) +register substdio *s; +{ + register int p; + + p = s->p; + if (!p) return 0; + s->p = 0; + return allwrite(s->op,s->fd,s->x,p); +} + +int substdio_bput(s,buf,len) +register substdio *s; +register char *buf; +register int len; +{ + register int n; + + while (len > (n = s->n - s->p)) { + byte_copy(s->x + s->p,n,buf); s->p += n; buf += n; len -= n; + if (substdio_flush(s) == -1) return -1; + } + /* now len <= s->n - s->p */ + byte_copy(s->x + s->p,len,buf); + s->p += len; + return 0; +} + +int substdio_put(s,buf,len) +register substdio *s; +register char *buf; +register int len; +{ + register int n; + + n = s->n; + if (len > n - s->p) { + if (substdio_flush(s) == -1) return -1; + /* now s->p == 0 */ + if (n < SUBSTDIO_OUTSIZE) n = SUBSTDIO_OUTSIZE; + while (len > s->n) { + if (n > len) n = len; + if (allwrite(s->op,s->fd,buf,n) == -1) return -1; + buf += n; + len -= n; + } + } + /* now len <= s->n - s->p */ + byte_copy(s->x + s->p,len,buf); + s->p += len; + return 0; +} + +int substdio_putflush(s,buf,len) +register substdio *s; +register char *buf; +register int len; +{ + if (substdio_flush(s) == -1) return -1; + return allwrite(s->op,s->fd,buf,len); +} + +int substdio_bputs(s,buf) +register substdio *s; +register char *buf; +{ + return substdio_bput(s,buf,str_len(buf)); +} + +int substdio_puts(s,buf) +register substdio *s; +register char *buf; +{ + return substdio_put(s,buf,str_len(buf)); +} + +int substdio_putsflush(s,buf) +register substdio *s; +register char *buf; +{ + return substdio_putflush(s,buf,str_len(buf)); +} diff --git a/tcp-env.1 b/tcp-env.1 @@ -0,0 +1,67 @@ +.TH tcp-env 1 +.SH NAME +tcp-env \- set up TCP-related environment variables +.SH SYNOPSIS +.B tcp-env +[ +.B \-rR +] +[ +.B \-t\fItimeout +] +.I program +[ +.I arg ... +] +.SH DESCRIPTION +The input for +.B tcp-env +must be a TCP connection. +.B tcp-env +finds out information about that connection, +puts the information into several environment variables +as described in +.B tcp-environ(5), +and runs +.I program +with the given arguments. + +Usually +.B tcp-env +is run from +.BR inetd . +It might instead be run from another server +that already sets up the right environment variables; +if +.B PROTO +is set to +.B TCP +when +.B tcp-env +is invoked, +.B tcp-env +assumes that all the other variables are set up properly, +and it does not check whether the input is a TCP connection. +.SH OPTIONS +.TP +.B \-r +(Default.) +Attempt to obtain +.B TCPREMOTEINFO +from the remote host. +.TP +.B \-R +Do not attempt to obtain +.B TCPREMOTEINFO +from the remote host. +.TP +.B \-t\fItimeout +Give up on the +.B TCPREMOTEINFO +connection attempt after +.I timeout +seconds. +Default: 30. +.SH "SEE ALSO" +tcp-environ(5), +inetd(8) diff --git a/tcp-env.c b/tcp-env.c @@ -0,0 +1,129 @@ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/param.h> +#include <netinet/in.h> +#include "sig.h" +#include "stralloc.h" +#include "str.h" +#include "env.h" +#include "fmt.h" +#include "scan.h" +#include "subgetopt.h" +#include "ip.h" +#include "dns.h" +#include "byte.h" +#include "remoteinfo.h" +#include "exit.h" +#include "case.h" + +void die() { _exit(111); } + +struct sockaddr_in salocal; +unsigned long localport; +struct ip_address iplocal; +stralloc localname = {0}; + +struct sockaddr_in saremote; +unsigned long remoteport; +struct ip_address ipremote; +stralloc remotename = {0}; + +char temp[IPFMT + FMT_ULONG]; + +void main(argc,argv) +int argc; +char *argv[]; +{ + int dummy; + char *proto; + int opt; + int flagremoteinfo; + unsigned long timeout; + + sig_pipeignore(); + + flagremoteinfo = 1; + timeout = 30; + while ((opt = sgopt(argc,argv,"rRt:")) != sgoptdone) + switch(opt) + { + case 'r': flagremoteinfo = 1; break; + case 'R': flagremoteinfo = 0; break; + case 't': scan_ulong(sgoptarg,&timeout); break; + } + + argv += sgoptind; + argc -= sgoptind; + + if (argc < 1) die(); + if (!env_init()) die(); + + proto = env_get("PROTO"); + if (!proto || str_diff(proto,"TCP")) + { + if (!env_put("PROTO=TCP")) die(); + + dummy = sizeof(salocal); + if (getsockname(0,(struct sockaddr *) &salocal,&dummy) == -1) die(); + + localport = ntohs(salocal.sin_port); + temp[fmt_ulong(temp,localport)] = 0; + if (!env_put2("TCPLOCALPORT",temp)) die(); + + byte_copy(&iplocal,4,&salocal.sin_addr); + temp[ip_fmt(temp,&iplocal)] = 0; + if (!env_put2("TCPLOCALIP",temp)) die(); + + switch(dns_ptr(&localname,&iplocal)) + { + case DNS_MEM: die(); + case DNS_SOFT: + if (!stralloc_copys(&localname,"softdnserror")) die(); + case 0: + if (!stralloc_0(&localname)) die(); + case_lowers(localname.s); + if (!env_put2("TCPLOCALHOST",localname.s)) die(); + break; + default: + if (!env_unset("TCPLOCALHOST")) die(); + } + + dummy = sizeof(saremote); + if (getpeername(0,(struct sockaddr *) &saremote,&dummy) == -1) die(); + + remoteport = ntohs(saremote.sin_port); + temp[fmt_ulong(temp,remoteport)] = 0; + if (!env_put2("TCPREMOTEPORT",temp)) die(); + + byte_copy(&ipremote,4,&saremote.sin_addr); + temp[ip_fmt(temp,&ipremote)] = 0; + if (!env_put2("TCPREMOTEIP",temp)) die(); + + switch(dns_ptr(&remotename,&ipremote)) + { + case DNS_MEM: die(); + case DNS_SOFT: + if (!stralloc_copys(&remotename,"softdnserror")) die(); + case 0: + if (!stralloc_0(&remotename)) die(); + case_lowers(remotename.s); + if (!env_put2("TCPREMOTEHOST",remotename.s)) die(); + break; + default: + if (!env_unset("TCPREMOTEHOST")) die(); + } + + if (!env_unset("TCPREMOTEINFO")) die(); + if (flagremoteinfo) + { + char *rinfo; + rinfo = remoteinfo_get(&ipremote,remoteport,&iplocal,localport,(int) timeout); + if (rinfo) + if (!env_put2("TCPREMOTEINFO",rinfo)) die(); + } + } + + sig_pipedefault(); + execvp(*argv,argv); + die(); +} diff --git a/tcp-environ.5 b/tcp-environ.5 @@ -0,0 +1,62 @@ +.TH tcp-environ 5 +.SH NAME +tcp-environ \- TCP-related environment variables +.SH DESCRIPTION +The following environment variables +describe a TCP connection. +They are set up by +.BR tcp-env , +.BR tcpclient , +and +.BR tcpserver . +Note that +.BR TCPLOCALHOST , +.BR TCPREMOTEHOST , +and +.B TCPREMOTEINFO +can contain arbitrary characters. +.TP 5 +PROTO +The string +.BR TCP . +.TP 5 +TCPLOCALHOST +The domain name of the local host, +with uppercase letters converted to lowercase. +If there is no currently available domain name +for the local IP address, +.B TCPLOCALHOST +is not set. +.TP 5 +TCPLOCALIP +The IP address of the local host, in dotted-decimal form. +.TP 5 +TCPLOCALPORT +The local TCP port number, in decimal. +.TP 5 +TCPREMOTEHOST +The domain name of the remote host, +with uppercase letters converted to lowercase. +If there is no currently available domain name +for the remote IP address, +.B TCPREMOTEHOST +is not set. +.TP 5 +TCPREMOTEINFO +A connection-specific string, perhaps a username, +supplied by the remote host +via 931/1413/IDENT/TAP. +If the remote host did not supply connection information, +.B TCPREMOTEINFO +is not set. +.TP 5 +TCPREMOTEIP +The IP address of the remote host. +.TP 5 +TCPREMOTEPORT +The remote TCP port number. +.SH "SEE ALSO" +tcpclient(1), +tcpserver(1), +tcp-env(1), +tcp(4) diff --git a/tcpto.c b/tcpto.c @@ -0,0 +1,165 @@ +#include "tcpto.h" +#include "open.h" +#include "lock.h" +#include "seek.h" +#include "now.h" +#include "ip.h" +#include "byte.h" +#include "datetime.h" +#include "readwrite.h" + +char tcpto_buf[1024]; + +static int flagwasthere; +static int fdlock; + +static int getbuf() +{ + int r; + int fd; + + fdlock = open_write("queue/lock/tcpto"); + if (fdlock == -1) return 0; + fd = open_read("queue/lock/tcpto"); + if (fd == -1) { close(fdlock); return 0; } + if (lock_ex(fdlock) == -1) { close(fdlock); close(fd); return 0; } + r = read(fd,tcpto_buf,sizeof(tcpto_buf)); + close(fd); + if (r < 0) { close(fdlock); return 0; } + r >>= 4; + if (!r) close(fdlock); + return r; +} + +int tcpto(ip) struct ip_address *ip; +{ + int n; + int i; + char *record; + datetime_sec when; + + flagwasthere = 0; + + n = getbuf(); + if (!n) return 0; + close(fdlock); + + record = tcpto_buf; + for (i = 0;i < n;++i) + { + if (byte_equal(ip->d,4,record)) + { + flagwasthere = 1; + if (record[4] >= 2) + { + when = (unsigned long) (unsigned char) record[11]; + when = (when << 8) + (unsigned long) (unsigned char) record[10]; + when = (when << 8) + (unsigned long) (unsigned char) record[9]; + when = (when << 8) + (unsigned long) (unsigned char) record[8]; + + if (now() - when < ((60 + (getpid() & 31)) << 6)) + return 1; + } + return 0; + } + record += 16; + } + return 0; +} + +void tcpto_err(ip,flagerr) struct ip_address *ip; int flagerr; +{ + int n; + int i; + char *record; + datetime_sec when; + datetime_sec firstwhen; + int firstpos; + datetime_sec lastwhen; + + if (!flagerr) + if (!flagwasthere) + return; /* could have been added, but not worth the effort to check */ + + n = getbuf(); + if (!n) return; + + record = tcpto_buf; + for (i = 0;i < n;++i) + { + if (byte_equal(ip->d,4,record)) + { + if (!flagerr) + record[4] = 0; + else + { + lastwhen = (unsigned long) (unsigned char) record[11]; + lastwhen = (lastwhen << 8) + (unsigned long) (unsigned char) record[10]; + lastwhen = (lastwhen << 8) + (unsigned long) (unsigned char) record[9]; + lastwhen = (lastwhen << 8) + (unsigned long) (unsigned char) record[8]; + when = now(); + + if (record[4] && (when < 120 + lastwhen)) { close(fdlock); return; } + + if (++record[4] > 10) record[4] = 10; + record[8] = when; when >>= 8; + record[9] = when; when >>= 8; + record[10] = when; when >>= 8; + record[11] = when; + } + if (seek_set(fdlock,i << 4) == 0) + if (write(fdlock,record,16) < 16) + ; /*XXX*/ + close(fdlock); + return; + } + record += 16; + } + + if (!flagerr) { close(fdlock); return; } + + record = tcpto_buf; + for (i = 0;i < n;++i) + { + if (!record[4]) break; + record += 16; + } + + if (i >= n) + { + firstpos = -1; + record = tcpto_buf; + for (i = 0;i < n;++i) + { + when = (unsigned long) (unsigned char) record[11]; + when = (when << 8) + (unsigned long) (unsigned char) record[10]; + when = (when << 8) + (unsigned long) (unsigned char) record[9]; + when = (when << 8) + (unsigned long) (unsigned char) record[8]; + when += (record[4] << 10); + if ((firstpos < 0) || (when < firstwhen)) + { + firstpos = i; + firstwhen = when; + } + record += 16; + } + i = firstpos; + } + + if (i >= 0) + { + record = tcpto_buf + (i << 4); + byte_copy(record,4,ip->d); + when = now(); + record[8] = when; when >>= 8; + record[9] = when; when >>= 8; + record[10] = when; when >>= 8; + record[11] = when; + record[4] = 1; + if (seek_set(fdlock,i << 4) == 0) + if (write(fdlock,record,16) < 16) + ; /*XXX*/ + } + + close(fdlock); +} diff --git a/tcpto.h b/tcpto.h @@ -0,0 +1,8 @@ +#ifndef TCPTO_H +#define TCPTO_H + +extern int tcpto(); +extern void tcpto_err(); +extern void tcpto_clean(); + +#endif diff --git a/tcpto_clean.c b/tcpto_clean.c @@ -0,0 +1,20 @@ +#include "tcpto.h" +#include "open.h" +#include "substdio.h" +#include "readwrite.h" + +char tcpto_cleanbuf[1024]; + +void tcpto_clean() /* running from queue/mess */ +{ + int fd; + int i; + substdio ss; + + fd = open_write("../lock/tcpto"); + if (fd == -1) return; + substdio_fdbuf(&ss,write,fd,tcpto_cleanbuf,sizeof(tcpto_cleanbuf)); + for (i = 0;i < sizeof(tcpto_cleanbuf);++i) substdio_put(&ss,"",1); + substdio_flush(&ss); /* if it fails, bummer */ + close(fd); +} diff --git a/timeoutconn.c b/timeoutconn.c @@ -0,0 +1,59 @@ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include "ndelay.h" +#include "select.h" +#include "error.h" +#include "readwrite.h" +#include "ip.h" +#include "byte.h" +#include "timeoutconn.h" + +int timeoutconn(s,ip,port,timeout) +int s; +struct ip_address *ip; +unsigned int port; +int timeout; +{ + char ch; + struct sockaddr_in sin; + char *x; + fd_set wfds; + struct timeval tv; + + byte_zero(&sin,sizeof(sin)); + byte_copy(&sin.sin_addr,4,ip); + x = (char *) &sin.sin_port; + x[1] = port; port >>= 8; x[0] = port; + sin.sin_family = AF_INET; + + if (ndelay_on(s) == -1) return -1; + + /* XXX: could bind s */ + + if (connect(s,(struct sockaddr *) &sin,sizeof(sin)) == 0) { + ndelay_off(s); + return 0; + } + if ((errno != error_inprogress) && (errno != error_wouldblock)) return -1; + + FD_ZERO(&wfds); + FD_SET(s,&wfds); + tv.tv_sec = timeout; tv.tv_usec = 0; + + if (select(s + 1,(fd_set *) 0,&wfds,(fd_set *) 0,&tv) == -1) return -1; + if (FD_ISSET(s,&wfds)) { + int dummy; + dummy = sizeof(sin); + if (getpeername(s,(struct sockaddr *) &sin,&dummy) == -1) { + read(s,&ch,1); + return -1; + } + ndelay_off(s); + return 0; + } + + errno = error_timeout; /* note that connect attempt is continuing */ + return -1; +} diff --git a/timeoutconn.h b/timeoutconn.h @@ -0,0 +1,6 @@ +#ifndef TIMEOUTCONN_H +#define TIMEOUTCONN_H + +extern int timeoutconn(); + +#endif diff --git a/timeoutread.c b/timeoutread.c @@ -0,0 +1,25 @@ +#include "timeoutread.h" +#include "select.h" +#include "error.h" +#include "readwrite.h" + +int timeoutread(fdt,buf,len) int fdt; char *buf; int len; +{ + fd_set rfds; + struct timeval tv; + int fd; + + tv.tv_sec = (fdt >> 10); + tv.tv_usec = 0; + + fd = (fdt & 1023); + FD_ZERO(&rfds); + FD_SET(fd,&rfds); + + if (select(fd + 1,&rfds,(fd_set *) 0,(fd_set *) 0,&tv) == -1) return -1; + if (FD_ISSET(fd,&rfds)) return read(fd,buf,len); + + shutdown(fd,0); + errno = error_timeout; + return -1; +} diff --git a/timeoutread.h b/timeoutread.h @@ -0,0 +1,8 @@ +#ifndef TIMEOUTREAD_H +#define TIMEOUTREAD_H + +#define TIMEOUTREAD(s,fd) (((s) << 10) | (fd)) + +extern int timeoutread(); + +#endif diff --git a/timeoutwrite.c b/timeoutwrite.c @@ -0,0 +1,25 @@ +#include "timeoutwrite.h" +#include "select.h" +#include "error.h" +#include "readwrite.h" + +int timeoutwrite(fdt,buf,len) int fdt; char *buf; int len; +{ + fd_set wfds; + struct timeval tv; + int fd; + + tv.tv_sec = (fdt >> 10); + tv.tv_usec = 0; + + fd = (fdt & 1023); + FD_ZERO(&wfds); + FD_SET(fd,&wfds); + + if (select(fd + 1,(fd_set *) 0,&wfds,(fd_set *) 0,&tv) == -1) return -1; + if (FD_ISSET(fd,&wfds)) return write(fd,buf,len); + + shutdown(fd,1); + errno = error_timeout; + return -1; +} diff --git a/timeoutwrite.h b/timeoutwrite.h @@ -0,0 +1,8 @@ +#ifndef TIMEOUTWRITE_H +#define TIMEOUTWRITE_H + +#define TIMEOUTWRITE(s,fd) (((s) << 10) | (fd)) + +extern int timeoutwrite(); + +#endif diff --git a/token822.c b/token822.c @@ -0,0 +1,511 @@ +#include "stralloc.h" +#include "alloc.h" +#include "str.h" +#include "token822.h" +#include "gen_allocdefs.h" + +static struct token822 comma = { TOKEN822_COMMA }; + +void token822_reverse(ta) +token822_alloc *ta; +{ + int i; + int n; + struct token822 temp; + + n = ta->len - 1; + for (i = 0;i + i < n;++i) + { + temp = ta->t[i]; + ta->t[i] = ta->t[n - i]; + ta->t[n - i] = temp; + } +} + +GEN_ALLOC_ready(token822_alloc,struct token822,t,len,a,i,n,x,30,token822_ready) +GEN_ALLOC_readyplus(token822_alloc,struct token822,t,len,a,i,n,x,30,token822_readyplus) +GEN_ALLOC_append(token822_alloc,struct token822,t,len,a,i,n,x,30,token822_readyplus,token822_append) + +static int needspace(t1,t2) +int t1; +int t2; +{ + if (!t1) return 0; + if (t1 == TOKEN822_COLON) return 1; + if (t1 == TOKEN822_COMMA) return 1; + if (t2 == TOKEN822_LEFT) return 1; + switch(t1) + { + case TOKEN822_ATOM: case TOKEN822_LITERAL: + case TOKEN822_QUOTE: case TOKEN822_COMMENT: + switch(t2) + { + case TOKEN822_ATOM: case TOKEN822_LITERAL: + case TOKEN822_QUOTE: case TOKEN822_COMMENT: + return 1; + } + } + return 0; +} + +static int atomok(ch) +char ch; +{ + switch(ch) + { + case ' ': case '\t': case '\r': case '\n': + case '(': case '[': case '"': + case '<': case '>': case ';': case ':': + case '@': case ',': case '.': + return 0; + } + return 1; +} + +static void atomcheck(t) +struct token822 *t; +{ + int i; + char ch; + for (i = 0;i < t->slen;++i) + { + ch = t->s[i]; + if ((ch < 32) || (ch > 126) || (ch == ')') || (ch == ']') || (ch == '\\')) + { + t->type = TOKEN822_QUOTE; + return; + } + } +} + +int token822_unparse(sa,ta,linelen) +stralloc *sa; +token822_alloc *ta; +unsigned int linelen; +{ + struct token822 *t; + int len; + int ch; + int i; + int j; + int lasttype; + int newtype; + char *s; + char *lineb; + char *linee; + + len = 0; + lasttype = 0; + for (i = 0;i < ta->len;++i) + { + t = ta->t + i; + newtype = t->type; + if (needspace(lasttype,newtype)) + ++len; + lasttype = newtype; + switch(newtype) + { + case TOKEN822_COMMA: + len += 3; break; + case TOKEN822_AT: case TOKEN822_DOT: case TOKEN822_LEFT: case TOKEN822_RIGHT: + case TOKEN822_SEMI: case TOKEN822_COLON: + ++len; break; + case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL: case TOKEN822_COMMENT: + if (t->type != TOKEN822_ATOM) len += 2; + for (j = 0;j < t->slen;++j) + switch(ch = t->s[j]) + { + case '"': case '[': case ']': case '(': case ')': + case '\\': case '\r': case '\n': ++len; + default: ++len; + } + break; + } + } + len += 2; + + if (!stralloc_ready(sa,len)) + return -1; + + s = sa->s; + lineb = s; + linee = 0; + + lasttype = 0; + for (i = 0;i < ta->len;++i) + { + t = ta->t + i; + newtype = t->type; + if (needspace(lasttype,newtype)) + *s++ = ' '; + lasttype = newtype; + switch(newtype) + { + case TOKEN822_COMMA: + *s++ = ','; +#define NSUW \ + s[0] = '\n'; s[1] = ' '; \ + if (linee && (!linelen || (s - lineb <= linelen))) \ + { while (linee < s) { linee[0] = linee[2]; ++linee; } linee -= 2; } \ + else { if (linee) lineb = linee + 1; linee = s; s += 2; } + NSUW + break; + case TOKEN822_AT: *s++ = '@'; break; + case TOKEN822_DOT: *s++ = '.'; break; + case TOKEN822_LEFT: *s++ = '<'; break; + case TOKEN822_RIGHT: *s++ = '>'; break; + case TOKEN822_SEMI: *s++ = ';'; break; + case TOKEN822_COLON: *s++ = ':'; break; + case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL: case TOKEN822_COMMENT: + if (t->type == TOKEN822_QUOTE) *s++ = '"'; + if (t->type == TOKEN822_LITERAL) *s++ = '['; + if (t->type == TOKEN822_COMMENT) *s++ = '('; + for (j = 0;j < t->slen;++j) + switch(ch = t->s[j]) + { + case '"': case '[': case ']': case '(': case ')': + case '\\': case '\r': case '\n': *s++ = '\\'; + default: *s++ = ch; + } + if (t->type == TOKEN822_QUOTE) *s++ = '"'; + if (t->type == TOKEN822_LITERAL) *s++ = ']'; + if (t->type == TOKEN822_COMMENT) *s++ = ')'; + break; + } + } + NSUW + --s; + sa->len = s - sa->s; + return 1; +} + +int token822_unquote(sa,ta) +stralloc *sa; +token822_alloc *ta; +{ + struct token822 *t; + int len; + int i; + int j; + char *s; + + len = 0; + for (i = 0;i < ta->len;++i) + { + t = ta->t + i; + switch(t->type) + { + case TOKEN822_COMMA: case TOKEN822_AT: case TOKEN822_DOT: case TOKEN822_LEFT: + case TOKEN822_RIGHT: case TOKEN822_SEMI: case TOKEN822_COLON: + ++len; break; + case TOKEN822_LITERAL: + len += 2; + case TOKEN822_ATOM: case TOKEN822_QUOTE: + len += t->slen; + } + } + + if (!stralloc_ready(sa,len)) + return -1; + + s = sa->s; + + for (i = 0;i < ta->len;++i) + { + t = ta->t + i; + switch(t->type) + { + case TOKEN822_COMMA: *s++ = ','; break; + case TOKEN822_AT: *s++ = '@'; break; + case TOKEN822_DOT: *s++ = '.'; break; + case TOKEN822_LEFT: *s++ = '<'; break; + case TOKEN822_RIGHT: *s++ = '>'; break; + case TOKEN822_SEMI: *s++ = ';'; break; + case TOKEN822_COLON: *s++ = ':'; break; + case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL: + if (t->type == TOKEN822_LITERAL) *s++ = '['; + for (j = 0;j < t->slen;++j) + *s++ = t->s[j]; + if (t->type == TOKEN822_LITERAL) *s++ = ']'; + break; + case TOKEN822_COMMENT: break; + } + } + sa->len = s - sa->s; + return 1; +} + +int token822_parse(ta,sa,buf) +token822_alloc *ta; +stralloc *sa; +stralloc *buf; +{ + int i; + int salen; + int level; + struct token822 *t; + int numtoks; + int numchars; + char *cbuf; + + salen = sa->len; + + numchars = 0; + numtoks = 0; + for (i = 0;i < salen;++i) + switch(sa->s[i]) + { + case '.': case ',': case '@': case '<': case '>': case ':': case ';': + ++numtoks; break; + case ' ': case '\t': case '\r': case '\n': break; + case ')': case ']': return 0; + /* other control chars and non-ASCII chars are also bad, in theory */ + case '(': + level = 1; + while (level) + { + if (++i >= salen) return 0; + switch(sa->s[i]) + { + case '(': ++level; break; + case ')': --level; break; + case '\\': if (++i >= salen) return 0; + default: ++numchars; + } + } + ++numtoks; + break; + case '"': + level = 1; + while (level) + { + if (++i >= salen) return 0; + switch(sa->s[i]) + { + case '"': --level; break; + case '\\': if (++i >= salen) return 0; + default: ++numchars; + } + } + ++numtoks; + break; + case '[': + level = 1; + while (level) + { + if (++i >= salen) return 0; + switch(sa->s[i]) + { + case ']': --level; break; + case '\\': if (++i >= salen) return 0; + default: ++numchars; + } + } + ++numtoks; + break; + default: + do + { + ++numchars; + if (++i >= salen) + break; + } + while (atomok(sa->s[i])); + --i; + ++numtoks; + } + + if (!token822_ready(ta,numtoks)) + return -1; + if (!stralloc_ready(buf,numchars)) + return -1; + cbuf = buf->s; + ta->len = numtoks; + + t = ta->t; + for (i = 0;i < salen;++i) + switch(sa->s[i]) + { + case '.': t->type = TOKEN822_DOT; ++t; break; + case ',': t->type = TOKEN822_COMMA; ++t; break; + case '@': t->type = TOKEN822_AT; ++t; break; + case '<': t->type = TOKEN822_LEFT; ++t; break; + case '>': t->type = TOKEN822_RIGHT; ++t; break; + case ':': t->type = TOKEN822_COLON; ++t; break; + case ';': t->type = TOKEN822_SEMI; ++t; break; + case ' ': case '\t': case '\r': case '\n': break; + case '(': + t->type = TOKEN822_COMMENT; t->s = cbuf; t->slen = 0; + level = 1; + while (level) + { + ++i; /* assert: < salen */ + switch(sa->s[i]) + { + case '(': ++level; break; + case ')': --level; break; + case '\\': ++i; /* assert: < salen */ + default: *cbuf++ = sa->s[i]; ++t->slen; + } + } + ++t; + break; + case '"': + t->type = TOKEN822_QUOTE; t->s = cbuf; t->slen = 0; + level = 1; + while (level) + { + ++i; /* assert: < salen */ + switch(sa->s[i]) + { + case '"': --level; break; + case '\\': ++i; /* assert: < salen */ + default: *cbuf++ = sa->s[i]; ++t->slen; + } + } + ++t; + break; + case '[': + t->type = TOKEN822_LITERAL; t->s = cbuf; t->slen = 0; + level = 1; + while (level) + { + ++i; /* assert: < salen */ + switch(sa->s[i]) + { + case ']': --level; break; + case '\\': ++i; /* assert: < salen */ + default: *cbuf++ = sa->s[i]; ++t->slen; + } + } + ++t; + break; + default: + t->type = TOKEN822_ATOM; t->s = cbuf; t->slen = 0; + do + { + *cbuf++ = sa->s[i]; ++t->slen; + if (++i >= salen) + break; + } + while (atomok(sa->s[i])); + atomcheck(t); + --i; + ++t; + } + return 1; +} + +static int gotaddr(taout,taaddr,callback) +token822_alloc *taout; +token822_alloc *taaddr; +int (*callback)(); +{ + int i; + + if (callback(taaddr) != 1) + return 0; + + if (!token822_readyplus(taout,taaddr->len)) + return 0; + + for (i = 0;i < taaddr->len;++i) + taout->t[taout->len++] = taaddr->t[i]; + + taaddr->len = 0; + return 1; +} + +int token822_addrlist(taout,taaddr,ta,callback) +token822_alloc *taout; +token822_alloc *taaddr; +token822_alloc *ta; +int (*callback)(); +{ + struct token822 *t; + struct token822 *beginning; + int ingroup; + int wordok; + + taout->len = 0; + taaddr->len = 0; + + if (!token822_readyplus(taout,1)) return -1; + if (!token822_readyplus(taaddr,1)) return -1; + + ingroup = 0; + wordok = 1; + + beginning = ta->t + 2; + t = ta->t + ta->len - 1; + + /* rfc 822 address lists are easy to parse from right to left */ + +#define FLUSH if (taaddr->len) if (!gotaddr(taout,taaddr,callback)) return -1; +#define FLUSHCOMMA if (taaddr->len) { \ +if (!gotaddr(taout,taaddr,callback)) return -1; \ +if (!token822_append(taout,&comma)) return -1; } +#define ADDRLEFT if (!token822_append(taaddr,t--)) return -1; +#define OUTLEFT if (!token822_append(taout,t--)) return -1; + + while (t >= beginning) + { + switch(t->type) + { + case TOKEN822_SEMI: + FLUSHCOMMA + if (ingroup) return 0; + ingroup = 1; + wordok = 1; + break; + case TOKEN822_COLON: + FLUSH + if (!ingroup) return 0; + ingroup = 0; + while ((t >= beginning) && (t->type != TOKEN822_COMMA)) + OUTLEFT + if (t >= beginning) + OUTLEFT + wordok = 1; + continue; + case TOKEN822_RIGHT: + FLUSHCOMMA + OUTLEFT + while ((t >= beginning) && (t->type != TOKEN822_LEFT)) + ADDRLEFT + /* important to use address here even if it's empty: <> */ + if (!gotaddr(taout,taaddr,callback)) return -1; + if (t < beginning) return 0; + OUTLEFT + while ((t >= beginning) && ((t->type == TOKEN822_COMMENT) || (t->type == TOKEN822_ATOM) || (t->type == TOKEN822_QUOTE) || (t->type == TOKEN822_AT) || (t->type == TOKEN822_DOT))) + OUTLEFT + wordok = 0; + continue; + case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL: + if (!wordok) + FLUSHCOMMA + wordok = 0; + ADDRLEFT + continue; + case TOKEN822_COMMENT: + /* comment is lexically a space; shouldn't affect wordok */ + break; + case TOKEN822_COMMA: + FLUSH + wordok = 1; + break; + default: + wordok = 1; + ADDRLEFT + continue; + } + OUTLEFT + } + FLUSH + ++t; + while (t > ta->t) + if (!token822_append(taout,--t)) return -1; + + token822_reverse(taout); + return 1; +} diff --git a/token822.h b/token822.h @@ -0,0 +1,37 @@ +#ifndef TOKEN822_H +#define TOKEN822_H + +struct token822 + { + int type; + char *s; + int slen; + } +; + +#include "gen_alloc.h" +GEN_ALLOC_typedef(token822_alloc,struct token822,t,len,a) + +extern int token822_parse(); +extern int token822_addrlist(); +extern int token822_unquote(); +extern int token822_unparse(); +extern void token822_free(); +extern void token822_reverse(); +extern int token822_ready(); +extern int token822_readyplus(); +extern int token822_append(); + +#define TOKEN822_ATOM 1 +#define TOKEN822_QUOTE 2 +#define TOKEN822_LITERAL 3 +#define TOKEN822_COMMENT 4 +#define TOKEN822_LEFT 5 +#define TOKEN822_RIGHT 6 +#define TOKEN822_AT 7 +#define TOKEN822_COMMA 8 +#define TOKEN822_SEMI 9 +#define TOKEN822_COLON 10 +#define TOKEN822_DOT 11 + +#endif diff --git a/trigger.c b/trigger.c @@ -0,0 +1,41 @@ +#include "select.h" +#include "open.h" +#include "trigger.h" +#include "hasnpbg1.h" + +static int fd = -1; +#ifdef HASNAMEDPIPEBUG1 +static int fdw = -1; +#endif + +void trigger_set() +{ + if (fd != -1) + close(fd); +#ifdef HASNAMEDPIPEBUG1 + if (fdw != -1) + close(fdw); +#endif + fd = open_read("lock/trigger"); +#ifdef HASNAMEDPIPEBUG1 + fdw = open_write("lock/trigger"); +#endif +} + +void trigger_selprep(nfds,rfds) +int *nfds; +fd_set *rfds; +{ + if (fd != -1) + { + FD_SET(fd,rfds); + if (*nfds < fd + 1) *nfds = fd + 1; + } +} + +int trigger_pulled(rfds) +fd_set *rfds; +{ + if (fd != -1) if (FD_ISSET(fd,rfds)) return 1; + return 0; +} diff --git a/trigger.h b/trigger.h @@ -0,0 +1,8 @@ +#ifndef TRIGGER_H +#define TRIGGER_H + +extern void trigger_set(); +extern void trigger_selprep(); +extern int trigger_pulled(); + +#endif diff --git a/triggerpull.c b/triggerpull.c @@ -0,0 +1,16 @@ +#include "ndelay.h" +#include "open.h" +#include "triggerpull.h" + +void triggerpull() +{ + int fd; + + fd = open_write("lock/trigger"); + if (fd >= 0) + { + ndelay_on(fd); + write(fd,"",1); /* if it fails, bummer */ + close(fd); + } +} diff --git a/triggerpull.h b/triggerpull.h @@ -0,0 +1,6 @@ +#ifndef TRIGGERPULL_H +#define TRIGGERPULL_H + +extern void triggerpull(); + +#endif diff --git a/trycpp.c b/trycpp.c @@ -0,0 +1,7 @@ +void main() +{ +#ifdef NeXT + printf("nextstep\n"); exit(0); +#endif + printf("unknown\n"); exit(0); +} diff --git a/trydrent.c b/trydrent.c @@ -0,0 +1,8 @@ +#include <sys/types.h> +#include <dirent.h> + +void foo() +{ + DIR *dir; + struct dirent *d; +} diff --git a/tryflock.c b/tryflock.c @@ -0,0 +1,8 @@ +#include <sys/types.h> +#include <sys/file.h> +#include <fcntl.h> + +void main() +{ + flock(0,LOCK_EX | LOCK_UN | LOCK_NB); +} diff --git a/trylsock.c b/trylsock.c @@ -0,0 +1,4 @@ +main() +{ + ; +} diff --git a/trymkffo.c b/trymkffo.c @@ -0,0 +1,7 @@ +#include <sys/types.h> +#include <sys/stat.h> + +void main() +{ + mkfifo("temp-trymkffo",0); +} diff --git a/trynpbg1.c b/trynpbg1.c @@ -0,0 +1,26 @@ +#include "select.h" +#include "open.h" +#include "fifo.h" + +#define FN "temp-trynpbg1.fifo" + +void main() +{ + int flagbug; + struct timeval instant; + fd_set rfds; + + flagbug = 0; + if (fifo_make(FN,0600) != -1) { + close(0); + if (open_read(FN) == 0) { + FD_ZERO(&rfds); + FD_SET(0,&rfds); + instant.tv_sec = instant.tv_usec = 0; + if (select(1,&rfds,(fd_set *) 0,(fd_set *) 0,&instant) > 0) + flagbug = 1; + } + unlink(FN); + } + _exit(!flagbug); +} diff --git a/tryrsolv.c b/tryrsolv.c @@ -0,0 +1,4 @@ +main() +{ + ; +} diff --git a/trysalen.c b/trysalen.c @@ -0,0 +1,11 @@ +#include <sys/types.h> +#include <sys/param.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +void foo() +{ + struct sockaddr sa; + sa.sa_len = 0; +} diff --git a/trysgact.c b/trysgact.c @@ -0,0 +1,10 @@ +#include <signal.h> + +void main() +{ + struct sigaction sa; + sa.sa_handler = 0; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(0,&sa,(struct sigaction *) 0); +} diff --git a/trysgprm.c b/trysgprm.c @@ -0,0 +1,10 @@ +#include <signal.h> + +void main() +{ + sigset_t ss; + + sigemptyset(&ss); + sigaddset(&ss,SIGCHLD); + sigprocmask(SIG_SETMASK,&ss,(sigset_t *) 0); +} diff --git a/tryshsgr.c b/tryshsgr.c @@ -0,0 +1,14 @@ +void main() +{ + short x[4]; + + x[0] = x[1] = 1; + if (getgroups(1,x) == 0) if (setgroups(1,x) == -1) _exit(1); + + if (getgroups(1,x) == -1) _exit(1); + if (x[1] != 1) _exit(1); + x[1] = 2; + if (getgroups(1,x) == -1) _exit(1); + if (x[1] != 2) _exit(1); + _exit(0); +} diff --git a/trysysel.c b/trysysel.c @@ -0,0 +1,8 @@ +#include <sys/types.h> +#include <sys/time.h> +#include <sys/select.h> /* SVR4 silliness */ + +void foo() +{ + ; +} diff --git a/trysyslog.c b/trysyslog.c @@ -0,0 +1,9 @@ +#include <sys/types.h> +#include <sys/time.h> +#include <syslog.h> + +main() +{ + openlog("foo",0,LOG_MAIL); + syslog(0,"foo"); +} diff --git a/tryulong32.c b/tryulong32.c @@ -0,0 +1,11 @@ +void main() +{ + unsigned long u; + u = 1; + u += u; u += u; u += u; u += u; u += u; u += u; u += u; u += u; + u += u; u += u; u += u; u += u; u += u; u += u; u += u; u += u; + u += u; u += u; u += u; u += u; u += u; u += u; u += u; u += u; + u += u; u += u; u += u; u += u; u += u; u += u; u += u; u += u; + if (!u) _exit(0); + _exit(1); +} diff --git a/tryvfork.c b/tryvfork.c @@ -0,0 +1,4 @@ +void main() +{ + vfork(); +} diff --git a/trywaitp.c b/trywaitp.c @@ -0,0 +1,7 @@ +#include <sys/types.h> +#include <sys/wait.h> + +void main() +{ + waitpid(0,0,0); +} diff --git a/uint32.h1 b/uint32.h1 @@ -0,0 +1,6 @@ +#ifndef UINT32_H +#define UINT32_H + +typedef unsigned int uint32; + +#endif diff --git a/uint32.h2 b/uint32.h2 @@ -0,0 +1,6 @@ +#ifndef UINT32_H +#define UINT32_H + +typedef unsigned long uint32; + +#endif diff --git a/wait.3 b/wait.3 @@ -0,0 +1,93 @@ +.TH wait 3 +.SH NAME +wait \- check child process status +.SH SYNTAX +.B #include <wait.h> + +int \fBwait_nohang\fP(&\fIwstat\fR); +.br +int \fBwait_stop\fP(&\fIwstat\fR); +.br +int \fBwait_stopnohang\fP(&\fIwstat\fR); +.br +int \fBwait_pid\fP(&\fIwstat\fR,\fIpid\fR); + +int \fBwait_exitcode\fP(\fIwstat\fR); +.br +int \fBwait_crashed\fP(\fIwstat\fR); +.br +int \fBwait_stopped\fP(\fIwstat\fR); +.br +int \fBwait_stopsig\fP(\fIwstat\fR); + +int \fIpid\fR; +.br +int \fIwstat\fR; +.SH DESCRIPTION +.B wait_nohang +looks for zombies (child processes that have exited). +If it sees a zombie, +it eliminates the zombie, +puts the zombie's exit status into +.IR wstat , +and returns the zombie's process ID. +If there are several zombies, +.B wait_nohang +picks one. +If there are children but no zombies, +.B wait_nohang +returns 0. +If there are no children, +.B wait_nohang +returns -1, +setting +.B errno +appropriately. + +.B wait_stopnohang +is similar to +.BR wait_nohang , +but it also looks for children that have stopped. + +.B wait_stop +is similar to +.BR wait_stopnohang , +but if there are children it will pause waiting for one of them +to stop or exit. + +.B wait_pid +waits for child process +.I pid +to exit. +It eliminates any zombie that shows up in the meantime, +discarding the exit status. + +.B wait_stop +and +.B wait_pid +retry upon +.BR error_intr . +.SH "STATUS PARSING" +If the child stopped, +.B wait_stopped +is nonzero; +.B wait_stopsig +is the signal that caused the child to stop. + +If the child exited by crashing, +.B wait_stopped +is zero; +.B wait_crashed +is nonzero. + +If the child exited normally, +.B wait_stopped +is zero; +.B wait_crashed +is zero; +and +.B wait_exitcode +is the child's exit code. +.SH "SEE ALSO" +wait(2), +error(3) diff --git a/wait.h b/wait.h @@ -0,0 +1,14 @@ +#ifndef WAIT_H +#define WAIT_H + +extern int wait_pid(); +extern int wait_nohang(); +extern int wait_stop(); +extern int wait_stopnohang(); + +#define wait_crashed(w) ((w) & 127) +#define wait_exitcode(w) ((w) >> 8) +#define wait_stopsig(w) ((w) >> 8) +#define wait_stopped(w) (((w) & 127) == 127) + +#endif diff --git a/wait_nohang.c b/wait_nohang.c @@ -0,0 +1,12 @@ +#include <sys/types.h> +#include <sys/wait.h> +#include "haswaitp.h" + +int wait_nohang(wstat) int *wstat; +{ +#ifdef HASWAITPID + return waitpid(-1,wstat,WNOHANG); +#else + return wait3(wstat,WNOHANG,(struct rusage *) 0); +#endif +} diff --git a/wait_pid.c b/wait_pid.c @@ -0,0 +1,13 @@ +#include <sys/types.h> +#include <sys/wait.h> +#include "error.h" + +/* restriction: you must not care about any other child. */ +int wait_pid(wstat,pid) int *wstat; int pid; +{ + int r; + do + r = wait(wstat); + while ((r != pid) && ((r != -1) || (errno == error_intr))); + return r; +} diff --git a/warn-auto.sh b/warn-auto.sh @@ -0,0 +1,2 @@ +#!/bin/sh +# WARNING: This file was auto-generated. Do not edit! diff --git a/warn-shsgr b/warn-shsgr @@ -0,0 +1,3 @@ +Oops. Your getgroups() returned 0, and setgroups() failed; this means +that I can't reliably do my shsgr test. Please either ``make'' as root +or ``make'' while you're in one or more supplementary groups.