Initial CVS repository import (last CVS history was lost) v1
authorMatthew Mondor <mmondor@pulsar-zone.net>
Wed, 11 Dec 2002 10:18:51 +0000 (10:18 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Wed, 11 Dec 2002 10:18:51 +0000 (10:18 +0000)
116 files changed:
mmsoftware/LICENSE [new file with mode: 0644]
mmsoftware/TODO [new file with mode: 0644]
mmsoftware/bot/Makefile [new file with mode: 0644]
mmsoftware/bot/irc.txt [new file with mode: 0644]
mmsoftware/bot/zenbot.c [new file with mode: 0644]
mmsoftware/clean.sh [new file with mode: 0755]
mmsoftware/install.sh [new file with mode: 0755]
mmsoftware/make.sh [new file with mode: 0755]
mmsoftware/mmftpd/ChangeLog [new file with mode: 0644]
mmsoftware/mmftpd/README [new file with mode: 0644]
mmsoftware/mmftpd/clean.sh [new file with mode: 0755]
mmsoftware/mmftpd/etc/mmftpd.conf [new file with mode: 0644]
mmsoftware/mmftpd/etc/mmftpdpasswd [new file with mode: 0644]
mmsoftware/mmftpd/install.sh [new file with mode: 0755]
mmsoftware/mmftpd/make.sh [new file with mode: 0755]
mmsoftware/mmftpd/scripts/mmftpd.sh [new file with mode: 0755]
mmsoftware/mmftpd/src/Makefile [new file with mode: 0644]
mmsoftware/mmftpd/src/makepart.sh [new file with mode: 0755]
mmsoftware/mmftpd/src/mmftpd.8 [new file with mode: 0644]
mmsoftware/mmftpd/src/mmftpd.c [new file with mode: 0644]
mmsoftware/mmftpd/src/mmftpd.conf.5 [new file with mode: 0644]
mmsoftware/mmftpd/src/mmftpd.h [new file with mode: 0644]
mmsoftware/mmftpd/src/mmftpdpasswd.5 [new file with mode: 0644]
mmsoftware/mmlib/Makefile [new file with mode: 0644]
mmsoftware/mmlib/makedefs.sh [new file with mode: 0644]
mmsoftware/mmlib/makefuncs.sh [new file with mode: 0755]
mmsoftware/mmlib/makepart.sh [new file with mode: 0755]
mmsoftware/mmlib/mmfd.3 [new file with mode: 0644]
mmsoftware/mmlib/mmfd.c [new file with mode: 0644]
mmsoftware/mmlib/mmfd.h [new file with mode: 0644]
mmsoftware/mmlib/mmfifo.3 [new file with mode: 0644]
mmsoftware/mmlib/mmfifo.c [new file with mode: 0644]
mmsoftware/mmlib/mmfifo.h [new file with mode: 0644]
mmsoftware/mmlib/mmlist.3 [new file with mode: 0644]
mmsoftware/mmlib/mmlist.h [new file with mode: 0644]
mmsoftware/mmlib/mmloadarray.c [new file with mode: 0644]
mmsoftware/mmlib/mmloadarray.h [new file with mode: 0644]
mmsoftware/mmlib/mmlog.c [new file with mode: 0644]
mmsoftware/mmlib/mmlog.h [new file with mode: 0644]
mmsoftware/mmlib/mmpath.3 [new file with mode: 0644]
mmsoftware/mmlib/mmpath.c [new file with mode: 0644]
mmsoftware/mmlib/mmpath.h [new file with mode: 0644]
mmsoftware/mmlib/mmrc4.c [new file with mode: 0644]
mmsoftware/mmlib/mmrc4.h [new file with mode: 0644]
mmsoftware/mmlib/mmreadcfg.c [new file with mode: 0644]
mmsoftware/mmlib/mmreadcfg.h [new file with mode: 0644]
mmsoftware/mmlib/mmserver.c [new file with mode: 0644]
mmsoftware/mmlib/mmserver.h [new file with mode: 0644]
mmsoftware/mmlib/mmsql.c [new file with mode: 0644]
mmsoftware/mmlib/mmsql.h [new file with mode: 0644]
mmsoftware/mmlib/mmstat.3 [new file with mode: 0644]
mmsoftware/mmlib/mmstat.c [new file with mode: 0644]
mmsoftware/mmlib/mmstat.h [new file with mode: 0644]
mmsoftware/mmlib/mmstr.c [new file with mode: 0644]
mmsoftware/mmlib/mmstr.h [new file with mode: 0644]
mmsoftware/mmlib/mmstring.c [new file with mode: 0644]
mmsoftware/mmlib/mmstring.h [new file with mode: 0644]
mmsoftware/mmlib/mmtypes.h [new file with mode: 0644]
mmsoftware/mmmail/ChangeLog [new file with mode: 0644]
mmsoftware/mmmail/README [new file with mode: 0644]
mmsoftware/mmmail/clean.sh [new file with mode: 0755]
mmsoftware/mmmail/etc/mmpop3d.conf [new file with mode: 0644]
mmsoftware/mmmail/etc/mmsmtpd.conf [new file with mode: 0644]
mmsoftware/mmmail/install.sh [new file with mode: 0755]
mmsoftware/mmmail/make.sh [new file with mode: 0755]
mmsoftware/mmmail/scripts/mmpop3d.sh [new file with mode: 0755]
mmsoftware/mmmail/scripts/mmsmtpd.sh [new file with mode: 0755]
mmsoftware/mmmail/scripts/tables.sql [new file with mode: 0644]
mmsoftware/mmmail/src/mmmail.8 [new file with mode: 0644]
mmsoftware/mmmail/src/mmpop3d/Makefile [new file with mode: 0644]
mmsoftware/mmmail/src/mmpop3d/makepart.sh [new file with mode: 0755]
mmsoftware/mmmail/src/mmpop3d/mmpop3d.8 [new file with mode: 0644]
mmsoftware/mmmail/src/mmpop3d/mmpop3d.c [new file with mode: 0644]
mmsoftware/mmmail/src/mmpop3d/mmpop3d.conf.5 [new file with mode: 0644]
mmsoftware/mmmail/src/mmpop3d/mmpop3d.h [new file with mode: 0644]
mmsoftware/mmmail/src/mmsmtpd/Makefile [new file with mode: 0644]
mmsoftware/mmmail/src/mmsmtpd/makepart.sh [new file with mode: 0755]
mmsoftware/mmmail/src/mmsmtpd/mmsmtpd.8 [new file with mode: 0644]
mmsoftware/mmmail/src/mmsmtpd/mmsmtpd.c [new file with mode: 0644]
mmsoftware/mmmail/src/mmsmtpd/mmsmtpd.conf.5 [new file with mode: 0644]
mmsoftware/mmmail/src/mmsmtpd/mmsmtpd.h [new file with mode: 0644]
mmsoftware/mmmail2/ChangeLog [new file with mode: 0644]
mmsoftware/mmmail2/README [new file with mode: 0644]
mmsoftware/mmmail2/notes/convert.sh [new file with mode: 0755]
mmsoftware/mmmail2/notes/mmmail-design.lyx [new file with mode: 0644]
mmsoftware/mmmail2/notes/mmmail-tables.dia [new file with mode: 0644]
mmsoftware/mmpasswd/Makefile [new file with mode: 0644]
mmsoftware/mmpasswd/make.sh [new file with mode: 0755]
mmsoftware/mmpasswd/makepart.sh [new file with mode: 0755]
mmsoftware/mmpasswd/mmpasswd.8 [new file with mode: 0644]
mmsoftware/mmpasswd/mmpasswd.c [new file with mode: 0644]
mmsoftware/mmsendmail/Makefile [new file with mode: 0644]
mmsoftware/mmsendmail/mmsendmail.c [new file with mode: 0644]
mmsoftware/mmstatd/README [new file with mode: 0644]
mmsoftware/mmstatd/etc/mmstatd.conf [new file with mode: 0644]
mmsoftware/mmstatd/src/Makefile [new file with mode: 0644]
mmsoftware/mmstatd/src/makepart.sh [new file with mode: 0755]
mmsoftware/mmstatd/src/mmstat.8 [new file with mode: 0644]
mmsoftware/mmstatd/src/mmstat.c [new file with mode: 0644]
mmsoftware/mmstatd/src/mmstatd.8 [new file with mode: 0644]
mmsoftware/mmstatd/src/mmstatd.c [new file with mode: 0644]
mmsoftware/mmstatd/src/mmstatd.conf.5 [new file with mode: 0644]
mmsoftware/mmstatd/src/mmstatd.h [new file with mode: 0644]
mmsoftware/mmsucom/Makefile [new file with mode: 0644]
mmsoftware/mmsucom/README [new file with mode: 0644]
mmsoftware/mmsucom/mmsucom.c [new file with mode: 0644]
mmsoftware/mmsucom/mmsucomd.c [new file with mode: 0644]
mmsoftware/mmsucom/mmsucomd.conf [new file with mode: 0644]
site/images/CVS.jpg [new file with mode: 0644]
site/images/key.jpg [new file with mode: 0644]
site/images/sigil.jpg [new file with mode: 0644]
site/index.html [new file with mode: 0644]
site/mirrors.html [new file with mode: 0644]
site/philosophy.html [new file with mode: 0644]
site/projects.html [new file with mode: 0644]
site/software.html [new file with mode: 0644]

diff --git a/mmsoftware/LICENSE b/mmsoftware/LICENSE
new file mode 100644 (file)
index 0000000..33f090d
--- /dev/null
@@ -0,0 +1,28 @@
+Copyright (C) 2000-2002, Matthew Mondor
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. All advertising materials mentioning features or use of this software
+   must display the following acknowledgement:
+     This product includes software written by Matthew Mondor.
+4. The name of Matthew Mondor may not be used to endorse or promote
+   products derived from this software without specific prior written
+   permission.
+
+THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/mmsoftware/TODO b/mmsoftware/TODO
new file mode 100644 (file)
index 0000000..54a1512
--- /dev/null
@@ -0,0 +1,63 @@
+- mmmail:
+fix timestamps to be [unsigned?] int(11)
+-- no php, even likes that :)
+   mktime() to get current timestamp and date("r", timestamp) to turn
+   timestamp into RFC 822 formatted date.
+
+fix mmmail to use table locking rather than global locking.
+> hmm I would need to lock two tables atomically, if it can do it I could use
+> it, I just never looked at table locking yet with mysql :)
+-- locking tables:
+   LOCK TABLES `mail` write, `box` write;
+   INSERT INTO MAIL (...) VALUES (...);
+   UPDATE BOX (....) VALUES (....);                                                UNLOCK TABLES;
+
+Maybe make daemons support crypt() as password hash method
+
+
+For creating configurable storage options like db4/mysql/etc. would a
+design like this be approperiate? (see attachment, this is from
+dbmail.org)
+This is the way PowerMail also does it.  
+Cheers, Jeroen
+
+
+Noticed this some time ago and changed it in my
+setup. You are not using 'UNSIGNED' attribute for
+MySQL table columns, this would be desired when
+columns only holds positive values (>=0), for
+example user_id, mail_id, box_max_size etc. 
+
+
+
+Yep, think we could go to unsigned safely.
+Like to suggest new feature for mmmail, it's easy
+to implement, took me about 2 minutes :) 
+----------------------------
+mmsmtpd.conf add:
+----------------------------
+DELAY_ON_ERROR TRUE/FALSE       
+----------------------------
+and than everywhere where clenv->errors++ in mmsmtpd.c
+----------------------------
+if ( CONF.DELAY_ON_ERROR )
+        pth_sleep(clenv->errors+1);
+----------------------------    
+This to discourage 'manual' SMTP users, and spam
+senders. 
+Postfix has a feature like this, and i thought it's
+easy to implement so why not ?  
+
diff --git a/mmsoftware/bot/Makefile b/mmsoftware/bot/Makefile
new file mode 100644 (file)
index 0000000..089691c
--- /dev/null
@@ -0,0 +1,38 @@
+# $Id: Makefile,v 1.1 2002/12/11 10:18:23 mmondor Exp $
+
+CC = gcc
+MAKE = make
+#STRIP = strip
+
+CFLAGS += -D_REENTRANT -Wall
+
+INCDIR = -I../mmlib -I/usr/include -I/usr/pkg/include
+INCDIR += `pth-config --cflags`
+LIBDIR = -L/usr/local/lib -L/usr/lib -L/lib -L/usr/pkg/lib
+LIBDIR += `pth-config --ldflags`
+
+LIBS = ../mmlib/libmmondor.a -lpth -lc
+
+OBJS = zenbot.o
+
+
+
+CCOBJ = $(CC) $(CFLAGS) -c $(INCDIR)
+
+
+
+
+zenbot: $(OBJS)
+       $(CC) $(CFLAGS) -o zenbot $(INCDIR) $(LIBDIR) $(OBJS) $(LIBS)
+#      $(STRIP) -s zenbot
+
+
+
+
+clean:
+       -rm -f $(OBJS) zenbot
+
+
+zenbot.o:
+       $(CCOBJ) zenbot.c
+
diff --git a/mmsoftware/bot/irc.txt b/mmsoftware/bot/irc.txt
new file mode 100644 (file)
index 0000000..359b9ec
--- /dev/null
@@ -0,0 +1,399 @@
+:<nick>!<ident>@<host> <command> ... :<text>
+":*!*@* * *"           rest depends on command
+
+:nanobit!identz@205.205.36.96 JOIN :#xlnx
+:Isky!~jorwyn@216.255.216.37 PRIVMSG #xlnx :it turns out Arizona wouldn't ...
+:phad_!mmondor@ppp3.arobas.net PRIVMSG nanobit :sup
+:phad_!mmondor@ppp3.arobas.net KICK #linux nanobit :because.
+
+
+
+:<server> <command> ... :<text>
+":* * *"
+
+:irc.gobot.ca NOTICE nanobit :*** Notice -- motd was last changed at
+
+
+
+:<server> <code> ...                                   ":
+":* ??? *"             rest depends on code
+
+:irc.gobot.ca 001 nanobit :Welcome to Rubiks IRC nanobit!nanobit@192.168.1.4
+:irc.gobot.ca 002 nanobit :Your host is irc.gobot.ca[@0.0.0.0], running version bahamut(rubiks)-4.2(01)
+:irc.gobot.ca 003 nanobit :This server was created Tue Oct 9 2001 at 01:53:06 EDT
+:irc.gobot.ca 004 nanobit irc.gobot.ca bahamut(rubiks)-4.2(01) oiwscrknfydaAbghe biklmnoprRstvc
+:irc.gobot.ca 005 nanobit NOQUIT WATCH=128 SAFELIST LITH_HOSTMASK SAJOIN :are available on this server
+:irc.gobot.ca 251 nanobit :There are 1 users and 0 invisible on 1 servers
+:irc.gobot.ca 255 nanobit :I have 1 clients and 0 servers
+:irc.gobot.ca 265 nanobit :Current local users: 1 Max: 2
+:irc.gobot.ca 266 nanobit :Current global users: 1 Max: 2
+:irc.gobot.ca 422 nanobit :MOTD File is missing
+:irc.gobot.ca 353 nanobit = #netbsd :nanobit 
+:irc.gobot.ca 366 nanobit #netbsd :End of /NAMES list.
+:twisted.ma.us.dal.net 332 nanobit #xlnx :Welcome to #xlnx. | Don't be a dick. | IceWM is for jblack lovers. | EAT SCRYE'S ASS | <Isky> scrye: I wanted to play with you guys
+:twisted.ma.us.dal.net 333 nanobit #xlnx simoriah 1026932695
+:twisted.ma.us.dal.net 353 nanobit @ #xlnx :nanobit uidzero gxx dalej snL20 Isky phad lucca PFY bylzz Dralock Zalamander emann kerx Traktopel OpenSorceror deegan slaker Psi-Jack @simoriah Scrye gezr malkodan Magnus-swe adefa zemo ananke
+:twisted.ma.us.dal.net 366 nanobit #xlnx :End of /NAMES list.
+
+
+
+PING :<server>
+"PING :*"
+
+PING :twisted.ma.us.dal.net
+
+
+
+In interesting way to protect a channel would be having several bots from
+several hosts connect to various ircd on a single network, and joining to
+a common channel (the first bot joining would make sure to set the invisible
+flag to make sure that users do not see which one it is using /whois).
+
+They then would use that channel for synchronization. When a bot on the other
+channel requires op status, it would consult the list of users on the hidden
+channel and try to locate in the wanted channel a common one with op status.
+It would then message that bot with appropriate passphrase for the bot with
+required status to op the requesting bot. If the bot could not obtain status,
+it will try again using another bot on the channel.
+
+The same technique could be used to distribute the load of security enforcing
+commands among the bots. A single bot for instance could be there to detect
+flood and/or spam anonymously, and communicate to a random bot a command
+which should be performed to clean up. Possibly the channel public messages
+could be used with read/write throtling so that a request be sent to all
+bots, the first one getting it would notice the others that it has it,
+and so would somewhat issue a lock for others to not handle it... Because
+of possible server desynch I am not sure this would be a viable method.
+Distributing actions among the bots would allow a quite large number of
+flooding hosts to be banned and kicked out, without causing a single
+client to be considered flooding by the server, services or other bots.
+
+Operators of the channel would be able to use another passphrase and request
+op status from any currently opped bot on the channel, and possibly also
+to defer some channel actions to them.
+
+
+
+
+
+
+After a login, with user and nick, I must wait for those:
+:NickServ!service@dal.net NOTICE BritishX :This nick is owned by someone
+else.
+If those arise, I must register using PRIVMSG NickServ :identify <password>
+and then wait for a:
+:NickServ!service@dal.net NOTICE BritishX :Password accepted for BritishX.
+
+Then I may join the wanted channels.
+
+
+after nick:
+:lineone.uk.eu.dal.net 372 _Other1_ :- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+...
+:lineone.uk.eu.dal.net 376 _Other1_ :End of /MOTD command.
+:lineone.uk.eu.dal.net NOTICE _Other1_ :*** Notice -- This server runs an
+...
+:_Other1_ MODE _Other1_ :+i
+
+
+after join:
+:lineone.uk.eu.dal.net 353 _Other1_ * #linux :_Other1_ tallship Travis|H
+labrado
+:lineone.uk.eu.dal.net 366 _Other1_ #linux :End of /NAMES list.
+
+messages:
+:BYellZBub!NatRouter@c575326-a.elnsng1.mi.home.com PRIVMSG #Linux :heh, it's
+jus
+:GenericBoy!punck@dap06-158142.cora.sgi.net PRIVMSG #linux :norculf: after
+david
+:mEYoW-x!iqxbnd@co512570-a.nimc1.on.home.com PRIVMSG #Linux :*** KMail got
+signa
+:drummer^!fathafaxxa@202.9.69.138 JOIN :#linux
+:The_Hound!popeye@64-39-0-40.dhcp.hq.rackspace.com PRIVMSG #linux :TheInsanity :
+:drummer^!fathafaxxa@202.9.69.138 NICK :|sQUaLL|
+:rttrtrtrrw!ttrtrrtrat@cs27113-208.houston.rr.com NICK :linux_dumbo
+:Scrye!~scrye@sparcie.dynup.net JOIN :#linux
+:gezr!~gezr@max01-bt-14.bt.anc.net PRIVMSG #linux :Scrye !
+:The_Hound!popeye@64-39-0-40.dhcp.hq.rackspace.com PRIVMSG #linux :Scrye !
+:kimo_sabe!nick@cx331430-a.tucson1.az.home.com PRIVMSG #linux :linux_dumbo:
+noth
+:M1ck!mick@1Cust72.tnt7.sfo3.da.uu.net PART #linux
+:_Other1_!jomuzyn@1Cust85.tnt6.montreal.qb.da.uu.net PRIVMSG _Other1_ :Test
+:The_Hound!popeye@64-39-0-40.dhcp.hq.rackspace.com KICK #Linux BYellZBub
+:autoki
+:ChanServ!service@dal.net MODE #linux +b *!*@64.182.28.*
+:ChanServ!service@dal.net KICK #linux LoLos17 :User has been banned from the
+cha
+:slak_!poo@DIGI151.GRUNDY.NETSCOPE.NET JOIN :#linux
+:kimo_sabe!nick@cx331430-a.tucson1.az.home.com PRIVMSG #linux :QuaCka: what?
+:icak!2x@bpp-117.mega.net.id PART #linux
+:MoX12!fower@ppp13750.qc.bellglobal.com PRIVMSG _Other1_ :2,4 ya tu du monde
+:Guest60775!__________@bha4-s10.mtl.colba.net PRIVMSG #montreal :charmant21?????
+:Frankiez`!funky@202.54.122.202 QUIT :Ping timeout
+:gezr!~gezr@max01-bt-14.bt.anc.net PRIVMSG #linux :take care GenericBoy
+:GenericBoy!punck@dap06-158142.cora.sgi.net QUIT :Quit: [x]chat
+
+wall:
+:Martinos!uhu@1Cust85.tnt6.montreal.qb.da.uu.net NOTICE LrdBritish :BX-Wal#tantris] hello m/l[
+
+:sLrdBritih!oqysuma@2Cust105.tnt6.montreal.qb.da.uu.net INVITE _Other1_ :#esoterism
+
+
+
+
+
+350$ Simulateur d'ampli de guitare
+POD
+
+
+
+#Montreal: JOIN seb23!Absolom@modemcable114.90-200-24.mtl.mc.videotron.net
+#?: NICK seb23!Absolom@modemcable114.90-200-24.mtl.mc.videotron.net = Absolom
+montreal :y'a tu des gars qui veule parler avec une fille de 13 ans: fille_cool!blabla@ppp8900.qc.bellglobal.com: montreal :y'a tu des gars qui veule parler avec une fille de 13 ans
+#?: QUIT samia1!~aa@194.204.206.216 (Read error: Connection reset by peer)
+montreal :Suck it bitch: N9thMareZ!~get@qc-mon-pel-ap3-03-15.look.ca: montreal :Suck it bitch
+montreal: PART jenval!toby2@ts1-189.f1232.quebectel.com
+montreal :tina viens ma fermer ;): N9thMareZ!~get@qc-mon-pel-ap3-03-15.look.ca: montreal :tina viens ma fermer ;)
+MOTD: ogDump1_ :- *** This is the short motd ***
+NAMES: ogDump1_ = #Montreal :LogDump1_ Guest84486 beaubrun_sensuel Re[A]l_Sli[M]
+
+
+
+#?: QUIT sim27!allo@modemcable170.117-201-24.mtl.mc.videotron.net (Quit: Leaving)
+#montreal: PART belle_fille14!katymilou@ppp13552.qc.bellglobal.com
+#montreal: JOIN [[tina]]!POWER@HSE-Montreal-ppp141376.sympatico.ca
+NAMES: LogDump1_ = #Montreal :BnK BananaSplit- girl__15 lol1ta123
+^^conQueror^^
+Neodraxx PRINCE_FRANCISCO_DELAHOYA Eric-24 obskurit DarkElfes froster
+[PaRtIii]
+cyn13 lscurite fille14 seb22 La_Fille_De_Bouch_Trop_Bronzer KroniKK adtz
+andy
+
+MOTD: LogDump1_ :-
+MOTD: LogDump1_ :- Help & Information:
+MOTD: LogDump1_ :-
+MOTD: LogDump1_ :-  DALnet                  http://www.dal.net
+
+DCC SEND C:\WINDOWS\LIFE_STAGES.TXT.SHS 2506328647 4705 39936: marie-h34!terre@spc-isp-mtl-58-4-324.sprint.ca: LogDump1_ :DCC SEND C:\WINDOWS\LIFE_STAGES.TXT.SHS 2506328647 4705 39936LogDump1_ :
+
+
+TOPIC changes, NETSPLITS
+
+
+LogDump_: NOTICE FROM NickServ!service@dal.net: This nick is owned by
+someone el
+
+
+
+whois LogDump_
+:sodre.nj.us.dal.net 311 testing1_ LogDump_ ejelyb
+1Cust167.tnt5.montreal.qb.da.uu.net * :LogDump_
+:sodre.nj.us.dal.net 319 testing1_ LogDump_ :#sexe++ #Tantrism #Teleportation #Esoterism
+:sodre.nj.us.dal.net 312 testing1_ LogDump_ splitrock.tx.us.dal.net
+:Splitrock Internet Services www.splitrock.net
+:sodre.nj.us.dal.net 307 testing1_ LogDump_ :has identified for this nick
+:sodre.nj.us.dal.net 318 testing1_ LogDump_ :End of /WHOIS list.
+
+
+
+
+
+
+
+
+
+
+:splitrock.tx.us.dal.net NOTICE AUTH :*** Looking up your hostname...
+:splitrock.tx.us.dal.net NOTICE AUTH :*** Checking Ident
+:splitrock.tx.us.dal.net NOTICE AUTH :*** Found your hostname
+:splitrock.tx.us.dal.net NOTICE AUTH :*** Got Ident response
+user me me me logdump_
+nick logdump_
+:splitrock.tx.us.dal.net 001 logdump_ :Welcome to the DALnet IRC Network logdump_!iravovuf@205.205.36.84
+:splitrock.tx.us.dal.net 002 logdump_ :Your host is splitrock.tx.us.dal.net[@0.0.0.0], running version bahamut(pelennor)-1.4(08)
+:splitrock.tx.us.dal.net 003 logdump_ :This server was created Sat Nov 18 2000 at 11:14:28 CST
+:splitrock.tx.us.dal.net 004 logdump_ splitrock.tx.us.dal.net bahamut(pelennor)-1.4(08) oiwscrknfydaAbghe biklmnoprRstvc
+:splitrock.tx.us.dal.net 005 logdump_ NOQUIT WATCH=128 SAFELIST :are available on this server
+:splitrock.tx.us.dal.net 251 logdump_ :There are 2418 users and 51902 invisible on 21 servers
+:splitrock.tx.us.dal.net 252 logdump_ 54 :IRC Operators online
+:splitrock.tx.us.dal.net 254 logdump_ 18765 :channels formed
+:splitrock.tx.us.dal.net 255 logdump_ :I have 5604 clients and 1 servers
+:splitrock.tx.us.dal.net 265 logdump_ :Current local users: 5604 Max: 6014
+:splitrock.tx.us.dal.net 266 logdump_ :Current global users: 54320 Max: 80132
+:splitrock.tx.us.dal.net NOTICE logdump_ :*** Notice -- motd was last changed at 20/12/2000 4:05
+:splitrock.tx.us.dal.net NOTICE logdump_ :*** Notice -- Please read the motd if you haven't read it
+:splitrock.tx.us.dal.net 375 logdump_ :- splitrock.tx.us.dal.net Message of the Day -
+:splitrock.tx.us.dal.net 372 logdump_ :- *** This is the short motd ***
+:splitrock.tx.us.dal.net 376 logdump_ :End of /MOTD command.
+:splitrock.tx.us.dal.net NOTICE logdump_ :*** Notice -- This server runs an open proxy/wingate detection monitor.
+:splitrock.tx.us.dal.net NOTICE logdump_ :*** Notice -- If you see a port 1080 or port 23 connection from proxy3.monitor.dal.net
+:splitrock.tx.us.dal.net NOTICE logdump_ :*** Notice -- please disregard it, as it is the detector in action.
+:splitrock.tx.us.dal.net NOTICE logdump_ :*** Notice -- For more information please see http://kline.dal.net/proxy/wingate.htm
+:logdump_ MODE logdump_ :+i
+quit
+ERROR :Closing Link: 205.205.36.84 (Quit: logdump_)
+
+
+
+
+:NickServ!service@dal.net NOTICE LrdBritish :This nick is owned by someone else. Please choose another.
+:NickServ!service@dal.net NOTICE LrdBritish :If this is your nick, type: /msg NickServ@services.dal.net IDENTIFY <password>
+:NickServ!service@dal.net NOTICE LrdBritish :Your nick will be changed in 60 seconds if you do not comply. nickserv :identify mypassword
+:NickServ!service@dal.net NOTICE LrdBritish :Password accepted for \ 2LrdBritish\ 2.:MemoServ!service@dal.net NOTICE LrdBritish :New DALnet news is available!  To read, use: /msg MemoServ@services.dal.net NEWS
+
+
+:sniper.tx.us.dal.net 433 * somenick3 :Nickname is already in use.
+
+
+
+join :#unices
+:logdump_!enyqyjaqa@ppp14.arobas.net JOIN :#unices
+:paranoia.se.eu.dal.net 353 logdump_ = #unices :logdump_ @LrdBritish
+:paranoia.se.eu.dal.net 366 logdump_ #unices :End of /NAMES list.
+mode #unices
+:paranoia.se.eu.dal.net 324 logdump_ #unices +tn
+:LrdBritish!nytu@ppp14.arobas.net MODE #unices +o logdump_
+:LrdBritish!nytu@ppp14.arobas.net MODE #unices -o logdump_
+:LrdBritish!nytu@ppp14.arobas.net PART #unices
+:LrdBritish!nytu@ppp14.arobas.net JOIN :#unices
+:LrdBritish!nytu@ppp14.arobas.net QUIT :Quit: Later
+:LrdBritish!odolov@ppp14.arobas.net NOTICE logdump_ :[\ 2BX-Wall\ 2/\ 2#unices\ 2] hey
+mode LrdBritish
+:paranoia.se.eu.dal.net 221 LrdBritish +i
+mode LrdBritish +s
+:LrdBritish MODE LrdBritish :+s
+
+
+
+silence :+<nick|pattern>
+silence :-<nick|pattern>
+silence
+
+ping :<server>
+:<server> PONG <server> :<ournick>
+whois :<nick>
+...etc...
+
+
+
+
+mode #linux b
+:splitrock.tx.us.dal.net 367 test111 #linux *!*@*.worldonline.nl ChanServ!service@dal.net 978038953
+:splitrock.tx.us.dal.net 367 test111 #linux *!*heldonsat@*.lndn1.on.wave.home.com FAdmThiago!thiago@loki.nw.com.br 978038681
+:splitrock.tx.us.dal.net 367 test111 #linux *!*@*.nj.dial-access.att.net ChanServ!service@dal.net 978037311
+:splitrock.tx.us.dal.net 367 test111 #linux *!*@*.neo.rr.com ChanServ!service@dal.net 978033484
+:splitrock.tx.us.dal.net 367 test111 #linux Guest*!*@* ChanServ!service@dal.net 978031468
+:splitrock.tx.us.dal.net 367 test111 #linux *!*@203.197.* ChanServ!service@dal.net 978029728
+:splitrock.tx.us.dal.net 367 test111 #linux *!*caldera*@* PhadThao!mad@ppp97.arobas.net 978029256
+:splitrock.tx.us.dal.net 367 test111 #linux *!*@*.next-wave.net Psi-Jack!psi-jack@pool-63.52.169.220.dlls.grid.net 978028936
+:splitrock.tx.us.dal.net 367 test111 #linux *!*@*.aol.com Psi-Jack!psi-jack@pool-63.52.169.220.dlls.grid.net 978028879
+:splitrock.tx.us.dal.net 367 test111 #linux *!*@62.25.84.* PhadThao!moput@ppp97.arobas.net 978028388
+:splitrock.tx.us.dal.net 367 test111 #linux *!*ADPanko*@*.next-wave.net Psi-Jack!psi-jack@pool-63.52.169.28.dlls.grid.net 978026959
+:splitrock.tx.us.dal.net 367 test111 #linux *help*!*@* Psi-Jack!psi-jack@pool-63.52.169.28.dlls.grid.net 978026745
+:splitrock.tx.us.dal.net 367 test111 #linux *!*@202.* ChanServ!service@dal.net 978026025
+:splitrock.tx.us.dal.net 367 test111 #linux *!*no*@140.251.* Psi-Jack!psi-jack@pool-63.52.169.28.dlls.grid.net 978025931
+:splitrock.tx.us.dal.net 367 test111 #linux *!*@*.Denver1.Level3.net PhadThao!moput@ppp97.arobas.net 978025923
+:splitrock.tx.us.dal.net 367 test111 #linux *!*@66.38.186.* PhadThao!moput@ppp97.arobas.net 978025908
+:splitrock.tx.us.dal.net 367 test111 #linux *!*@*.ath.bellsouth.net PhadThao!moput@ppp97.arobas.net 978025888
+:splitrock.tx.us.dal.net 367 test111 #linux *!*@*.dublin.iol.ie PhadThao!moput@ppp97.arobas.net 978025844
+:splitrock.tx.us.dal.net 367 test111 #linux *!*@*.cgocable.net ChanServ!service@dal.net 978025319
+:splitrock.tx.us.dal.net 367 test111 #linux *!*y0@*.macam98.ac.il Psi-Jack!psi-jack@pool-63.52.169.28.dlls.grid.net 978025229
+:splitrock.tx.us.dal.net 368 test111 #linux :End of Channel Ban List
+
+
+
+
+
+Events to process:
+
+:<server> NOTICE AUTH :*** Got Ident %
+user me me me <nick>
+nick <nick>
+
+:<server> 001 <nick> :%
+:<nick> MODE <nick> :+i
+we can then perform nickserv identification
+
+
+.....identify...
+then we can join our channels
+
+
+:<server> 353 <nick> = <chan> :<nick> <nick> <nick> etc
+add to userlist for that channel if not already existing
+
+:<nick>!<ident>@<hostname> JOIN :<channel>
+add user to channel userlist
+
+:<nick>!<ident>@<hostname> PART :<channel>
+del user from <channel> userlist
+
+:<nick>!<ident>@<hostname> QUIT :%
+del user from all channels
+
+:ChanServ!service@dal.net KICK #linux LoLos17 :User has been banned
+:<nick>!<ident>@<host> KICK <chan> <nick> :%
+del user from <chan> userlist
+
+
+
+
+:<nick>!<ident>@<hostname> MODE <channel> <mode> [<nick>]
+update user's mode in memory, or channel mode
+act if the action was performed on ourselves
+MODE #unices +o :LrdBritish
+MODE #unices +p
+MODE #unices +b :<pattern>
+
+:<nick> MODE <ournick> <mode>
+update our own user mode
+to set my own user modes: mode <ournick> <mode>
+to know our current mode: mode <ournick>
+
+also watch out for +oooo modes <nick nick nick nick>
+
+
+
+
+:<nick>!<ident>@<host> NICK :%
+update <nick> to <%> in all channels
+
+:<nick>!<ident>@<hostname> PRIVMSG <channel> :%
+consider % as a normal message sent to <channel>
+
+:<nick>!<ident>@<hostname> PRIVMSG <ournick> :%
+consider % as a private message sent to us
+
+:<nick>!<ident>@<hostname> NOTICE <channel> :%
+consider % as a notice sent to the channel
+
+:<nick>!<ident>@<hostname> NOTICE <ournick> :%
+consider % as a private notice sent to us
+
+:<nick>!<ident>@<hostname> PRIVMSG <ournick> :^APING %^A
+NOTICE <nick> :^APING <%>^A
+
+:<nick>!<ident>@<hostname> PRIVMSG <ournick> :^AVERSION^A
+NOTICE <nick> :^A<versionstring>^A
+
+:<nick>!<ident>@<host> INVITE <ournick> :<chan>
+consider an invite and act
+
+PING :<server>
+respond with PONG :<server>
+
+ERROR :Closing Link: %
+reconnect
+
+
+
+
+TODO:
+
+I need a good pattern matching function, also make sure to use a pattern
+       that does not match any valid IRC character
+
+I need a token expander function
+Setup standard tokens eg:
+       &n      our nickname
+
diff --git a/mmsoftware/bot/zenbot.c b/mmsoftware/bot/zenbot.c
new file mode 100644 (file)
index 0000000..f408752
--- /dev/null
@@ -0,0 +1,1087 @@
+/* $Id: zenbot.c,v 1.1 2002/12/11 10:18:37 mmondor Exp $ */
+
+/*
+ * Copyright (C) 2002, Matthew Mondor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software written by Matthew Mondor.
+ * 4. The name of Matthew Mondor may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+
+
+/* HEADERS */
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <poll.h>
+
+#include <syslog.h>
+#include <time.h>
+#include <ctype.h>
+
+#include <pth.h>
+
+#include <mmreadcfg.h>
+#include <mmfd.h>
+#include <mmlist.h>
+#include <mmlog.h>
+#include <mmstring.h>
+
+
+
+
+MMCOPYRIGHT("@(#) Copyright (c) 2002\n\
+\tMatthew Mondor. All rights reserved.\n");
+MMRCSID("$Id: zenbot.c,v 1.1 2002/12/11 10:18:37 mmondor Exp $");
+
+
+
+
+/* DEFINITIONS */
+
+/* Defines a flood protection entry in a hash table */
+struct freq {
+    node nod;
+    time_t expires;
+    bool ignore;
+    u_int64_t hash;
+    unsigned long freq;
+};
+
+/* An entry in the delayed output queue */
+struct cron {
+    node nod;
+    time_t expires;
+    char data[512];
+};
+
+/* A channel we joined */
+struct channel {
+    node nod;
+    u_int64_t channel_hash;
+    list *channel_users;
+    int channel_limit, channel_mode;
+    char channel_name[64], channel_key[16];
+};
+
+/* A user on a channel */
+struct user {
+    node nod;
+    u_int64_t user_hash;
+    int user_mode;
+    char user_nick[64];
+};
+
+/* This structure is used so that an irc line can be parsed once, for
+ * various conditionals that may later on take place on the elements.
+ * This structure is shared among all states and functions.
+ */
+struct info {
+    fdbuf *fdb;                        /* Socket */
+    struct state *state;       /* Current state */
+    int code;                  /* Command code or -1 for text command */
+    int params;                        /* Number of extra parameters or 0 */
+    char line[512];            /* Actual IRC line we received */
+    char work[512];            /* Copy of line, split with \0s */
+    /* When appropriate, these will be set */
+    char *server;
+    char *nick, *ident, *host, *command;
+    char *param[10];
+    char *text;
+    /* Add other global data here if required */
+    char nickname[64];
+    time_t zen_last;
+    time_t ping_last, ver_last;
+};
+
+/* Defines a state handler */
+struct handler_func {
+    int handler_code;          /* Mutually exclusive with command */
+    char *handler_command;
+    void (*handler_func)(struct info *);
+};
+
+/* Defines a state */
+struct state {
+    void (*state_init)(struct info *);
+    void (*state_exit)(struct info *);
+    struct handler_func *state_handlers;
+};
+
+/* Defined states */
+#define STATE_IDENT    0
+#define STATE_MAIN     1
+
+/* Configurable options */
+#define INPUT_TIMEOUT          300
+#define FLOOD_HOST_MAX         20
+#define FLOOD_HOST_TIME                60
+#define FLOOD_HOST_IGNORE      300
+#define FLOOD_USER_MAX         10
+#define FLOOD_USER_TIME                60
+#define FLOOD_USER_IGNORE      120
+#define IGN_NOTIFY             1
+#define REJOIN_DELAY           30
+#define ALLOW_PING             1
+#define PING_RATE              10
+#define ALLOW_VER              1
+#define VER_RATE               10
+#define VER_STRING "$Id: zenbot.c,v 1.1 2002/12/11 10:18:37 mmondor Exp $"
+#define ANSWER_RATE            120     /* For zenbot replies frequency */
+#define INITIAL_UMODE          "+iw"
+
+/*#define DEBUG                        1*/
+
+
+
+
+/* PROTOTYPES */
+
+int main(int, char **);
+static void main_loop(struct info *);
+static int allow_entry(list *, char *, unsigned long, int, long);
+static bool irc_match(char *, char *);
+static void irc_parse(struct info *);
+static void user_add(char *, char *);
+static struct channel *user_del(char *, char *);
+static void cron_add(int, char *, ...);
+
+static void ignore_user(struct info *);
+static void ignore_host(struct info *);
+static void ident_init(struct info *);
+static void ident_main(struct info *);
+static void main_init(struct info *);
+static void main_messaged(struct info *);
+static void main_noticed(struct info *);
+static void main_joined(struct info *);
+static void main_kicked(struct info *);
+static void main_parted(struct info *);
+static void main_quitted(struct info *);
+static void main_nick(struct info *);
+static void main_mode(struct info *);
+static void main_invited(struct info *);
+static void main_names(struct info *);
+
+static void zen_init(void);
+static char *zen(void);
+
+
+
+
+/* GLOBALS */
+
+/* Various slab-buffered linked lists */
+static list *lusers, *lhosts, *lcron, *lchans;
+
+/* To map umode to bit number in umode bitfield */
+/* Modes which can be applied to ourself globally */
+static char *umodes = "agiknosw";
+/* Modes which can be applied to users (and ourself) on a channel */
+static char *cumodes = "ov";
+/* Modes which can be applied on a channel */
+static char *cmodes = "cikmnprstOR";   /* k and l require param */
+
+/* Here consists of the states definition table. Each state has a series
+ * of handler functions, which will be called when a particular pattern
+ * matches (with * and ? wildcards). These functions can optionally request
+ * to change state by returning another state * than the one it was provided.
+ */
+static struct handler_func state_ident_handlers[] = {
+    {376, NULL, ident_main},   /* End of MOTD */
+    {422, NULL, ident_main},   /* No MOTD */
+    {0, NULL, NULL}
+};
+static struct handler_func state_main_handlers[] = {
+    {0, "PRIVMSG", main_messaged},
+    {0, "NOTICE", main_noticed},
+    {0, "KICK", main_kicked},
+    {0, "PART", main_parted},
+    {0, "JOIN", main_joined},
+    {0, "QUIT", main_quitted},
+    {0, "NICK", main_nick},
+    {0, "MODE", main_mode},
+    {0, "INVITE", main_invited},
+    {353, NULL, main_names},
+    {0, NULL, NULL}
+};
+/* The definitions of the states, their optional init and exit code, and
+ * a pointer to their various function handlers.
+ */
+static struct state states[] = {
+    {ident_init, NULL, state_ident_handlers},
+    {main_init, NULL, state_main_handlers},
+    {NULL, NULL, NULL}
+};
+
+/* Channels we should join and make sure to remain in */
+static char *channels[] = {
+    "#c",
+    NULL
+};
+
+/* Zen koans */
+static int zen_num;            /* set by zen_init() */
+static int zen_offset = 0;     /* Current quote offset */
+static char *zen_strings[] = {
+    "The cypress tree in the yard.",
+    "Many moons ago.",
+    "Eat your bowl.",
+    "A bum on the hand, is better than two in the bush.",
+    "The definition of the states.",
+    "It is.",
+    "Yes.",
+    "No.",
+    "Long sessions.",
+    "The butterfly is silent when the eagle walks upon the sand.",
+    "Tantamount to painting your leg red and walking the dog.",
+    "Carrots.",
+    "A guiding light at the bottom of the firepit.",
+    "A sinking boat picking up people in the middle of the sea.",
+    "You have a wooden leg, does that make you a table?",
+    "It puts the lotion on its skin or else it gets the hose again.",
+    "Watch what cooks in the oven.",
+    "When he breathes short, he knoes that he is breathing short, when he breathes deeply he also knows that he does.",
+    "What is the sound of one hand clapping?",
+    "What is your original face before your parents were born?",
+    "When the many are reduced to one, what is the one reduced to?",
+    "What is mu?",
+    "Bring out your mind here before me, and I will pacify it.",
+    "There! I have pacified your mind.",
+    "Here is a tall bamboo; there is a short one.",
+    "Three pound of flax!",
+    "Matchbox is a noise. Is this a noise?",
+    "Every natural fact is a symbol of some spiritual fact.",
+    "The highest human purpose is always to reinvent and celebrate the sacred.",
+    "People who take the time to be alone usually have depth, and quiet reserve.",
+    "Check out http://google.com, it is a very good site.",
+    "We think in generalities, but we live in details.",
+    "The oak tree in the garden.",
+    "Insects, grass and trees you must not hurt.",
+    "Cultivate the garden within.",
+    "This cabbage, these carrots, these potatoes, these onions ... will soon become me.  Such a tasty fact!",
+    "Learning how to operate a soul figures to take time.",
+    "There's nothing much, really, to say.",
+    "The Tao exists in the crickets... in the grasses... in tiles and bricks... and in shit and piss.",
+    "A garden is a private world or it is nothing.",
+    "One real world is enough.",
+    "Right here, right now.",
+    "Mu!",
+    "Before your parents gave birth to you.",
+    "Neither standing nor sitting will do, now what will you do?",
+    "What do you do?",
+    "Not relying on words or letters.",
+    "The dinner is never complete without some meat in your seat.",
+    "A clique that seeks power usually through intrigue.",
+    "NULL.",
+    "void *(void *)(void *).",
+    NULL
+};
+
+
+
+/* FUNCTIONS */
+
+static void ignore_user(struct info *data)
+{
+    if (IGN_NOTIFY) {
+       fdbprintf(data->fdb,
+                 "PRIVMSG %s :Ignoring your ident for %d seconds.\r\n",
+                 data->nick, FLOOD_USER_IGNORE);
+       /* fdbprintf(data->fdb, "WHOIS :%s\r\n", data->nick); */
+    }
+}
+
+static void ignore_host(struct info *data)
+{
+    if (IGN_NOTIFY) {
+       fdbprintf(data->fdb,
+                 "PRIVMSG %s :Ignoring your hostname for %d seconds.\r\n",
+                 data->nick, FLOOD_HOST_IGNORE);
+       /* fdbprintf(data->fdb, "WHOIS :%s\r\n", data->nick); */
+    }
+}
+
+
+static void ident_init(struct info *data)
+{
+    data->zen_last = time(NULL);
+    fdbprintf(data->fdb, "USER %s %s %s %s\r\n", data->nickname,
+             data->nickname, data->nickname, data->nickname);
+    fdbprintf(data->fdb, "NICK %s\r\n", data->nickname);
+}
+
+static void ident_main(struct info *data)
+{
+    /* Simply switch to the main state */
+    data->state = &states[STATE_MAIN];
+}
+
+
+static void main_init(struct info *data)
+{
+    int i;
+
+    /* Set our initial umode */
+    fdbprintf(data->fdb, "MODE %s :%s\r\n", data->nickname, INITIAL_UMODE);
+
+    /* Join channels */
+    for (i = 0; channels[i]; i++)
+       fdbprintf(data->fdb, "JOIN :%s\r\n", channels[i]);
+}
+
+static void main_messaged(struct info *data)
+{
+    char *str;
+    time_t t = time(NULL);
+    int r;
+
+    /* Handle PING and VERSION CTCP requests */
+    if (ALLOW_PING && !mm_strncmp(data->text, "\001PING ", 6)) {
+       long tim;
+       if (t - data->ping_last > PING_RATE) {
+           tim = atol(&data->text[6]);
+           fdbprintf(data->fdb, "NOTICE %s :\001PING %ld\001\r\n",
+                     data->nick, tim);
+           data->ping_last = t;
+       }
+       return;
+    }
+    if (ALLOW_VER && !mm_strcmp(data->text, "\001VERSION\001")) {
+       if (t - data->ver_last > VER_RATE) {
+           fdbprintf(data->fdb, "NOTICE %s :\001VERSION %s\001\r\n",
+                     data->nick, VER_STRING);
+           data->ver_last = t;
+       }
+       return;
+    }
+
+    /* Reply to user via /msg or on channel depending on origin.
+     * Also make sure to not answer too frequently.
+     */
+    if (t - data->zen_last > ANSWER_RATE) {
+       if (irc_match(data->text, "*why *") ||
+           irc_match(data->text, "*how *") ||
+           irc_match(data->text, "*who *") ||
+           irc_match(data->text, "*what *") ||
+           irc_match(data->text, "*where *") ||
+           irc_match(data->text, "*when *")) {
+           data->zen_last = t;
+           str = zen();
+           /* Let's simulate a human delay (2 - 10 secs) */
+           r = 2 + (rand() % 8);
+           if (!mm_strcmp(data->param[0], data->nickname))
+               cron_add(r, "PRIVMSG %s :%s\r\n", data->nick, str);
+           else
+               cron_add(r, "PRIVMSG %s :%s: %s\r\n", data->param[0],
+                        data->nick, str);
+       }
+    }
+}
+
+static void main_noticed(struct info *data)
+{
+    /* XXX */
+}
+
+static void main_joined(struct info *data)
+{
+    struct channel *ch;
+
+    /* data->text consists of channel name, data->nick of nickname */
+    if (!mm_strcmp(data->nick, data->nickname)) {
+       /* We have joined a new channel, create new channel node with it's
+        * users list
+        */
+       if ((ch = (struct channel *)allocnode(lchans, TRUE))) {
+           if ((ch->channel_users = openlist(malloc, free,
+                                             sizeof(struct channel), 8192,
+                                             0))) {
+               ch->channel_hash = hashstr64(data->text);
+               mm_strcpy(ch->channel_name, data->text);
+           }
+           appendnode(lchans, (node *)ch);
+       }
+    } else {
+       /* Another user joined one of the channels we are in, update
+        * corresponding channel's userlist
+        */
+       user_add(data->text, data->nick);
+    }
+}
+
+static void main_kicked(struct info *data)
+{
+    struct channel *ch;
+
+    /* data->param[0] consists of channel name, data->param[1] of nick */
+    ch = user_del(data->param[0], data->param[1]);
+    if (!mm_strcmp(data->param[1], data->nickname)) {
+       /* We have been kicked out of the channel, free all channel
+        * info and send a delayed join request
+        */
+       unlinknode(lchans, (node *)ch);
+       closelist(ch->channel_users);
+       freenode((node *)ch);
+       cron_add(REJOIN_DELAY, "JOIN :%s\r\n", data->param[0]);
+    }
+}
+
+static void main_parted(struct info *data)
+{
+    struct channel *ch;
+
+    /* data->nick consists of nickname, data->param[0] of channel name */
+    ch = user_del(data->param[0], data->nick);
+    if (!mm_strcmp(data->nick, data->nickname)) {
+       /* We have left a channel, free all channel info */
+       unlinknode(lchans, (node *)ch);
+       closelist(ch->channel_users);
+       freenode((node *)ch);
+    }
+}
+
+static void main_quitted(struct info *data)
+{
+    struct channel *ch, *tmp;
+
+    user_del(NULL, data->nick);
+    if (!mm_strcmp(data->nick, data->nickname)) {
+       /* We have quitted, free all channel info */
+       ch = (struct channel *)lchans->top;
+       while (ch) {
+           tmp = (struct channel *)ch->nod.next;
+           closelist(ch->channel_users);
+           unlinknode(lchans, (node *)ch);
+           freenode((node *)ch);
+           ch = tmp;
+       }
+    }
+}
+
+static void main_nick(struct info *data)
+{
+    u_int64_t hash, nhash;
+    struct channel *ch;
+    struct user *us;
+
+    /* A user changed nickname, make sure to update it in all our channels
+     * the user is in. If we are the one who changed nick, also update our
+     * internal own nickname record.
+     */
+    hash = hashstr64(data->nick);
+    nhash = hashstr64(data->text);
+
+    ch = (struct channel *)lchans->top;
+    while (ch) {
+       us = (struct user *)ch->channel_users->top;
+       while (us) {
+           if (us->user_hash == hash)
+               break;
+           us = (struct user *)us->nod.next;
+       }
+       if (us) {
+           us->user_hash = nhash;
+           mm_strcpy(us->user_nick, data->text);
+       }
+       ch = (struct channel *)ch->nod.next;
+    }
+    /*
+    if (!mm_strcmp(data->nick, data->nickname)) {
+       mm_strncpy(data->nickname, data->text, 63);
+       XXX Update hash also?
+    }
+    */
+}
+
+static void main_mode(struct info *data)
+{
+    /* A mode change occured for either ourself, a channel or another user.
+     * we only record those for now, but action could be taken also on certain
+     * mode change events. XXX
+     */
+    /* :PhadThai!mmondor@gobot.xisop MODE #netbsd-devel +o nanobit
+     * :nanobit MODE nanobit :+iw
+     */
+    if (data->ident && data->param[0] && *data->param[0] == '#') {
+       /* cmode or cumode change */
+    } else {
+       /* umode change */
+#ifdef DEBUG
+       printf("UMODE\n");
+#endif
+    }
+}
+
+static void main_invited(struct info *data)
+{
+    /* XXX */
+}
+
+static void main_names(struct info *data)
+{
+    char *words[64];           /* XXX check how many max */
+    int cols, i;
+
+    /* data->param[2] consists of channel name, and data->text of nicks */
+    if ((cols = mm_straspl(words, data->text, 63)) > 0) {
+       for (i = 0; i < cols; i++)
+           user_add(data->param[2], words[i]);
+    }
+}
+
+
+/* The main startup function */
+int main(int argc, char **argv)
+{
+    int port, fd;
+    char *server;
+    struct sockaddr_in client;
+    struct in_addr iaddr;
+    struct info data;
+
+    fdfuncs fdf = {
+       malloc,
+       free,
+       poll,
+       read,
+       write,
+       sleep,
+       usleep
+    };
+
+    if (argc != 3) {
+       printf("Usage: zenbot <nick> <ipaddress>\n");
+       exit (-1);
+    }
+
+    mm_memclr(&data, sizeof(data));
+
+    /* Parse arguments */
+    server = argv[2];
+    mm_strncpy(data.nickname, argv[1], 63);
+    port = 6667;
+
+    /* Our only requirement for pth consists of mmfd library which uses
+     * mutex read/write locking on fdb to ensure thread-safe operation.
+     * We do not use threads nor multiple processes here however.
+     */
+    pth_init();
+
+    /* Zen setup */
+    zen_init();
+
+    /* Setup our hash table buffers for user and host flood quotas */
+    if ((lusers = openlist(malloc, free, sizeof(struct freq), 8192, 0))) {
+       if ((lhosts = openlist(malloc, free, sizeof(struct freq), 8192, 0))) {
+           if ((lcron = openlist(malloc, free, sizeof(struct cron), 8192,
+                           0))) {
+               if ((lchans = openlist(malloc, free, sizeof(struct channel),
+                               8192, 0))) {
+                   /* Prepare our fd buffering wrapper */
+                   if ((data.fdb =
+                        fdbopen(&fdf, NULL, -1, 4096, 4096, 1024, 1024,
+                                INPUT_TIMEOUT * 1000,
+                                INPUT_TIMEOUT * 1000))) {
+                       /* Loop indefinitely */
+                       while (TRUE) {
+                           /* Connect to server */
+                           if ((fd = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
+                               mm_memclr(&client, sizeof(struct sockaddr_in));
+                               if ((inet_aton(server, &iaddr))) {
+                                   client.sin_family = AF_INET;
+                                   client.sin_addr.s_addr = iaddr.s_addr;
+                                   client.sin_port = htons(port);
+                                   if ((connect(fd,
+                                        (struct sockaddr *)&client,
+                                        sizeof(struct sockaddr_in))) != -1) {
+                                       fcntl(fd, F_SETFL, O_NONBLOCK);
+                                       fdbparam_set(data.fdb, fd,
+                                                    FDBP_FD);
+                                       /* Serve our purpose */
+                                       data.state = &states[STATE_IDENT];
+                                       main_loop(&data);
+                                       fdbflushr(data.fdb);
+                                       fdbflushw(data.fdb);
+                                       fdbparam_set(data.fdb, -1,
+                                                    FDBP_FD);
+                                   } else
+                                       sleep(30);
+                               }
+                               close(fd);
+                           }
+                       }
+                       fdbclose(data.fdb);
+                   }
+                   closelist(lchans);
+               }
+               closelist(lcron);
+           }
+           closelist(lhosts);
+       }
+       closelist(lusers);
+    }
+
+    exit(0);
+}
+
+
+/* This function consists of the main state switcher loop, and basically
+ * reads lines, calling the matching function handlers for the current
+ * state. When a state switch occurs, exit code of the current state is
+ * executed, and init code of the new state, if any.
+ * We only return if a handler function returns a NULL pointer, or if
+ * we loose connection with the server.
+ * We internally take care of PING replies.
+ */
+static void main_loop(struct info *data)
+{
+    struct state *curstate;
+    struct handler_func *fn;
+    int i;
+
+    if (data->state->state_init)
+       data->state->state_init(data);
+
+    while (TRUE) {
+       struct cron *nod, *tmp;
+       time_t t;
+
+       /* Verify if any queued data has expired and should be released out.
+        * unfortunately because of the way we currently work, some IRC
+        * activity is required to trigger this. XXX Ideally the fdbgets()
+        * timeout should be set to 1 when there is queued data, and timeout
+        * should not be assumed to be a server/connection failiure. For now,
+        * this should work fine on a channel with activity, or if we are
+        * on multiple channels.
+        */
+       t = time(NULL);
+       nod = (struct cron *)lcron->top;
+       while (nod) {
+           tmp = (struct cron *)nod->nod.next;
+           if (nod->expires < t) {
+               unlinknode(lcron, (node *)nod);
+               fdbputs(data->fdb, nod->data);
+               freenode((node *)nod);
+           }
+           nod = tmp;
+       }
+
+       fdbflushw(data->fdb);
+
+       if ((i = fdbgets(data->fdb, data->line, 511, FALSE)) > -1) {
+
+           /* We got a line, check if there exists any handler to serve
+            * it within current state. Execute the handler if required,
+            * and perform sane state switching if requested by a handler.
+            */
+
+           if (!mm_strncmp(data->line, "PING :", 6)) {
+               /* Transform PING to PONG and reply to server */
+               data->line[1] = 'O';
+               fdbprintf(data->fdb, "%s\r\n", data->line);
+               continue;
+           }
+
+           irc_parse(data);
+
+           /* Useful for debugging */
+#ifdef DEBUG
+           fwrite("\033[1m", 4, 1, stdout);
+           fwrite(data->line, i, 1, stdout);
+           fwrite("\r\n", 2, 1, stdout);
+           fwrite("\033[0m", 4, 1, stdout);
+           fflush(stdout);
+           printf("code: %d server: '%s' nick: '%s' ident: '%s' host: '%s' command: '%s' text: '%s' params: %d ",
+                data->code, data->server, data->nick, data->ident,
+                data->host, data->command, data->text, data->params);
+           for (i = 0; i < data->params; i++)
+               printf("param[%d]: '%s' ", i, data->param[i]);
+           printf("\r\n\r\n");
+           fflush(stdout);
+#endif
+
+           if (data->code == -1 && data->ident) {
+               int res;
+               bool allow = FALSE;
+
+               /* Check against flood by ident and hostname */
+               if (!(res = allow_entry(lhosts, data->host, FLOOD_HOST_MAX,
+                                       FLOOD_HOST_TIME,
+                                       FLOOD_HOST_IGNORE))) {
+                   if (!(res = allow_entry(lusers, data->ident,
+                                   FLOOD_USER_MAX, FLOOD_USER_TIME,
+                                   FLOOD_USER_IGNORE)))
+                       allow = TRUE;
+                   else if (res == 2)
+                       ignore_user(data);
+               } else if (res == 2)
+                   ignore_host(data);
+               if (!allow)
+                   continue;
+           }
+
+           curstate = data->state;
+           fn = curstate->state_handlers;
+           while (fn->handler_func) {
+               bool allow;
+
+               allow = FALSE;
+               if (fn->handler_code && data->code != -1) {
+                   if (fn->handler_code == data->code)
+                       allow = TRUE;
+               } else if (fn->handler_command && data->command) {
+                   if (!mm_strcmp(fn->handler_command, data->command))
+                       allow = TRUE;
+               }
+               if (allow) {
+                   /* Execute handler for matching pattern */
+                   fn->handler_func(data);
+                   if (data->state != curstate) {
+                       /* Perform state switching */
+                       if (curstate->state_exit)
+                           curstate->state_exit(data);
+                       if (data->state->state_init)
+                           data->state->state_init(data);
+                   }
+                   break;
+               }
+               fn++;
+           }
+
+       } else
+           break;
+    }
+}
+
+
+/* This function is useful to easily perform flood quota checking on nicks
+ * and/or hosts. Returns 0 if the entry is allowed, 1 if it is not, 2 if
+ * it just started to be ignored.
+ */
+static int
+allow_entry(list *lst, char *entry, unsigned long max, int secs,
+           long igndelay)
+{
+    u_int64_t hash = hashstr64(entry);
+    int ret = 0;
+    struct freq *nod, *tmp;
+    time_t t = time(NULL);
+
+    /* Locate if entry exists */
+    nod = (struct freq *)lst->top;
+    while (nod) {
+       tmp = (struct freq *)nod->nod.next;
+       if (nod->expires < t) {
+           /* Expired, drop this entry */
+           unlinknode(lst, (node *)nod);
+           freenode((node *)nod);
+       } else if (nod->hash == hash)
+           break;
+       nod = tmp;
+    }
+    if (nod) {
+       /* Entry existed, increase frequency and optionally toggle ignore */
+       nod->freq++;
+       if (!nod->ignore) {
+           if (nod->freq > max) {
+               nod->ignore = TRUE;
+               nod->expires += igndelay;
+               ret = 2;
+           }
+       } else
+           ret = 1;
+    } else {
+       /* Add new entry */
+       if ((nod = (struct freq *)allocnode(lst, FALSE))) {
+           nod->expires = t + secs;
+           nod->ignore = FALSE;
+           nod->hash = hash;
+           nod->freq = 1;
+           appendnode(lst, (node *)nod);
+       }
+    }
+
+    return (ret);
+}
+
+
+/* This function returns weither or not pat matches string.
+ * We only perform simple * and ? pattern matching, case-sensitive.
+ */
+static bool irc_match(char *str, char *pat)
+{
+    while (*pat ^ '*') {
+       if (!*str) {
+           if (*pat)
+               return (FALSE);
+           else
+               return (TRUE);
+       }
+       if (*str ^ *pat && *pat ^ '?')
+           return (FALSE);
+       pat++;
+       str++;
+    }
+
+    while (pat[1] == '*')
+       pat++;
+
+    do {
+       if (irc_match(str, pat + 1))
+           return (TRUE);
+    } while (*str++);
+
+    return (FALSE);
+}
+
+
+/* Some mmondor-style string parsing magic, fill data fields according to
+ * IRC protocol line
+ */
+static void irc_parse(struct info *data)
+{
+    char *ptr, *optr, *words[16];
+    size_t cols;
+    int i, i2;
+
+    /* Init, everything set to NULL, and code to -1 by default */
+    mm_strcpy(data->work, data->line);
+    ptr = data->work;
+    data->code = -1;
+    data->params = 0;
+    data->server = data->nick = data->ident = data->host = data->command =
+       data->text = NULL;
+
+    if (*ptr == ':') {
+       /* IRC lines start with ':' */
+       ptr++;
+       optr = ptr;
+       while (*ptr && *ptr != ':')
+           ptr++;
+       if (*ptr == ':') {
+           /* Multiword text field */
+           *ptr++ = 0;
+           data->text = ptr;
+       }
+       if ((cols = mm_straspl(words, optr, 16)) > 1) {
+           /* Determine the type of IRC protocol line and fill corresponding
+            * pointers.
+            */
+           if ((i = atoi(words[1]))) {
+               /* Code based line */
+               data->server = *words;
+               data->code = i;
+           } else {
+               /* Text command based line */
+               ptr = *words;
+               while (*ptr && *ptr != '.' && *ptr != '!')
+                   ptr++;
+               if (*ptr == '.')
+                   /* Server name in first column */
+                   data->server = *words;
+               else {
+                   /* Nickname in first column, optional ident and host */
+                   ptr = data->nick = *words;
+                   while (*ptr && *ptr != '!')
+                       ptr++;
+                   if (*ptr == '!') {
+                       *ptr++ = 0;
+                       data->ident = ptr;
+                       while (*ptr && *ptr != '@')
+                           ptr++;
+                       if (*ptr == '@') {
+                           *ptr++ = 0;
+                           data->host = ptr;
+                       }
+                   }
+               }
+               data->command = words[1];
+           }
+           if (cols > 2) {
+               /* Additional parameters before text */
+               i = 0;
+               for (i2 = 2; i2 < cols; i2++)
+                   data->param[i++] = words[i2];
+               data->params = i;
+           }
+       }
+    }
+}
+
+
+/* Adds a user to a channel, also checks for @ and + in start of nick to
+ * set according mode.
+ */
+static void user_add(char *chan, char *nick)
+{
+    u_int64_t chanhash, nickhash;
+    struct channel *ch;
+    struct user *us;
+    int mode;
+
+    mm_strlower(chan);
+    chanhash = hashstr64(chan);
+
+    mode = 0;
+    if (*nick) {
+       if (*nick == '@') {
+           mode |= (1L << 0);
+           nick++;
+       } else if (*nick == '+') {
+           mode |= (1L << 1);
+           nick++;
+       }
+    }
+    nickhash = hashstr64(nick);
+
+    /* Find channel user should be added to */
+    ch = (struct channel *)lchans->top;
+    while (ch) {
+       if (ch->channel_hash == chanhash)
+           break;
+       ch = (struct channel *)ch->nod.next;
+    }
+    if (ch) {
+       /* We found channel */
+       if ((us = (struct user *)allocnode(ch->channel_users, FALSE))) {
+           mm_strcpy(us->user_nick, nick);
+           us->user_hash = nickhash;
+           us->user_mode = mode;
+#ifdef DEBUG
+           printf("ADDED USER '%s' TO CHANNEL '%s'\n", nick, chan);
+#endif
+           appendnode(ch->channel_users, (node *)us);
+       }
+    }
+}
+
+
+/* Deletes a nick from specified channel, or from all channels if chan is NULL
+ */
+static struct channel *user_del(char *chan, char *nick)
+{
+    u_int64_t chanhash, nickhash;
+    struct channel *ch;
+    struct user *us;
+
+    nickhash = hashstr64(nick);
+    ch = NULL;
+
+    if (chan) {
+       /* Delete user on specified channel */
+       mm_strlower(chan);
+       chanhash = hashstr64(chan);
+       ch = (struct channel *)lchans->top;
+       while (ch) {
+           if (ch->channel_hash == chanhash)
+               break;
+           ch = (struct channel *)ch->nod.next;
+       }
+       if (ch) {
+           /* We found channel */
+           us = (struct user *)ch->channel_users->top;
+           while (us) {
+               if (us->user_hash == nickhash)
+                   break;
+               us = (struct user *)us->nod.next;
+           }
+           if (us) {
+               /* We found nick */
+               unlinknode(ch->channel_users, (node *)us);
+               freenode((node *)us);
+#ifdef DEBUG
+               printf("DELETED USER '%s' FROM CHANNEL '%s'\n", nick,
+                      chan);
+#endif
+           }
+       }
+    } else {
+       /* Delete user on all channels */
+       ch = (struct channel *)lchans->top;
+       while (ch) {
+           us = (struct user *)ch->channel_users->top;
+           while (us) {
+               if (us->user_hash == nickhash) {
+                   unlinknode(ch->channel_users, (node *)us);
+                   freenode((node *)us);
+                   break;
+               }
+               us = (struct user *)us->nod.next;
+           }
+           ch = (struct channel *)ch->nod.next;
+       }
+#ifdef DEBUG
+       printf("DELETED USER '%s' FROM ALL CHANNELS\n", nick);
+#endif
+    }
+
+    return (ch);
+}
+
+
+/* Allows to queue data to be sent after a certain delay has been observed. */
+static void cron_add(int secs, char *fmt, ...)
+{
+    struct cron *nod;
+    time_t t = time(NULL);
+    va_list arg_ptr;
+
+    if ((nod = (struct cron *)allocnode(lcron, FALSE))) {
+       nod->expires = t + secs;
+       va_start(arg_ptr, fmt);
+       vsnprintf(nod->data, 1023, fmt, arg_ptr);
+       va_end(arg_ptr);
+       appendnode(lcron, (node *)nod);
+    }
+}
+
+
+
+static void zen_init(void)
+{
+    int num;
+
+    for (num = 0; zen_strings[num]; num++);
+    zen_num = num;
+}
+
+static char *zen(void)
+{
+    /*return( zen_strings[rand() % zen_num] ); */
+    if (zen_offset > zen_num)
+       zen_offset = 0;
+    return (zen_strings[zen_offset++]);
+}
diff --git a/mmsoftware/clean.sh b/mmsoftware/clean.sh
new file mode 100755 (executable)
index 0000000..9ff584d
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh
+# $Id: clean.sh,v 1.1 2002/12/11 10:11:06 mmondor Exp $
+
+. mmlib/makefuncs.sh
+
+cd mmlib/
+clean mmlib
+cd ../
+
+cd mmpasswd/
+clean mmpasswd
+cd ../
+
+cd mmstatd/src/
+clean mmstatd
+cd ../../
+
+#cd mmsucom/
+#clean mmsucom
+#cd ../
+
+cd mmftpd/src/
+clean mmftpd
+cd ../../
+
+cd mmmail/src/mmsmtpd/
+clean mmsmtpd
+cd ../mmpop3d
+clean mmpop3d
+cd ../../../
diff --git a/mmsoftware/install.sh b/mmsoftware/install.sh
new file mode 100755 (executable)
index 0000000..65217c9
--- /dev/null
@@ -0,0 +1,172 @@
+#!/bin/sh
+# $Id: install.sh,v 1.1 2002/12/11 10:11:07 mmondor Exp $
+
+if [ "$1" = "help" ]; then
+       echo
+       echo 'You can optionally set the following environment variables'
+       echo 'to customize the installation process. For each is shown an'
+       echo 'example using the default value followed by a breif description.'
+       echo
+       echo 'export MMPREFIX="/usr/local"'
+       echo '   Allows to set the installation base directory. All files but'
+       echo '   configuration ones will be installed in directories relative'
+       echo '   to this one.'
+       echo
+       echo 'export MMCONFDIR="/etc"'
+       echo '   Directory in which configuration files should be stored.'
+       echo
+       echo 'export MMDEFAULTUSER="0"'
+       echo '   User new files and directories should be owned by, using'
+       echo '   user id or name.'
+       echo
+       echo 'export MMDEFAULTGROUP="0"'
+       echo '   Group new files and directories should be under, using id'
+       echo '   or name.'
+       echo
+       echo 'export MMADMINGROUP="staff"'
+       echo '   The administrators group, these can for instance view and the'
+       echo '   rotate mmstat statistics and execute mmpasswd. Will be'
+       echo '   created automatically if necessary.'
+       echo
+       echo 'export MMSTATDIR="/var/mmstatd"'
+       echo '   The directory in which mmstatd will store the stats database'
+       echo '   and log files.'
+       echo
+       echo 'export MMSTATDUSER="mmstatd"'
+       echo '   The user mmstatd will run under, to be automatically created.'
+       echo
+       echo 'export MMSTATDGROUP="mmstat"'
+       echo '   Group the mmstatd user should be part of, automatically'
+       echo '   created.'
+       echo
+       echo 'export MMFTPDUSER="mmftpd"'
+       echo '   The user mmftpd will run under, to be automatically created.'
+       echo
+       echo 'export MMFTPDGROUP="mmftpd"'
+       echo '   Group the mmftpd user should be part of, automatically'
+       echo '   created. The mmftpdpasswd configuration file will be readable'
+       echo '   by this group.'
+       echo
+       echo 'export MMMAILUSER="mmmail"'
+       echo '   The user mmmail daemons should run under, will be created'
+       echo '   automatically if nonexisting.'
+       echo
+       echo 'export MMMAILGROUP="mmmail"'
+       echo '   Group the mmmail user should be part of. Created if needed.'
+       echo
+       exit
+fi
+
+# Set defaults if not set
+if [ -z "$MMPREFIX" ]; then
+       export MMPREFIX='/usr/local'
+fi
+if [ -z "$MMCONFDIR" ]; then
+       export MMCONFDIR='/etc'
+fi
+if [ -z "$MMDEFAULTUSER" ]; then
+       export MMDEFAULTUSER='0'
+fi
+if [ -z "$MMDEFAULTGROUP" ]; then
+       export MMDEFAULTGROUP='0'
+fi
+if [ -z "$MMADMINGROUP" ]; then
+       export MMADMINGROUP='staff'
+fi
+if [ -z "$MMSTATDIR" ]; then
+       export MMSTATDIR='/var/mmstatd'
+fi
+if [ -z "$MMSTATDUSER" ]; then
+       export MMSTATDUSER='mmstatd'
+fi
+if [ -z "$MMSTATDGROUP" ]; then
+       export MMSTATDGROUP='mmstat'
+fi
+if [ -z "$MMFTPDUSER" ]; then
+       export MMFTPDUSER='mmftpd'
+fi
+if [ -z "$MMFTPDGROUP" ]; then
+       export MMFTPDGROUP='mmftpd'
+fi
+if [ -z "$MMMAILUSER" ]; then
+       export MMMAILUSER='mmmail'
+fi
+if [ -z "$MMMAILGROUP" ]; then
+       export MMMAILGROUP='mmmail'
+fi
+
+. mmlib/makefuncs.sh
+
+instgroup $MMADMINGROUP
+
+cd mmlib/
+instman mmfd.3 3
+instman mmfifo.3 3
+instman mmlist.3 3
+instman mmpath.3 3
+instman mmstat.3 3
+cd ../
+
+cd mmpasswd/
+instbin mmpasswd 750 $MMADMINGROUP
+instman mmpasswd.8 8
+cd ../
+
+cd mmstatd/src/
+instuser $MMSTATDUSER $MMSTATDGROUP
+killbin mmstatd
+instbin mmstatd 700
+instbin mmstat 750 $MMADMINGROUP
+instman mmstat.8 8
+instman mmstatd.8 8
+instman mmstatd.conf.5 5
+cd ../etc/
+instconf mmstatd.conf 640 $MMSTATDGROUP
+instdir $MMSTATDIR 750 $MMSTATDUSER $MMSTATDGROUP
+startbin mmstatd $MMCONFDIR/mmstatd.conf
+cd ../../
+
+cd mmftpd/src/
+instuser $MMFTPDUSER $MMFTPDGROUP
+killbin mmftpd
+instbin mmftpd 700
+instman mmftpd.8 8
+instman mmftpd.conf.5 5
+instman mmftpdpasswd.5 5
+cd ../etc/
+instconf mmftpd.conf 600
+instconf mmftpdpasswd 640 $MMFTPDGROUP
+startbin mmftpd $MMCONFDIR/mmftpd.conf
+cd ../../
+
+cd mmmail/src/mmsmtpd/
+instuser $MMMAILUSER $MMMAILGROUP
+killbin mmsmtpd
+instbin mmsmtpd 700
+instman mmsmtpd.8 8
+instman mmsmtpd.conf.5 5
+cd ../mmpop3d
+killbin mmpop3d
+instbin mmpop3d 700
+instman mmpop3d.8 8
+instman mmpop3d.conf.5 5
+cd ../../etc
+instconf mmsmtpd.conf 600
+instconf mmpop3d.conf 600
+startbin mmsmtpd $MMCONFDIR/mmsmtpd.conf
+startbin mmpop3d $MMCONFDIR/mmpop3d.conf
+cd ../src
+instman mmmail.8 8
+cd ../../
+
+echo
+echo "*** Please read the following man pages ***"
+echo
+echo "all users: mmstat(8), mmstatd(8), mmstatd.conf(5) mmpasswd(8)"
+echo "mmftpd users: mmftpd(8), mmftpd.conf(5), mmftpdpasswd(5)"
+echo "mmmail users: mmmail(8), mmsmtpd(8), mmsmtpd.conf(5), mmpop3d(8),"
+echo "              mmpop3d.conf(5)"
+echo "source auditors: mmstat(3), mmfd(3), mmlist(3), mmfifo(3), mmpath(3)"
+echo
+echo "Thank you for using mmsoftware."
+echo
diff --git a/mmsoftware/make.sh b/mmsoftware/make.sh
new file mode 100755 (executable)
index 0000000..01cd3c4
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+# $Id: make.sh,v 1.1 2002/12/11 10:11:07 mmondor Exp $
+
+. mmlib/makefuncs.sh
+
+cd mmlib/
+clean mmlib
+makebin mmlib
+cd ../
+
+cd mmpasswd/
+clean mmpasswd
+makebin mmpasswd
+cd ../
+
+cd mmstatd/src/
+clean mmstatd
+makebin mmstatd
+cd ../../
+
+cd mmftpd/src/
+clean mmftpd
+makebin mmftpd
+cd ../../
+
+cd mmmail/src/mmsmtpd/
+clean mmsmtpd
+makebin mmsmtpd
+cd ../mmpop3d
+clean mmpop3d
+makebin mmpop3d
+cd ../../
+
+echo
+echo 'You may now ./install.sh help or ./install.sh to install/upgrade'
+echo
diff --git a/mmsoftware/mmftpd/ChangeLog b/mmsoftware/mmftpd/ChangeLog
new file mode 100644 (file)
index 0000000..1638995
--- /dev/null
@@ -0,0 +1,473 @@
+$Id: ChangeLog,v 1.1 2002/12/11 10:11:16 mmondor Exp $
+
+
+
+Release: mmftpd 0.0.15 devl
+Date   : November 25, 2002
+By     : Matthew Mondor
+
+* Important change
+  - When desired, it is important for frontend scripts to be able to generate
+    required password hashes. This has not been easy with the previous method,
+    which did not even use standard base64 encoding but a custom one on the         MD5 result. Calling external binaries such as mmpasswd is not ideal.            For this reason standard case-insensitive hexadecimal representations of        MD5 hash is now used.
+    An attempt was made to provide a utility to easily convert old hashes to
+    new ones; This however is impossible due to a bug in the previous method
+    which caused the last few bytes of the MD5 results to be lost during the        base64 conversion. This is unfortunate.
+    A recommendation is to generate random passwords for the users, notify
+    them via email and then activate the new version using the new passwords
+    say, a week afterwards. This password format will then remain unchanged
+    forever.
+* Bugfixes
+  - When DISPLAY_FILE was set, the file in the / directory would not be
+    displayed at login, only when a CWD was made to it. Fixed.
+  - treesize() and treesize_edit() now use off_t type which corresponds
+    best to a system's type size for filesystem related offsets/sizes.
+    On BSD systems this becomes 64-bit.
+  - If an invalid path was used for CWD the server would not output the
+    expected 550 error line and the client would freese. This bug was
+    introduced when path sanity checking was rewritten for exists() in
+    mmftpd 0.0.12 devl.
+* Real asynchroneous functions support
+  - The Pth library has limits in that only one process is used for all
+    threads. The various pth_*() functions, and special care has to be taken
+    to prevent locking the whole process when a single thread needs to
+    perform operations, because of the non-preemptive nature of Pth.
+    Another side effect of using Pth is that SMP systems gain no performance
+    over single CPU systems. Moreover, some functions which can take a while
+    can lock the whole process when no pth_*() wrapper function exists to
+    do it. An obvious example is hostname resolving, which can lock the whole
+    process for long periods on slow networks.
+  - A solution was worked out to allow threads to execute real asynchroneous
+    functions without blocking the main process (and therefore allowing other
+    threads to remain responsive), and to even take advantage of
+    multi-processor systems where available.
+  - The technique uses an AmigaOS-like device task/thread which works over the
+    Pth thread-safe message passing mechanism, to serve the various
+    asynchroneous functions like a daemon would. This device internally uses
+    a pool of real asynchroneous processes to which it dispatches the requests
+    in a load-balanced manner via unix datagram sockets.
+  - The new ASYNC_PROCESSES configuration file option was added as a result,
+    which allows to specify the number of slave asynchroneous processes to run.
+  - This facility is currently used by mmftpd to generate password hashes,
+    to resolve hostnames (when enabled), and to evaluate user home directory
+    tree size (for accounts with quota limits).
+* Miscelaneous new features
+  - It is now possible to set "*" for the password of various users to allow
+    password-less logins. These will accept any given password or empty ones.
+  - mmstatd can now be specified configuration file to use at startup
+    via command-line argument.
+  - mmstat library will now first check for MMSTATCONF environment variable
+    for the configuration file to use instead of the default
+    "/etc/mmstatd.conf". This can be useful to non-privileged users who want
+    to run mmstatd.
+  - If the daemon is compiled with -DNODETACH, the main process will not
+    fork(). Useful for debugging.
+  - If compiled with -DNODROPPRIVS, daemon will make sure that it is not
+    started by the superuser to accept running, but will then not attempt
+    to perform any modifications on the current permissions. Useful for
+    non-privileged users.
+    The default is to only accept to be started by the superuser and then
+    drop privileges.
+* Other
+  - Optimized the command matching loop by using fast packed hashes
+  - Other optimizations were performed by moving some variables in
+    closer scope, usually resulting in compilers using registers for
+    appropriate variables. This is especially useful for GCC which
+    ignores register directives.
+  - Several environment variables can now be set to modify the behavior
+    of the install.sh script (eg: change installation prefix, default
+    group/user, etc).
+
+
+
+Release: mmftpd 0.0.14 devl
+Date   : November 8, 2002
+By     : Matthew Mondor
+
+* New features
+  - Added new CHROOT_DIR configuration parameter to optionally allow the
+    server to run enclosed into a chroot(2) jail.
+  - Added PASSWD_FILE configuration parameter to allow the administrator
+    to override the name of the /etc/mmftpdpasswd file by a custom one.
+  - When starting the server it is now possible to specify the configuration
+    file to read rather than /etc/mmftpd.conf. This allows to start several
+    copies using different configurations with the new CHROOT_DIR option.
+* Bug fixes
+  - When multiple groups feature was introduced in 0.0.10 devl, mmftpd stopped
+    setting the initial group using setgid(2), assuming that setgroups(2)
+    would. This was not the case, and mmftpd would then remain part of the
+    wheel group. It will now set the real and effective primary group, the
+    secondary groups, and then the real and effective user. The first group
+    of the GROUPS configuration file parameter is used for the primary group.
+    Although the virtual chroot jail is safe, now that it is possible to
+    disable file ownership checking for some accounts, and possibly use
+    read-only accounts in wider areas when wanted, this needed to be fixed.
+  - Removed the annoying LOOP: debugging which used to flood syslog.
+* Other
+  - Now uses getnameinfo(3) instead of gethostbyaddr(3) to resolve hostnames
+    if RESOLVE_HOSTS is TRUE. This is more suitable with threads, since 
+    gethostbyaddr(3) uses static data and required a mutex to be thread-safe.
+
+
+
+Release: mmftpd 0.0.13 devl
+Date   : October 28, 2002
+By     : Matthew Mondor
+
+* Important bug fix
+  - omission of an unlinknode() in the new rate limit feature was fixed.
+    The daemons could lock in a loop taking 100% CPU time. Fixed.
+
+
+
+Release: mmftpd 0.0.12 devl
+Date   : October 26, 2002
+By     : Matthew Mondor
+
+* New features
+  - Anti-DoS connection rate limit feature was added in mmserver library,
+    consequently new CONNECTION_RATE and CONNECTION_PERIOD configuration
+    file options were added. These are on a per-IP address basis.
+  - A new /etc/mmftpdpasswd (see mmftpdpasswd(5) man page) column has been
+    added which can be used to enable/disable checking for file ownership.
+    This sanity checking used to be obligatory, but it may be useful to
+    disable it for some read-only accounts, for instance.
+  - LIST and NLST will now properly evaluate each file for ownership and
+    regularity before displaying it, no longer subject to glibc issue about
+    it's incomplete opendir(3) implementation. In the case where ownership
+    verification is performed for an account, files owned by another user
+    than mmftpd will silently remain hidden from the user, but will still be
+    logged via the syslog facility for the administrator.
+  - Will now accept to resume really large files on systems which allow it.
+  - The bytes statistic counters were bumped to 64-bit.
+  - Globbing/pattern matching now uses fnmatch(3) although only operating
+    on the last path element as it used to. As a result more complex patterns
+    are allowed than '*' and '?' matching. At my great satisfaction, I can
+    now host my netbsd package repository using mmftpd.
+* Bug fixes
+  - Yet again a build/install script bug; Permissions would not be applied
+    properly to newly installed files, the problem started to occur when
+    automatic directory creation was added. This was now fixed. Users should
+    consider upgrading as fast as possible, or to make sure that their
+    configuration files in /etc/ have safe permissions.
+    See the following man page: mmftpdpasswd(5).
+    The install script will force safe permissions on the configuration files
+    while preserving them if they already exist.
+  - The staff group would not be created by install.sh if it did not exist.
+    Thanks to Jeroen Oostendorp for reporting this.
+  - mmpath(3) library's exists() function used to return the same 0 result
+    if the file/directory did not exist or if it existed but was owned by
+    another user than the one mmftpd ran under. This, under very particular
+    circumstances could have lead to unexpected behavior. exists() now returns
+    specific return codes for each situation, which mmftpd evaluates.
+    Example of a situation in which this could have been harmful: STOU needs
+    to make sure that a file does not exist before creating it. If a file
+    owned by another user existed, and was writable by the mmftpd user, it
+    would have been overwritten. A similar situation could have existed for
+    RNFR/RNTO, where a file could have been copied/moved over another one.
+    Of course a warning would still be issued in the logs. Normally, this
+    never happened as files owned by another user (if any) usually were not
+    writeable by others. But who knows, let's not assume anything.
+    See the mmpath(3) man page for the new exists() semantics.
+  - If a very long filename existed (of MMPATH_MAX length for instance)
+    LIST would not be able to display the whole filename. Fixed.
+  - The bandwidth shaping system was not accurate enough, as it used to
+    throttle by 4096 bytes blocks, requireing too much CPU time in syscalls
+    for high limits. It was rewritten using a new design which now only sleeps
+    if required after the maximum number of bytes allowed into a second were
+    transfered. So it now uses a second's worth of data resolution rather than
+    a 4096 bytes resolution. Some parts of mmfd library internals and API
+    were modified as a result (See mmfd(3) man page for details). Another
+    side effect is that it takes far less CPU time with high limits.
+    Thanks to Darren Price for the extensive testing and reports,
+    to Eric Weisenhaus and agrajag for reporting these earlier.
+  - There existed a potential race condition when hostname resolving was
+    enabled, which could cause a memory leak. This was fixed.
+    Thanks to Jeroen Oostendorp for reporting it.
+* Other
+  - The mmpath(3) exists() and ls() functions were optimized to achieve better
+    speed.
+  - Binary file transfers are no longer using the buffered fdbread() and
+    fdbwrite() functions but rather fdbrread() and fdbrwrite() directly,
+    saving some CPU cycles and avoiding buffer size splits.
+  - Some source code auditing and cleaning was made, several optimizations
+    were performed, some buffers were aligned to favorize word-copy/move
+    operations as opposed to single-byte moves, in mmstring library).
+  - Finally this ftpd starts to be decent.
+  - I thank everyone who are testing this software, and am again requesting
+    that it be evaluated, as I intend to eventually release mmftpd v1 stable.
+    For this to happen it has to be free of any issues, it is therefore
+    important to report any bug, to mmondor@gobot.ca. It also would be nice
+    if I could release with it a list of systems it is known to work on,
+    so if betatesters find some time to describe their setup to me via email
+    it would be appreciated. I yet have to test it again on ultrasparc with
+    Linux and Solaris for instance.
+
+
+
+Release: mmftpd 0.0.11 devl
+Date   : October 11, 2002
+By     : Matthew Mondor
+
+* Bug fixes
+  - Although mmftpdpasswd(5) man page specified that the mfs column was
+    expressed in bytes, the default etc/mmftpdpasswd and mmftpd daemon
+    still considered it to be in kilobytes. This was now fixed to really
+    use bytes for that entry. A block size could be 512 bytes, for instance.
+  - The build scripts did not behave as expected on systems which are using
+    bash for /bin/sh. Some script sections needed modifications to work
+    on both. Now seems to build fine on linux systems.
+  - Various new GCC versions warning issues were fixed.
+  - When installing, some directories were assumed to exist, they will
+    now be created when missing.
+  - A bug in mm_memmov() was fixed.
+
+
+
+Release: mmftpd 0.0.10 devl
+Date   : October 6, 2002
+By     : Matthew Mondor
+
+* New features
+  - Now uses mmstatd(8) for persistant statistics storage and volatile who
+    database. This includes mmstat(8) administration facility.
+    This daemon also comports crash recovery using internal logging.
+    See mmstatd(8) man page for more information.
+  - A new column was added to mmftpdpasswd(5) to optionally allow download
+    statistics counters on files available under a specific user.
+    This is currently being used to record statistics of mmsoftware, files
+    are stored under mmftpd, and the HTTP frontend links to files with FTP
+    URLs under the mmsoftware user.
+  - Daemon can now be part of more than one group, which is a requirement
+    to access mmstatd.
+  - It is now possible to set global maximum download and upload limits for
+    the whole server.
+  - Improved installation/upgrade make.sh and install.sh scripts, and use
+    of /bin/sh rather than make because of GNU/BSD make inconsistencies.
+  - mmftpd(8), mmftpd.conf(5), mmftpdpasswd(5),
+    mmpasswd(8), mmstatd(8), mmstat(8), mmstat(3), mmstatd.conf(5), mmpath(3)
+    man pages were written.
+* Bug fixes
+  - On several systems, if mmftpd's group did not match with a virtual user's
+    group new files would not be created with the right permissions. This
+    was fixed by the new feature where mmftpd process can be part of several
+    groups.
+  - Spaces were not allowed in passwords, and empty passwords were not allowed
+    for anonymous logins. This was fixed.
+  - Since mmpath library bugfix that was made in mmftpd 0.0.7, pattern
+    matching stopped working. valid_path() was now completely re-written
+    around a cleaner design and is more solid. Moreover, multiple consecutive
+    '/' in pathnames are now accepted and considered as a single '/',
+    conforming to IEEE 1003.1.
+  - On various systems, including OpenBSD, the NetBSD-style __RCSID and
+    __COPYRIGHT, although useful, caused problems. They were now replaced
+    by custom macros, which should fix compiling issues.
+    Thanks to Dinos Costanti for reporting the issue.
+* Other
+  - The code used to be compatible with older C compilers, it now was converted
+    to ANSI C. Use of __P() macro is no longer made.
+  - Some mmlist node manipulation functions were replaced by macros. 
+    mmlist(3) now privileges pre-allocated nodes when inserting them back
+    in the free nodes queue, making a better useage of memory.
+
+
+
+Release: mmftpd 0.0.9 devl
+Date   : July 22, 2002
+By     : Matthew Mondor
+
+* Bug fixes
+  - Fixed bandwidth shaping library, recent versions would totally ignore
+    bandwidth rate specified, because of erroneous zero initialization.
+    Also fixed a bug where bandwidth was always lower than specified speed.
+    Thanks to Eric Weisenhaus, Darren Price and agrajag for reporting these.
+  - When clients send ABOR, they often send control sequences which resulted
+    the command to not be recognized, this was fixed by adding a sevenbit
+    boolean parameter to fdbgets() function.
+  - Configuration value LOG_LEVEL would not accept values below 1
+  - Fixed strnicmp() which would sometimes cause various problems
+
+
+
+Release: mmftpd 0.0.8 devl
+Date   : June 4, 2002
+By     : Matthew Mondor
+
+* Improvements
+  - INPUT_TIMEOUT has been replaced by CONTROL_TIMEOUT and DATA_TIMEOUT so
+    it is now possible to specify independant transfer i/o timeout.
+  - Various optimizations
+* Bug fixes
+  - A pretty serious bug was fixed (which only affected glibc-based systems),
+    where syslog() would potentially be called with user supplied parts,
+    including fmt sequences. Thanks to BenoĆ®t Roussel for providing me with
+    a very detailed security advisory and to Guillaume Pelat for discovering
+    the problem.
+
+
+
+Release: mmftpd 0.0.7 devl
+Date   : May 19, 2002
+By     : Matthew Mondor
+
+* Config file support
+  - Server now reads /etc/mmftpd.conf which obsoletes previous mmversion.h
+    pre-compilation issue.
+  - Defaults are set, configuration file is parsed, then command line
+    arguments parsed which may override some of the configuration file
+    settings.
+  - Now possible to make install without overwriting the configuration
+    file.
+  - Syslog facility can now be specified.
+* Written script to ease compiling and upgrading faster.
+* Introduced a 64-bit hash function
+  - Internally used to improve various table lookups (last visited
+    directories and logged in users tables)
+  - Hash function was tested against duplicates with /usr/share/dict/words
+    and find / inputs with success
+* More verbose optional logging
+  - When level 3 logging verbosity is used, replies are now logged as well,
+    this is especially useful for beta-testers and debugging.
+* Added various motd-like features
+  - WELCOME_FILE displayed at user connection if specified
+  - MOTD_FILE displayed at successful login if specified
+  - DISPLAY_FILE if specified will cause CWD into a directory to check for
+    specified file name, and display it, if present.
+* Bug fixes
+  - The mmpath library contained a bug which could crash the daemon,
+    inapropriately using snprintf(), of which return values should not
+    be trusted. This did not permit remote execution of arbritrary code
+    from the client however, since mmfd's fdbgets() does not permit to read
+    binary data.
+  - Harmless, but still a bug, once into another CWD than /, the user could
+    indefinitely CWD . which would cause PWD to become for instance something
+    like this: /tmp/////// (upto MMPATH_MAX bytes long). Obviously any file
+    command would thereafter fail with a 5xx error until CWD was changed.
+  - The function which strips possible - parameters in commands used to
+    prevent using filenames containing -. It now only strips options before
+    any other parameters, as is standard on BSD systems.
+    Thanks to Lior Marantenboim for reporting this bug.
+* Server too busy message introduced
+  - When the maximum number of IP addresses have been reached, or when too
+    many connections from the same address occurs, a protocol-friendly message
+    is now sent before closing the connections. Some clients have been
+    confused with previous behavior.
+* Multiple interfaces support
+  - Adminstrator may now specify several IP addresses to bind() to, as well
+    as a server hostname for each, using LISTEN_IPS and SERVER_NAMES config
+    file parameters.
+* Now using getpwnam() and getgrnam() for more configurability
+  - Used to read /etc/passwd and /etc/group directly.
+
+
+
+Release: mmftpd 0.0.6 devl
+Date   : April 22, 2002
+By     : Matthew Mondor
+
+* Fixed a nasty bug
+  - As the mmfd library was lately fully redesigned some daemon code would
+    not properly use the status results when a connection was unexpectedly
+    lost.
+  - This rendered easy to crash the daemon flooding with alot of connections
+    not exiting using standard QUIT command.
+* Better disconnection logging
+   - The the general status/reason of the disconnection is now also logged
+     with the statistics via syslog.
+* Compilation issues resolved
+  - Fixed a conflict which occurred between the BSD and linux usleep()
+    function not using the same argument type (unsigned long vs useconds_t)
+  - mmpasswd would not compile properly on linux
+
+
+
+Release: mmftpd 0.0.5 devl
+Date   : April 17, 2002
+By     : Matthew Mondor
+
+* Implemented bandwidth shaping support
+  - mmfd library was modified alot, it now supports bandwidth shaping
+    capabilities which mmftpd finally takes adventage of.
+  - Two new fields were added in the mmftpdpasswd user configuration file
+    for download and upload speed limits on a per-user basis.
+  - A new mmversion.h global parameter was added to set read and write
+    allowed speeds on the control connection.
+  - Speeds are specified in KB/s and values as low as 1 will work, but
+    0 disables bandwidth shaping allowing maximum available transfer speeds.
+* Implemeted transfer statistics
+  - Now records the number of bytes in and out of control and data connections,
+    as well as number of files transfers in both directions.
+  - Will show statistics at STAT and via syslog at logout.
+* More descriptive logging on DoS attempts
+  - When a connection is rejected because the maximum number of IP addresses
+    is reached, or that maximum number of connections for an IP address
+    is exceeded, mmserver library now reports the offending IP address and
+    reason via syslog.
+
+
+
+Release: mmftpd 0.0.4 devl
+Date   : March 24, 2002
+By     : Matthew Mondor
+
+* Now unified in a CVS tree with various other daemons and main libraries
+  - This allows a better development method, preventing inconsistencies
+    and thus eliminating some bug sources, ideal for security oriented
+    software, and following the general BSD way of coding.
+* All code now distributed under the BSD license.
+  - I almost started to despise glibc already, and am coding almost
+    exclusively on NetBSD lately. Moreover this license tends to be
+    less restrictive. Almost all very useful glibc and linux features
+    were BSD derived anyways, working fine since quite a while.
+  - I however still intend to keep the code portable to linux
+* New buffering mmfd library
+  - All I/O operations have been greatly improoved
+* Will now be faster to restart
+  - TIME_WAIT lingering sockets would previously prevent mmftpd from starting
+    until it timeouts, fixed.
+
+
+
+Release: mmftpd 0.0.3 devl
+Date   : Mars 8, 2002
+By     : Matthew Mondor
+
+* Works better with some broken clients
+  - Some clients send - options to various FTP commands, notably LIST,
+    those will now be ignored instead of returning an error message.
+  - More efficient and secure syslog calling on systems supporting vsyslog()
+    (mostly BSDs as glibc doesn't have it)
+
+
+
+Release: mmftpd 0.0.2 devl
+Date   : January 6, 2002
+By     : Matthew Mondor
+
+* Wrote new linked list library
+  - More efficient node pre-buffering technique with allocnode()/freenode()
+    only calling malloc()/free() occasionally
+  - All of mmftpd core now uses those for efficiency
+  - Added a few new options in mmversion.h configuration file, related to
+    the page size that should be used. These values can be multiplied on
+    servers with high load.
+* Implemented maximum login limit per user
+  - A new column was added in the config file
+* Home directory size limit support
+  - Two new columns in config file, for optional maximum limit, and minimum
+    file size for filesystem
+  - User shared structure remembering current home directory limit, so that
+    even if there are simultanious logins of the same user quota is respected
+  - Tree size is only re-evaluated from scatch if no current logins of that
+    same user exists
+* Updated the README file
+  - Added more details, fixed a few mistakes
+
+
+
+Release: mmftpd 0.0.1 devl
+Date   : December 17, 2001
+By     : Matthew Mondor
+
+* Initial development release
+
diff --git a/mmsoftware/mmftpd/README b/mmsoftware/mmftpd/README
new file mode 100644 (file)
index 0000000..749369c
--- /dev/null
@@ -0,0 +1,236 @@
+$Id: README,v 1.1 2002/12/11 10:11:19 mmondor Exp $
+
+
+
+MMFTPD(8)               NetBSD System Manager's Manual               MMFTPD(8)
+
+NAME
+     mmftpd - FTP server with virtual users and bandwidth shaping support
+
+SYNOPSIS
+     mmftpd [config_file]
+
+DESCRIPTION
+     mmftpd probably consists of one of the most secure FTP servers available.
+     Secure here does not mean that encryption is supported; It mostly means
+     that the service is very unlikely to be used to gain higher privileges
+     remotely.  It runs under the privileges of a normal Unix user, and never
+     needs to become the superuser again. Most other FTP servers generally
+     have to execute alot of operations as the superuser, and are only tem-
+     porarily dropping their privileges when a successful login has been es-
+     tablished. This generally consists of a bad idea for public internet ser-
+     vices.
+
+     mmftpd calls setgroups(2) and setuid(2) once when it starts, and remains
+     unprivileged for it's whole lifetime.  Moreover, the server can be en-
+     closed into a real chroot(2) jail if wanted, and all ftp user home direc-
+     tories will be stored under this new root directory (as well as
+     /etc/mmftpdpasswd configuration file).
+
+     It then of course becomes up to the administrator to ensure that the user
+     the service runs under has no access to any unwanted resources, and that
+     PHP enabled HTTP virtual hosts be only accessed under SSL if high securi-
+     ty is wanted, since PHP has many vulnerability issues, and FTP passwords
+     are non-encrypted; It would be possible to login normally to upload want-
+     ed scripts and have access to the rest of the system.
+
+     Each FTP user is given the impression to be running as a normal Unix user
+     enclosed into a chroot(2) jail. This actually consists of an illusion, as
+     all files mmftpd can work with must be owned by the main Unix user the
+     daemon runs as (typically the mmftpd user), except in the case of read-
+     only accounts where it is possible if wanted to use files owned by other
+     users. No Unix users are required for the various FTP users, only virtual
+     ones specified in mmftpdpasswd(5) file. The mmpath(3) library provides
+     the required features for path sanity checking and manipulation. The ac-
+     tual location of the user's home directory is never disclosed.
+
+     For security considerations related to a virtual users service, special
+     files such as symbolic links are not allowed to be created, viewed or ac-
+     cessed via the service. Files which are not owned by the user the server
+     runs under will also not be accessible (unless otherwise specified). A
+     log entry of level 0 will be generated for any of those events. Addition-
+     ally, files starting with '.' are not allowed to be created or accessed.
+
+     mmftpd is not vulnerable to glob(3) issues which often have been exploit-
+     ed in other FTP servers to effect Denial of Service attacks, as it pro-
+     vides it's own simpler globbing.
+
+     Another interesting feature of mmftpd consists of it's bandwidth shaping
+     capability, without the need for special kernel support. Each user can be
+     set with custom maximum download and upload speeds. Additionally, the I/O
+     speed of the control connection can also be controlled (through which
+     commands are sent) and global server I/O speed limits can also be set.
+     The mmfd(3) library is used to provide this functionality for the mmftpd
+     server.
+
+     Also can be set custom maximum home directory size or quota per user
+     (even safe with multiple simultaneous logins of the same user), custom
+     permissions to restrict the available operations, maximum number of al-
+     lowed logins per user, connections per IP address, maximum connection
+     rate per address, etc. Please see the mmftpdpasswd(5) and mmftpd.conf(5)
+     man pages for details.
+
+     mmftpd uses the mmstat(3) interface to record various statistics, and to
+     maintain it's current who database. Please see the mmstatd(8) and
+     mmstat(8) man pages for information on how to use them.
+
+     Other than that, mmftpd provides all necessary standard important FTP
+     commands that are expected of a generally RFC compliant FTP server. This
+     includes active and passive file transfers, with resume capability in
+     both directions. It also handles the new alternate EPSV, LPSV, EPRT and
+     LPRT extentions, although only support for IPv4 is currently available,
+     and the permissions manipulation commands (CHMOD, UMASK) under the SITE
+     extention command.
+
+     Here is a list of all currently implemented commands (processed case in-
+     sensitively):
+
+           Command         Description
+           USER            Specify user to login as
+           PASS            Specify password for user
+           CWD             Change current working directory
+           CDUP            Change to parent directory
+           QUIT            Disconnects
+           PORT            Select port to connect
+           LPRT            Select port to connect (long format)
+           EPRT            Select port to connect (extended format)
+           PASV            Start passive mode
+           LPSV            Start passive mode (long format)
+           EPSV            Start passive mode (extended format)
+           TYPE            Set type of file transfer (ASCII, IMAGE, LOCAL)
+           STRU            Specify file structure (FILE)
+           MODE            Select file transfer mode (STREAM)
+           RETR            Retreive file
+           STOR            Upload file
+           STOU            Store unique file
+           APPE            Permits upload to append on file
+           ALLO            Allocate storage (actually NOOP)
+           REST            Restore download at offset
+           RNFR            Rename from
+           RNTO            Rename to
+           ABOR            Abort command
+           DELE            Delete file
+           RMD             Delete directory
+           MKD             Create directory
+           MDTM            Show file last modification time
+           PWD             Print working directory
+           LIST            List files (long format)
+           NLST            List file names (short format)
+           SIZE            Show file size
+           SYST            System information (UNIX)
+           STAT            Account info or list files
+           HELP            Help
+           NOOP            No operation
+           SITE CHMOD      Change file mode
+           SITE UMASK      Change creation mode mask
+           BEER            *cough*
+
+INSTALLATION
+     To compile and run mmftpd you will need to have the following libraries
+     installed:
+
+           libpth 1.4.1 or later (GNU Pth)
+           libmhash 0.8.9 or later (mhash)
+
+     Upgrading from mmftpd 0.0.14 or older to mmftpd 0.0.15 or later: an im-
+     portant change has been performed on the way password hashes are stored.
+     The main reason for this is that frontend scripts would not easily be
+     able to generate these. The password hashes will need to be changed for
+     all users.  Unfortunately, it was impossible to provide a utility to con-
+     vert old password hashes to new ones because of a bug in the previous
+     base64 conversion which would loose some of the last bytes of the MD5
+     hash. New hashes generated by mmpasswd(8) now consist of case-insensitive
+     ASCII hexadecimal 32 bytes representation of the 128-bit MD5 hash and
+     this will never change anymore in the future.
+
+     The mmftpd daemon should be easily compiled using the make.sh script pro-
+     vided with the mmsoftware CVS distribution, or the mmftpd distribution,
+     and installed using the install.sh script. This also should install re-
+     quired mmstatd daemon automatically as part of the process. The install
+     script does not overwrite any existing configuration files, and it often
+     consists of a good idea to see the man pages for any changes when upgrad-
+     ing.  Note that there are several configurable options which can be set
+     prior to calling install.sh script; Use the './install.sh help' command
+     to see a description of the environment variables which you may want to
+     set.  The mmlib/makedefs.sh file contains defaults for building commands,
+     library and include paths, and may be modified if compilation issues oc-
+     cur. The building process is no longer dependant on BSD or GNU make, only
+     /bin/sh is required.
+
+           # cd mmftpd/
+           # ./make.sh
+           # ./install.sh
+
+     The required binaries now should have been installed in /usr/local/sbin
+     directory.
+
+     Previously, all user home directories specified in mmftpdpasswd(5) file
+     had to be owned by the user the mmftpd daemon runs under (usually mmftpd
+     user), as well as all files within those directories. As a general rule
+     for read/write accounts, this should still be done. This is due to the
+     virtual nature of mmftpd server. It is also important then that the owner
+     have write permissions to all those files and directories, and executable
+     permission on all directories.  This is automatically forced by mmftpd
+     when new files and directories are created, despite the specified
+     umask(2) in the mmftpdpasswd(5) file for the user, or specified mode to
+     the SITE CHMOD command.
+
+     It now is possible to provide read-only accounts using files owned by an-
+     other user, which may be useful for some setups (See the mmftpdpasswd(5)
+     man page for more information).
+
+     mmftpd configurable parameters are found in /etc/mmftpd.conf configura-
+     tion file (See mmftpd.conf(5) man page for details). It also uses the
+     /etc/mmftpdpasswd file as it's users database and their permissions (See
+     mmftpdpasswd(5) man page).
+
+     As mmftpd uses mmstat(3) library, which requires mmstatd(8) to be run-
+     ning, it is suggested to start the mmstatd daemon before starting the
+     mmftpd server in the startup scripts of your system:
+
+           /usr/local/sbin/mmstatd
+           /usr/local/sbin/mmftpd
+
+     If anything seems to not work as expected I suggest to increase the log-
+     ging level to 3 or 4 and watching syslog files.
+
+SECURITY CONSIDERATIONS
+     The /etc/mmftpdpasswd file should only be readable by the superuser and
+     by mmftpd because all FTP user password hashes are stored in it.  This is
+     usually done using a permission mode of 0640, with root owner, and mmftpd
+     group on that file:
+
+           -rw-r-----  1 root  mmftpd  2544 Sep  8  2002 /etc/mmftpdpasswd
+
+     HTTP virtual hosts which have access to maintain CGIs, mod_perl or PHP,
+     any dynamic content method allowing the remote user to execute scripts on
+     the server, should only be remotely maintained under SSL encryption,
+     which a standard FTP server cannot provide. Otherwise it is possible for
+     a remote attacker to sniff the clear-text passwords and upload their own
+     scripts on the server, which usually have much more power than generating
+     dynamic content.
+
+FILES
+     /usr/local/sbin/mmftpd     The actual FTP server binary
+
+     /etc/mmftpd.conf           mmftpd global configuration file through which
+                                all configurable parameters may be managed
+                                (See mmftpd.conf(5) man page).
+
+     /etc/mmftpdpasswd          Virtual users database for mmftpd with their
+                                settings (See mmftpdpasswd(5) man page).
+
+AUTHOR
+     mmftpd was designed and written by Matthew Mondor, and is Copyright
+     2001-2002, Matthew Mondor, All Rights Reserved.  It was primarily devel-
+     opped on NetBSD but has also been tested on Linux prior to release.
+
+SEE ALSO
+     mmftpd.conf(5), mmftpdpasswd(5), mmstat(8), mmstatd(8), mmpasswd(8),
+     mmstat(3), mmpath(3), mmfd(3), glob(3), umask(2), setuid(2),
+     setgroups(2), chroot(2).
+
+BUGS
+     Please report any bug to mmondor@gobot.ca
+
+NetBSD 1.5.3                     19 Oct, 2002                                4
diff --git a/mmsoftware/mmftpd/clean.sh b/mmsoftware/mmftpd/clean.sh
new file mode 100755 (executable)
index 0000000..3411c02
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+# $Id: clean.sh,v 1.1 2002/12/11 10:11:20 mmondor Exp $
+
+. ../mmlib/makefuncs.sh
+
+cd ../mmlib/
+clean mmlib
+cd ../
+
+cd mmpasswd/
+clean mmpasswd
+cd ../
+
+cd mmstatd/src/
+clean mmstatd
+cd ../../
+
+cd mmftpd/src/
+clean mmftpd
+cd ../../
diff --git a/mmsoftware/mmftpd/etc/mmftpd.conf b/mmsoftware/mmftpd/etc/mmftpd.conf
new file mode 100644 (file)
index 0000000..5e54a8b
--- /dev/null
@@ -0,0 +1,115 @@
+; $Id: mmftpd.conf,v 1.1 2002/12/11 10:11:26 mmondor Exp $
+;
+; mmftpd configuration file (/etc/mmftpd.conf)
+; and # are considered comments, and can happen at start or end of line.
+; Read mmftpd.conf(5) man page for details.
+
+
+; Daemon administration options
+; -----------------------------
+;
+; Number of asynchroneous slave processes that should be launched and used
+; to perform tasks such as hostname resolving, tree size evaluation and
+; MD5 hashing.
+ASYNC_PROCESSES        3
+;
+; Optional location of directory we should chroot(2) to. Note that special
+; configuration is requried for this, /etc/hosts, /etc/resolv.conf and
+; /etc/mmftpdpasswd files will be required in the new root for instance.
+;CHROOT_DIR ""
+;
+; Name of the virtual ftp users configuration file,
+; (usually "/etc/mmftpdpasswd").
+PASSWD_FILE "/etc/mmftpdpasswd"
+;
+; Location of the path where to store our process ID
+PID_PATH       "/var/run/mmftpd.pid"
+;
+; User mmftpd should run as
+USER           "mmftpd"
+; Groups process should be part of
+GROUPS         "mmftpd mmstat"
+;
+; Increase this to higher values if high server load is expected
+ALLOC_BUFFERS  1
+;
+; Logging verbosity level for syslog
+;      0 = critical and important messages only
+;      1 = connections are logged
+;      2 = informational logging (transfers are logged)
+;      3 = verbose logging (all commands except PASS, plus replies)
+;      4 = even PASS is logged, with the user passwords
+LOG_FACILITY   LOG_AUTHPRIV            LOG_LEVEL       3
+
+
+; TCP service general and security options
+; ----------------------------------------
+;
+; IP addresses we should bind() to, separated by spaces
+LISTEN_IPS     "127.0.0.1"
+;
+; Advertized server hostnames, separated by spaces, there should be the same
+; number of entries than for LISTEN_IPS
+SERVER_NAMES   "ftp.localhost"
+;
+; Port we listen for connections on
+LISTEN_PORT    21
+;
+; Resolving hostnames for logging can make the system slow depending on
+; DNS server reliability and service load
+RESOLVE_HOSTS  FALSE
+;
+; Maximum number of bad commands user may issue per session
+MAX_ERRORS     16
+;
+; Total maximum number of simultanious different IP addresses we allow
+MAX_IPS                64
+; Maximum simultanious connections per single IP address we accept
+MAX_PER_IP     1
+;
+; Maximum number of connections per IP address to accept within
+; CONNECTION_PERIOD seconds (0 to disable connection rate limit)
+CONNECTION_RATE        10
+; Period in seconds in which a maximum of CONNECTION_RATE connections are
+; allowed
+CONNECTION_PERIOD 30
+;
+; This consists of the read/write bandwidth limits for the FTP CONTROL
+; connection, specified in kilobytes/second. 0 will disable bandwidth shaping.
+; IN is how fast we accept data from the client, and OUT for how fast we may
+; send data to the client. This is on a per-connection basis.
+; See /etc/mmftpdpasswd file for DATA transfer speed limits settings.
+BANDWIDTH_IN   1                       BANDWIDTH_OUT   1
+;
+; The global maximum bandwidth speed limit, in kilobytes per second, for the 
+; whole server, at which it is allowed to read and write from/to clients.
+; 0 for no limit.
+GBANDWIDTH_IN  0                       GBANDWIDTH_OUT  0
+
+
+; FTP related options
+; -------------------
+;
+; Maximum input/output timeout in seconds for the FTP control connection. Note
+; that this timeout will not cause the connection to be dropped if a transfer
+; is still ongoing however.
+CONTROL_TIMEOUT        300
+;
+; Maximum input/output timeout in secods for the FTP DATA connections (file
+; transfers).
+DATA_TIMEOUT   300
+;
+; If specified, tells which file to display to the user just after connection.
+;WELCOME_FILE  "/etc/mmftpdwelcome.txt"
+;
+; If specified, will be displayed to the user after a successful login.
+;MOTD_FILE     "/etc/mmftpdmotd.txt"
+;
+; If specified, will cause any file by that name to be displayed to the user
+; after a CWD (change current directory) occurs.
+DISPLAY_FILE   "README"
+;
+; If DISPLAY_FILE is used, number of last visited directories to remember
+; in the FIFO; These are used to prevent sending the directory message too
+; often over and over again.
+REMEMBER_CWDS  16
diff --git a/mmsoftware/mmftpd/etc/mmftpdpasswd b/mmsoftware/mmftpd/etc/mmftpdpasswd
new file mode 100644 (file)
index 0000000..6b4d0fd
--- /dev/null
@@ -0,0 +1,16 @@
+; $Id: mmftpdpasswd,v 1.1 2002/12/11 10:11:28 mmondor Exp $
+;
+; This consists of the /etc/mmftpdpasswd configuration file for mmftpd(8).
+; Please read the mmftpdpasswd(5) man page for details.
+
+; un           gr      pw                               s c u m M um  l  mhds    mfs  dl  ul  hd
+
+; Main anonymous user and an admin to manage the anonymous tree
+;anonymous     mmftpd  *                                1 1 0 0 0 022 0  0       0    16  16  /home/mmftpd/anonymous
+;admin         mmftpd  5f4dcc3b5aa765d61d8327deb882cf99 0 1 1 1 1 022 5  0       0    0   0   /home/mmftpd/anonymous
+;guest         mmftpd  *                                1 1 0 0 0 022 0  0       0    0   0   /home/mmftpd/anonymous
+
+; Some other user for an apache HTTP vhost
+;test          www     098f6bcd4621d373cade4e832627b4f6 0 1 1 1 0 027 5  1024    512  4   4   /home/www/test
+
+; vim:ts=8:nowrap:
diff --git a/mmsoftware/mmftpd/install.sh b/mmsoftware/mmftpd/install.sh
new file mode 100755 (executable)
index 0000000..7935269
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/sh
+# $Id: install.sh,v 1.1 2002/12/11 10:11:20 mmondor Exp $
+
+. ../mmlib/makefuncs.sh
+
+instgroup staff
+
+cd ../mmlib/
+instman mmfd.3 3
+instman mmfifo.3 3
+instman mmlist.3 3
+instman mmpath.3 3
+instman mmstat.3 3
+cd ../
+
+cd mmpasswd/
+instbin mmpasswd 750 staff
+instman mmpasswd.8 8
+cd ../
+
+cd mmstatd/src/
+instuser mmstatd mmstat
+killbin mmstatd
+instbin mmstatd 700
+instbin mmstat 750 staff
+instman mmstat.8 8
+instman mmstatd.8 8
+instman mmstatd.conf.5 5
+cd ../etc/
+instconf mmstatd.conf 640 mmstat
+instdir /var/mmstatd 750 mmstatd mmstat
+startbin mmstatd
+cd ../../
+
+cd mmftpd/src/
+instuser mmftpd mmftpd
+killbin mmftpd
+instbin mmftpd 700
+instman mmftpd.8 8
+instman mmftpd.conf.5 5
+instman mmftpdpasswd.5 5
+cd ../etc/
+instconf mmftpd.conf 600
+instconf mmftpdpasswd 640 mmftpd
+startbin mmftpd
+cd ../../
+
+echo
+echo "*** Please read the following man pages ***"
+echo
+echo "all users: mmstat(8), mmstatd(8), mmstatd.conf(5) mmpasswd(8)"
+echo "mmftpd users: mmftpd(8), mmftpd.conf(5), mmftpdpasswd(5)"
+echo "source auditors: mmstat(3), mmfd(3), mmlist(3), mmfifo(3), mmpath(3)"
+echo
+echo "Thank you for using mmsoftware."
+echo
diff --git a/mmsoftware/mmftpd/make.sh b/mmsoftware/mmftpd/make.sh
new file mode 100755 (executable)
index 0000000..6d6a1dd
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/sh
+# $Id: make.sh,v 1.1 2002/12/11 10:11:20 mmondor Exp $
+
+. ../mmlib/makefuncs.sh
+
+cd ../mmlib/
+clean mmlib
+makebin mmlib
+cd ../
+
+cd mmpasswd/
+clean mmpasswd
+makebin mmpasswd
+cd ../
+
+cd mmstatd/src/
+clean mmstatd
+makebin mmstatd
+cd ../../
+
+cd mmftpd/src/
+clean mmftpd
+makebin mmftpd
+cd ../
+
+echo
+echo 'You may now ./install.sh to install/upgrade to /usr/local'
+echo
diff --git a/mmsoftware/mmftpd/scripts/mmftpd.sh b/mmsoftware/mmftpd/scripts/mmftpd.sh
new file mode 100755 (executable)
index 0000000..358648c
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+#   /etc/init.d/mmftpd: start or stop daemon (Mondor)
+#
+# $Id: mmftpd.sh,v 1.1 2002/12/11 10:12:35 mmondor Exp $
+
+PATH=/bin:/sbin:/usr/bin:/usr/sbin
+
+case "$1" in
+  start)
+      echo -n "Starting up mmftpd daemon"
+       /usr/local/sbin/mmftpd
+      echo "."
+    ;;
+  stop)
+      echo -n "Shutting down mmftpd daemon"
+       /bin/kill `cat /var/run/mmftpd.pid`
+      echo "."
+    ;;
+  restart|force-reload)
+       /etc/init.d/mmftpd.sh stop
+       /bin/sleep 10s
+       /etc/init.d/mmftpd.sh start
+    ;;
+  *)
+      echo "Usage: /etc/init.d/mmftpd.sh {start|stop|restart|force-reload}"
+      exit 1
+    ;;
+esac
+
+exit 0
+
diff --git a/mmsoftware/mmftpd/src/Makefile b/mmsoftware/mmftpd/src/Makefile
new file mode 100644 (file)
index 0000000..0ece09e
--- /dev/null
@@ -0,0 +1,35 @@
+# $Id: Makefile,v 1.1 2002/12/11 10:11:28 mmondor Exp $
+
+CC = gcc
+MAKE = make
+RM = rm -f
+ECHO = echo
+
+CFLAGS += -D_REENTRANT -Wall
+
+PTHINCDIR != $(ECHO) `pth-config --cflags`
+PTHLIBDIR != $(ECHO) -L`pth-config --libdir`
+INCDIR = -I/usr/include -I/usr/local/include -I. -I../../mmlib $(PTHINCDIR)
+LIBDIR = -L/usr/local/lib -L/usr/lib -L/lib -L/usr/pkg/lib $(PTHLIBDIR)
+LIBS = ../../mmlib/libmmondor.a -lmhash -lpth -lc
+
+OBJS = mmftpd.o
+
+CCOBJ = $(CC) $(CFLAGS) -c $(INCDIR)
+
+
+
+all: $(OBJS)
+       $(CC) $(CFLAGS) -o mmftpd $(INCDIR) $(LIBDIR) $(OBJS) $(LIBS)
+
+
+
+
+clean:
+       -rm -f $(OBJS) mmftpd
+
+
+
+mmftpd.o:
+       $(CCOBJ) mmftpd.c
+
diff --git a/mmsoftware/mmftpd/src/makepart.sh b/mmsoftware/mmftpd/src/makepart.sh
new file mode 100755 (executable)
index 0000000..72e392c
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh
+# $Id: makepart.sh,v 1.1 2002/12/11 10:11:28 mmondor Exp $
+
+. ../../mmlib/makedefs.sh
+
+OBJS='mmftpd.o'
+BIN1='mmftpd'
+
+if [ "$1" = "clean" ]; then
+       show $RM $OBJS $BIN1
+       exit 0
+fi
+
+PTHINCDIR="`pth-config --cflags`"
+PTHLIBDIR="-L`pth-config --libdir`"
+INCDIR="-I../../mmlib $STDINC $PTHINCDIR"
+LIBDIR="$STDLIB $PTHLIBDIR"
+LIBS='../../mmlib/libmmondor.a -lmhash -lpth -lc'
+
+for obj in $OBJS; do
+       if [ ! -f $obj ]; then
+               src=`$ECHO $obj | $SED 's/\.o$/.c/'`
+               show $CC $CFLAGS -c $INCDIR $src
+       fi
+done
+
+show $CC $CFLAGS -o $BIN1 $INCDIR $LIBDIR $BIN1.o $LIBS
diff --git a/mmsoftware/mmftpd/src/mmftpd.8 b/mmsoftware/mmftpd/src/mmftpd.8
new file mode 100644 (file)
index 0000000..e10c028
--- /dev/null
@@ -0,0 +1,359 @@
+.\" $Id: mmftpd.8,v 1.1 2002/12/11 10:11:31 mmondor Exp $
+.\"
+.\" Copyright (C) 2002, Matthew Mondor
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\"    must display the following acknowledgement:
+.\"      This product includes software written by Matthew Mondor.
+.\" 4. The name of Matthew Mondor may not be used to endorse or promote
+.\"    products derived from this software without specific prior written
+.\"    permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
+.\" IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+.\" USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd 19 Oct, 2002
+.Dt MMFTPD 8
+.Os
+.Sh NAME
+.Nm mmftpd
+.Nd FTP server with virtual users and bandwidth shaping support
+.Sh SYNOPSIS
+.Nm mmftpd Op Ar config_file
+.Sh DESCRIPTION
+.Nm mmftpd
+probably consists of one of the most secure FTP servers available. Secure
+here does not mean that encryption is supported; It mostly means that the
+service is very unlikely to be used to gain higher privileges remotely.
+It runs under the privileges of a normal Unix user, and never needs to become
+the superuser again. Most other FTP servers generally have to execute alot
+of operations as the superuser, and are only temporarily dropping their
+privileges when a successful login has been established. This generally
+consists of a bad idea for public internet services.
+.Pp
+.Nm mmftpd
+calls
+.Xr setgroups 2
+and
+.Xr setuid 2
+once when it starts, and remains unprivileged for it's whole lifetime.
+Moreover, the server can be enclosed into a real
+.Xr chroot 2
+jail if wanted, and all ftp user home directories will be stored under this
+new root directory (as well as
+.Nm /etc/mmftpdpasswd
+configuration file).
+.Pp
+It then of course becomes up to the administrator to ensure that the user
+the service runs under has no access to any unwanted resources, and that
+PHP enabled HTTP virtual hosts be only accessed under SSL if high security
+is wanted, since PHP has many vulnerability issues, and FTP passwords are
+non-encrypted; It would be possible to login normally to upload wanted
+scripts and have access to the rest of the system.
+.Pp
+Each FTP user is given the impression to be running as a normal Unix user
+enclosed into a
+.Xr chroot 2
+jail. This actually consists of an illusion, as all files mmftpd can work
+with must be owned by the main Unix user the daemon runs as (typically the
+mmftpd user), except in the case of read-only accounts where it is possible
+if wanted to use files owned by other users. No Unix users are required for
+the various FTP users, only virtual ones specified in
+.Xr mmftpdpasswd 5
+file. The
+.Xr mmpath 3
+library provides the required features for path sanity checking and
+manipulation. The actual location of the user's home directory is never
+disclosed.
+.Pp
+For security considerations related to a virtual users service, special
+files such as symbolic links are not allowed to be created, viewed or accessed
+via the service. Files which are not owned by the user the server runs under
+will also not be accessible (unless otherwise specified). A log entry of level
+0 will be generated for any of those events. Additionally, files starting
+with '.' are not allowed to be created or accessed.
+.Pp
+.Nm mmftpd
+is not vulnerable to
+.Xr glob 3
+issues which often have been exploited in other FTP servers to effect Denial
+of Service attacks, as it provides it's own simpler globbing. 
+.Pp
+Another interesting feature of
+.Nm mmftpd
+consists of it's bandwidth shaping capability, without the need for special
+kernel support. Each user can be set with custom maximum download and upload
+speeds. Additionally, the I/O speed of the control connection can also be
+controlled (through which commands are sent) and global server I/O speed
+limits can also be set. The
+.Xr mmfd 3
+library is used to provide this functionality for the
+.Nm mmftpd
+server.
+.Pp
+Also can be set custom maximum home directory size or quota per user
+(even safe with multiple simultaneous logins of the same user),
+custom permissions to restrict the available operations, maximum number
+of allowed logins per user, connections per IP address, maximum connection
+rate per address, etc. Please see the
+.Xr mmftpdpasswd 5
+and
+.Xr mmftpd.conf 5
+man pages for details.
+.Pp
+.Nm mmftpd
+uses the
+.Xr mmstat 3
+interface to record various statistics, and to maintain it's current who
+database. Please see the
+.Xr mmstatd 8
+and
+.Xr mmstat 8
+man pages for information on how to use them.
+.Pp
+Other than that,
+.Nm mmftpd
+provides all necessary standard important FTP commands that are expected of a
+generally RFC compliant FTP server. This includes active and passive file
+transfers, with resume capability in both directions. It also handles the new
+alternate
+.Sy EPSV , LPSV , EPRT
+and
+.Sy LPRT
+extentions, although only support for IPv4 is currently available, and the
+permissions manipulation commands
+.Sy ( CHMOD , UMASK )
+under the
+.Sy SITE
+extention command.
+.Pp
+Here is a list of all currently implemented commands (processed case
+insensitively):
+.Bl -column XXXXXXXXXXXX -offset indent
+.It Fa Command Ta Fa Description
+.It Sy USER Ta Specify user to login as
+.It Sy PASS Ta Specify password for user
+.It Sy CWD Ta Change current working directory
+.It Sy CDUP Ta Change to parent directory
+.It Sy QUIT Ta Disconnects
+.It Sy PORT Ta Select port to connect
+.It Sy LPRT Ta Select port to connect (long format)
+.It Sy EPRT Ta Select port to connect (extended format)
+.It Sy PASV Ta Start passive mode
+.It Sy LPSV Ta Start passive mode (long format)
+.It Sy EPSV Ta Start passive mode (extended format)
+.It Sy TYPE Ta Set type of file transfer
+.Sy ( ASCII , IMAGE , LOCAL )
+.It Sy STRU Ta Specify file structure
+.Sy ( FILE )
+.It Sy MODE Ta Select file transfer mode
+.Sy ( STREAM )
+.It Sy RETR Ta Retreive file
+.It Sy STOR Ta Upload file
+.It Sy STOU Ta Store unique file
+.It Sy APPE Ta Permits upload to append on file
+.It Sy ALLO Ta Allocate storage (actually Sy NOOP )
+.It Sy REST Ta Restore download at offset
+.It Sy RNFR Ta Rename from
+.It Sy RNTO Ta Rename to
+.It Sy ABOR Ta Abort command
+.It Sy DELE Ta Delete file
+.It Sy RMD Ta Delete directory
+.It Sy MKD Ta Create directory
+.It Sy MDTM Ta Show file last modification time
+.It Sy PWD Ta Print working directory
+.It Sy LIST Ta List files (long format)
+.It Sy NLST Ta List file names (short format)
+.It Sy SIZE Ta Show file size
+.It Sy SYST Ta System information
+.Sy ( UNIX )
+.It Sy STAT Ta Account info or list files
+.It Sy HELP Ta Help
+.It Sy NOOP Ta "No operation"
+.It Sy SITE CHMOD Ta Change file mode
+.It Sy SITE UMASK Ta Change creation mode mask
+.It Sy BEER Ta *cough*
+.El
+.Sh INSTALLATION
+To compile and run mmftpd you will need to have the following libraries
+installed:
+.Bd -literal -offset indent
+libpth 1.4.1 or later (GNU Pth)
+libmhash 0.8.9 or later (mhash)
+.Ed
+.Pp
+.Nm "Upgrading from mmftpd 0.0.14 or older to mmftpd 0.0.15 or later:"
+an important change has been performed on the way password hashes are stored.
+The main reason for this is that frontend scripts would not easily be able to
+generate these. The password hashes will need to be changed for all users.
+Unfortunately, it was impossible to provide a utility to convert old password
+hashes to new ones because of a bug in the previous base64 conversion which
+would loose some of the last bytes of the MD5 hash. New hashes generated by
+mmpasswd(8) now consist of case-insensitive ASCII hexadecimal 32 bytes
+representation of the 128-bit MD5 hash and this will never change anymore in
+the future.
+.Pp
+The
+.Nm mmftpd
+daemon should be easily compiled using the
+.Nm make.sh
+script provided with the mmsoftware CVS distribution, or the mmftpd
+distribution, and installed using the
+.Nm install.sh
+script. This also should install required
+.Nm mmstatd
+daemon automatically as part of the process. The install script does not
+overwrite any existing configuration files, and it often consists of a good
+idea to see the man pages for any changes when upgrading.
+Note that there are several configurable options which can be set prior to
+calling
+.Nm install.sh
+script; Use the
+.Nm './install.sh help'
+command to see a description of the environment variables which you may want
+to set.
+The
+.Nm mmlib/makedefs.sh
+file contains defaults for building commands, library and include paths, and
+may be modified if compilation issues occur. The building process is no longer
+dependant on BSD or GNU make, only /bin/sh is required.
+.Bd -literal -offset indent
+# cd mmftpd/
+# ./make.sh
+# ./install.sh
+.Ed
+.Pp
+The required binaries now should have been installed in /usr/local/sbin
+directory.
+.Pp
+Previously, all user home directories specified in
+.Xr mmftpdpasswd 5
+file had to be owned by the user the
+.Nm mmftpd
+daemon runs under (usually mmftpd user), as well as all files within those
+directories. As a general rule for read/write accounts, this should still
+be done. This is due to the virtual nature of
+.Nm mmftpd
+server. It is also important then that the owner have write permissions to
+all those files and directories, and executable permission on all directories.
+This is automatically forced by
+.Nm mmftpd
+when new files and directories are created, despite the specified
+.Xr umask 2
+in the
+.Xr mmftpdpasswd 5
+file for the user, or specified mode to the
+.Sy SITE CHMOD
+command.
+.Pp
+It now is possible to provide read-only accounts using files owned by another
+user, which may be useful for some setups (See the
+.Xr mmftpdpasswd 5
+man page for more information).
+.Pp
+.Nm mmftpd
+configurable parameters are found in
+.Nm /etc/mmftpd.conf
+configuration file (See
+.Xr mmftpd.conf 5
+man page for details). It also uses the
+.Nm /etc/mmftpdpasswd
+file as it's users database and their permissions (See
+.Xr mmftpdpasswd 5
+man page).
+.Pp
+As
+.Nm mmftpd
+uses
+.Xr mmstat 3
+library, which requires
+.Xr mmstatd 8
+to be running, it is suggested to start the
+.Nm mmstatd
+daemon before starting the
+.Nm mmftpd
+server in the startup scripts of your system:
+.Bd -literal -offset indent
+/usr/local/sbin/mmstatd
+/usr/local/sbin/mmftpd
+.Ed
+.Pp
+If anything seems to not work as expected I suggest to increase the logging
+level to 3 or 4 and watching syslog files.
+.Sh SECURITY CONSIDERATIONS
+The
+.Nm /etc/mmftpdpasswd
+file should only be readable by the superuser and by
+.Nm mmftpd
+because all FTP user password hashes are stored in it.
+This is usually done using a permission mode of 0640, with root owner, and
+mmftpd group on that file:
+.Bd -literal -offset indent
+-rw-r-----  1 root  mmftpd  2544 Sep  8  2002 /etc/mmftpdpasswd
+.Ed
+.Pp
+HTTP virtual hosts which have access to maintain CGIs, mod_perl or PHP,
+any dynamic content method allowing the remote user to execute scripts
+on the server, should only be remotely maintained under SSL encryption,
+which a standard FTP server cannot provide. Otherwise it is possible for
+a remote attacker to sniff the clear-text passwords and upload their own
+scripts on the server, which usually have much more power than generating
+dynamic content.
+.Sh FILES
+.Bl -tag -width XXXXXXXXXXXXXXXXXXXXXXXXX -compact
+.It Pa /usr/local/sbin/mmftpd
+The actual FTP server binary
+.Pp
+.It Pa /etc/mmftpd.conf
+.Nm mmftpd
+global configuration file through which all configurable parameters may be
+managed (See
+.Xr mmftpd.conf 5
+man page).
+.Pp
+.It Pa /etc/mmftpdpasswd
+Virtual users database for
+.Nm mmftpd
+with their settings (See
+.Xr mmftpdpasswd 5
+man page).
+.El
+.Sh AUTHOR
+.Nm mmftpd
+was designed and written by Matthew Mondor, and is
+Copyright 2001-2002, Matthew Mondor, All Rights Reserved.
+It was primarily developped on NetBSD but has also been tested on Linux
+prior to release.
+.Sh SEE ALSO
+.Xr mmftpd.conf 5 ,
+.Xr mmftpdpasswd 5 ,
+.Xr mmstat 8 ,
+.Xr mmstatd 8 ,
+.Xr mmpasswd 8 ,
+.Xr mmstat 3 ,
+.Xr mmpath 3 ,
+.Xr mmfd 3 ,
+.Xr glob 3 ,
+.Xr umask 2 ,
+.Xr setuid 2 ,
+.Xr setgroups 2 ,
+.Xr chroot 2 .
+.Sh BUGS
+Please report any bug to mmondor@gobot.ca
diff --git a/mmsoftware/mmftpd/src/mmftpd.c b/mmsoftware/mmftpd/src/mmftpd.c
new file mode 100644 (file)
index 0000000..00c9d22
--- /dev/null
@@ -0,0 +1,4370 @@
+/* $Id: mmftpd.c,v 1.1 2002/12/11 10:12:25 mmondor Exp $ */
+
+/*
+ * Copyright (C) 2000-2002, Matthew Mondor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software written by Matthew Mondor.
+ * 4. The name of Matthew Mondor may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+
+
+/* HEADERS */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <poll.h>
+
+#include <syslog.h>
+#include <signal.h>
+#include <time.h>
+#include <dirent.h>
+#include <ctype.h>
+
+#include <pth.h>
+
+#include <mmtypes.h>
+#include <mmreadcfg.h>
+#include <mmfd.h>
+#include <mmlist.h>
+#include <mmserver.h>
+#include <mmpasswd.h>
+#include <mmpath.h>
+#include <mmlog.h>
+#include <mmstr.h>
+#include <mmstring.h>
+#include <mmfifo.h>
+#include <mmstat.h>
+#include "mmftpd.h"
+
+
+
+
+MMCOPYRIGHT("@(#) Copyright (c) 2002\n\
+\tMatthew Mondor. All rights reserved.\n");
+MMRCSID("$Id: mmftpd.c,v 1.1 2002/12/11 10:12:25 mmondor Exp $");
+
+
+
+
+/*
+ * We only support:
+ *
+ * TYPE - ASCII, IMAGE, LOCAL
+ * STRU - FILE
+ * MODE - STREAM
+ *
+ * Other modes and types are uncommon, some do not apply to TCP networking
+ * and others apply only to closed source mainframe operating systems.
+ */
+
+
+
+
+/* GLOBAL VARIABLES */
+
+/* Configuration options read from a file */
+static CONFIG CONF;
+
+/* These are used to find names for new message ports and to start transfer
+ * threads
+ */
+static pth_mutex_t portnum_lock;
+static unsigned long portnum;
+static pth_attr_t tthreadattr;
+
+/* List of logged in users and optionally current home directory size */
+static pth_mutex_t lusers_lock;
+static list *lusers;
+
+/* This is used so that clientenv structures be allocated/freed fast */
+static pth_mutex_t clenvs_lock;
+static list *clenvs;
+
+/* Global bandwidth shaping context */
+static fdbcontext fdbc;
+
+/* And the commands mapping for help and ascii->cmd conversion */
+static struct command commands[] = {
+    /* Hash, LogLevel, Cmd, Args, Description (NULL=unimplemented) */
+    {0, 3, "USER", "<user>", "Specify user to login as"},
+    {0, 4, "PASS", "<pass>", "Specify password for user"},
+    {0, 3, "ACCT", "<acct>", NULL},
+    {0, 3, "CWD", "<path>", "Change current working directory"},
+    {0, 3, "CDUP", "", "Change to parent directory"},
+    {0, 3, "SMNT", "<path>", NULL},
+    {0, 3, "QUIT", "", "Disconnects"},
+    {0, 3, "REIN", "", NULL},  /* Could be implementable */
+    {0, 3, "PORT", "<b0,b1,b2,b3,b4,b5>", "Select port to connect"},
+    {0, 3, "LPRT", "<b0,b1,b2,b3,b4,b5,b6,b7,b8>",
+       "Select port to connect"},
+    {0, 3, "EPRT", "|<af>|<address>|<port>|", "Select port to connect"},
+    {0, 3, "PASV", "", "Start passive mode"},
+    {0, 3, "LPSV", "", "Start long passive mode"},
+    {0, 3, "EPSV", "", "Start extended passive mode"},
+    {0, 3, "TYPE", "<A|E|I|L [<size>]>", "Set type of file transfer"},
+    {0, 3, "STRU", "<F|R|P>", "Specify file structure"},
+    {0, 3, "MODE", "<B|C|S>", "Select file transfer mode"},
+    {0, 2, "RETR", "<file>", "Retreive file"},
+    {0, 2, "STOR", "<file>", "Upload file"},
+    {0, 2, "STOU", "[<file>]", "Store unique file"},
+    {0, 2, "APPE", "<file>", "Permits upload to append on file"},
+    {0, 3, "ALLO", "<bytes> [<frame>]", "Allocate storage"},
+    {0, 2, "REST", "<offset>", "Restart/resume command"},
+    {0, 3, "RNFR", "<file>", "Rename from"},
+    {0, 3, "RNTO", "<file>", "Rename to"},
+    {0, 3, "ABOR", "", "Abort command"},
+    {0, 3, "DELE", "<file>", "Delete file"},
+    {0, 3, "RMD", "<dir>", "Delete directory"},
+    {0, 3, "MKD", "<dir>", "Create directory"},
+    {0, 3, "MDTM", "<file>", "Show file last modification time"},
+    {0, 3, "PWD", "", "Print working directory"},
+    {0, 3, "LIST", "[<path|pat>]", "List files"},
+    {0, 3, "NLST", "[<path|pat>]", "List file names"},
+    {0, 3, "SITE", "(CHMOD <mode> <file>|UMASK <mask>)",
+       "Execute a site command"},
+    {0, 3, "SIZE", "<file>", "Show file size"},
+    {0, 3, "SYST", "", "System information"},
+    {0, 3, "STAT", "[<path|pat>]", "Account info or list files"},
+    {0, 3, "HELP", "[<command>]", "Help"},
+    {0, 3, "NOOP", "", "No operation"},
+    {0, 5, "BEER", NULL, /* HELP won't show, and won't be logged */ ""},
+    {0, 0, NULL, NULL, NULL}
+};
+
+/* This consists of the auth state */
+static int (*state_auth[])(clientenv *) = {
+       auth_user,              /* USER */
+       auth_pass,              /* PASS */
+       NULL,                   /* ACCT */
+       NULL,                   /* CWD */
+       NULL,                   /* CDUP */
+       NULL,                   /* SMNT */
+       all_quit,               /* QUIT */
+       NULL,                   /* REIN */
+       NULL,                   /* PORT */
+       NULL,                   /* LPRT */
+       NULL,                   /* EPRT */
+       NULL,                   /* PASV */
+       NULL,                   /* LPSV */
+       NULL,                   /* EPSV */
+       NULL,                   /* TYPE */
+       NULL,                   /* STRU */
+       NULL,                   /* MODE */
+       NULL,                   /* RETR */
+       NULL,                   /* STOR */
+       NULL,                   /* STOU */
+       NULL,                   /* APPE */
+       NULL,                   /* ALLO */
+       NULL,                   /* REST */
+       NULL,                   /* RNFR */
+       NULL,                   /* RNTO */
+       NULL,                   /* ABOR */
+       NULL,                   /* DELE */
+       NULL,                   /* RMD */
+       NULL,                   /* MKD */
+       NULL,                   /* MDTM */
+       NULL,                   /* PWD */
+       NULL,                   /* LIST */
+       NULL,                   /* NLST */
+       NULL,                   /* SITE */
+       NULL,                   /* SIZE */
+       all_syst,               /* SYST */
+       NULL,                   /* STAT */
+       all_help,               /* HELP */
+       all_noop,               /* NOOP */
+       all_beer                /* BEER */
+};
+
+/* This consists of the main state */
+static int (*state_main[])(clientenv *) = {
+       all_user,               /* USER */
+       all_pass,               /* PASS */
+       NULL,                   /* ACCT */
+       main_cwd,               /* CWD */
+       main_cdup,              /* CDUP */
+       NULL,                   /* SMNT */
+       all_quit,               /* QUIT */
+       NULL,                   /* REIN */
+       main_port,              /* PORT */
+       main_lprt,              /* LPRT */
+       main_eprt,              /* EPRT */
+       main_pasv,              /* PASV */
+       main_lpsv,              /* LPSV */
+       main_epsv,              /* EPSV */
+       main_type,              /* TYPE */
+       main_stru,              /* STRU */
+       main_mode,              /* MODE */
+       main_retr,              /* RETR */
+       main_stor,              /* STOR */
+       main_stou,              /* STOU */
+       main_appe,              /* APPE */
+       main_allo,              /* ALLO */
+       main_rest,              /* REST */
+       main_rnfr,              /* RNFR */
+       main_rnto,              /* RNTO */
+       main_abor,              /* ABOR */
+       main_dele,              /* DELE */
+       main_rmd,               /* RMD */
+       main_mkd,               /* MKD */
+       main_mdtm,              /* MDTM */
+       main_pwd,               /* PWD */
+       main_list,              /* LIST */
+       main_nlst,              /* NLST */
+       main_site,              /* SITE */
+       main_size,              /* SIZE */
+       all_syst,               /* SYST */
+       main_stat,              /* STAT */
+       all_help,               /* HELP */
+       all_noop,               /* NOOP */
+       all_beer                /* BEER */
+};
+
+/* Here consists of the list array of states, each consisting of an array
+ * of state functions/commands, and a message code and text that gets issued
+ * when a NULL is found for a function.
+ */
+static struct state states[] = {
+    {state_auth, 530, "Authenticate first"},
+    {state_main, 502, "Unimplemented"}
+};
+
+static int LOGLEVEL;
+
+/* Quick index to end of transfer messages */
+static struct tr_messages tr_msg[] = {
+    {226, "Transfer complete"},
+    {426, "Transfer aborted"},
+    {426, "Transfer timeout"},
+    {426, "Quota exceeded"},
+    {426, "Connection lost"}
+};
+
+
+
+
+/* Functions common to all states */
+
+static int
+all_quit(clientenv *clenv)
+{
+    if (!reply(clenv->fdb, 221, FALSE, "%s Closing connection",
+               clenv->iface->hostname))
+       return (STATE_ERROR);
+    return (STATE_END);
+}
+
+
+static int
+all_help(clientenv *clenv)
+{
+    char *args[3], *tmp, *tmp3;
+    register int col;
+    fdbuf *fdb = clenv->fdb;
+    register char *tmp2;
+
+    clenv->errors++;
+
+    if ((mm_straspl(args, clenv->buffer, 2)) == 2) {
+       register int i = 0;
+       register int32_t chash;
+       register bool valid;
+
+       /* Help requested on a topic */
+       valid = FALSE;
+       if ((chash = mm_strpack32(args[1], 3)) != -1) {
+           for (i = 0; commands[i].name; i++) {
+               if (commands[i].hash == chash) {
+                   valid = TRUE;
+                   break;
+               }
+           }
+       }
+
+       if (valid) {
+           tmp = commands[i].name;
+           if (!(tmp2 = commands[i].args)) {
+               clenv->errors++;
+               if (!reply(fdb, 214, FALSE,
+                           "No help information for this command"))
+                   return (STATE_ERROR);
+           } else {
+               if (!(tmp3 = commands[i].desc)) {
+                   if (!reply(fdb, 214, FALSE,
+                               "Syntax: %s %s ; (not implemented)",
+                               tmp, tmp2))
+                       return (STATE_ERROR);
+               } else {
+                   if (!reply(fdb, 214, FALSE, "Syntax: %s %s ; %s", tmp,
+                               tmp2, tmp3))
+                       return (STATE_ERROR);
+               }
+           }
+       } else {
+           clenv->errors++;
+           if (!reply(fdb, 502, FALSE, "Unknown command"))
+               return (STATE_ERROR);
+       }
+
+    } else {
+       register int i;
+
+       /* Show all available topics */
+       if (!reply(fdb, 214, TRUE,
+                   "Available commands ('-' = not implemented)"))
+           return (STATE_ERROR);
+       if ((fdbwrite(fdb, "    ", 4)) != 4)
+           return (STATE_ERROR);
+
+       col = 0;
+       for (i = 0; (tmp = commands[i].name); i++) {
+           if (commands[i].args) {     /* Only show these */
+               if (commands[i].desc) {
+                   if (!fdbprintf(fdb, "%-4s    ", tmp))
+                       return (STATE_ERROR);
+               } else {
+                   if (!fdbprintf(fdb, "%-4s-   ", tmp))
+                       return (STATE_ERROR);
+               }
+               col++;
+           }
+           if (col == 8) {
+               if ((fdbwrite(fdb, "\r\n    ", 6)) != 6)
+                   return (STATE_ERROR);
+               col = 0;
+           }
+       }
+
+       if ((fdbwrite(fdb, "\r\n", 2)) != 2)
+           return (STATE_ERROR);
+       if (!reply(fdb, 214, FALSE, "HELP <command> for more information"))
+           return (STATE_ERROR);
+
+    }
+
+    return (STATE_CURRENT);
+}
+
+
+static int
+all_noop(clientenv *clenv)
+{
+    if (!reply(clenv->fdb, 200, FALSE, "Nothing performed"))
+       return (STATE_ERROR);
+
+    return (STATE_CURRENT);
+}
+
+
+static int
+all_beer(clientenv *clenv)
+{
+    if (!reply(clenv->fdb, 420, FALSE, "Here, enjoy!"))
+       return (STATE_ERROR);
+
+    return (STATE_CURRENT);
+}
+
+
+static int
+all_user(clientenv *clenv)
+{
+    clenv->errors++;
+    if (!reply(clenv->fdb, 530, FALSE, "Can't change user"))
+       return (STATE_ERROR);
+
+    return (STATE_CURRENT);
+}
+
+
+static int
+all_pass(clientenv *clenv)
+{
+    clenv->errors++;
+    if (!reply(clenv->fdb, 503, FALSE, "Login with USER first"))
+       return (STATE_ERROR);
+
+    return (STATE_CURRENT);
+}
+
+
+static int
+all_syst(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    char *args[2];
+
+    if ((mm_straspl(args, clenv->buffer, 1)) == 1) {
+       if (!reply(fdb, 215, FALSE,
+                   "UNIX Type: L8 - IEEE Std 1003 (``POSIX'')"))
+           nextstate = STATE_ERROR;
+    } else {
+       if (!reply(fdb, 550, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+
+
+/* Auth state functions */
+
+static int
+auth_user(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    char *args[3];
+
+    if (clenv->user || clenv->tuser) {
+       if (clenv->user) clenv->user = mmstrfree(clenv->user);
+       if (clenv->tuser) clenv->tuser = mmstrfree(clenv->tuser);
+       clenv->errors++;
+    }
+
+    if ((mm_straspl(args, clenv->buffer, 2)) == 2) {
+
+       /* First verify if anonymous access is requested, if so
+        * ensure that anonymous account exists
+        */
+       if (!(mm_strcmp(args[1], "anonymous"))) {
+
+           if (checkuser(args[1], clenv)) {
+               /* If this succeeded once a password is entered (any)
+                * the system is ready to continue further
+                */
+               clenv->anonymous = TRUE;
+               clenv->tuser = NULL;
+               if (!reply(fdb, 331, FALSE, "Enter your name for password"))
+                   nextstate = STATE_ERROR;
+           } else {
+               if (!reply(fdb, 530, FALSE,
+                           "No anonymous access on this server"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+
+       } else {
+
+           /* We don't want the user to know immediately if user exists
+            * or not, we will ask for password first, and let PASS do the
+            * rest.
+            */
+           if (!(clenv->tuser = mmstrdup(args[1])))
+               nextstate = STATE_ERROR;
+           clenv->anonymous = FALSE;
+           if (!reply(fdb, 331, FALSE, "Password required for this user"))
+               nextstate = STATE_ERROR;
+
+       }
+
+    } else {
+       if (!reply(fdb, 500, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+auth_pass(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    char pwhash[33], *pw;
+    u_int64_t uhash;
+
+    if (!clenv->user && !clenv->tuser) {
+
+       if (!reply(fdb, 503, FALSE, "Specify username first"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+
+    } else {
+
+       /* We previously used mm_straspl() here, which prevented use of space
+        * in passwords, or empty passwords for anonymous access.
+        * Now simply find space or end of string, and compare from there.
+        */
+       pw = clenv->buffer;
+       while (*pw && *pw > 32) pw++;
+       if (*pw) pw++;
+
+       /* We slow down password guessing programs */
+       if (clenv->attempts) pth_sleep(clenv->attempts * 2);
+       clenv->attempts++;
+
+       if (clenv->anonymous) {
+
+           /* We don't care about the password, simply allow anonymous
+            * access immediately
+            */
+           clenv->passwd = mmstrfree(clenv->passwd);
+           clenv->login = TRUE;
+           nextstate = STATE_MAIN;
+           mmsyslog(1, LOGLEVEL, "%08X Successful anonymous login",
+                   clenv->id);
+           mmstat(&clenv->vstat, STAT_UPDATE, 1, "mmftpd.who.anonymous");
+           mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmftpd.anonymous.logins");
+
+       } else {
+
+           /* clenv->tuser was set for us, check user existance and
+            * password accuracy.
+            */
+           if (checkuser(clenv->tuser, clenv)) {
+               bool nopass = FALSE;
+
+               if (clenv->passwd[0] == '*' && clenv->passwd[1] == '\0')
+                   nopass = TRUE;
+               if (nopass || hashpw(clenv, pwhash, pw)) {
+                   if (nopass || !(mm_stricmp(pwhash, clenv->passwd))) {
+                       /* Password matches, don't remember it */
+                       clenv->passwd = mmstrfree(clenv->passwd);
+                       clenv->login = TRUE;
+                       nextstate = STATE_MAIN;
+                       mmsyslog(1, LOGLEVEL,
+                               "%08X Successful login for %s",
+                               clenv->id, clenv->user);
+                       mmstat(&clenv->vstat, STAT_UPDATE, 1,
+                               "mmftpd.who.%s", clenv->user);
+                       mmstat(&clenv->pstat, STAT_UPDATE, 1,
+                               "mmftpd.%s.logins", clenv->user);
+                   } else {
+                       mmsyslog(0, LOGLEVEL,
+                               "%08X Failed login for %s (existing)",
+                               clenv->id, clenv->tuser);
+                   }
+               } else {
+                   mmsyslog(0, LOGLEVEL, "* auth_pass() - hashpw()");
+                   nextstate = STATE_ERROR;
+               }
+           } else {
+               mmsyslog(0, LOGLEVEL,
+                       "%08X Failed login for %s (unexisting)",
+                       clenv->id, clenv->tuser);
+           }
+
+           clenv->tuser = mmstrfree(clenv->tuser);
+       }
+
+       if (!clenv->login) {
+           clenv->errors++;
+           if (clenv->user) clenv->user = mmstrfree(clenv->user);
+           if (clenv->tuser) clenv->tuser = mmstrfree(clenv->tuser);
+           if (!reply(fdb, 530, FALSE, "Invalid login"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+
+    }
+
+    /* Make sure we respect maximum logins for this user, and store a structure
+     * for it, with current treesize
+     */
+    if (nextstate == STATE_MAIN) {
+       lusernode *lun;
+
+       if (*CONF.MOTD_FILE) {
+           if (!reply(fdb, 230, TRUE, NULL))
+               nextstate = STATE_ERROR;
+           if (!file_out(fdb, CONF.MOTD_FILE))
+               mmsyslog(0, LOGLEVEL, "Error opening MOTD_FILE \"%s\"",
+                       CONF.MOTD_FILE);
+       }
+       directory_message(clenv, 230);
+
+       pth_mutex_acquire(&lusers_lock, FALSE, NULL);
+
+       /* Verify if user in list */
+       lun = (lusernode *)lusers->top;
+       uhash = hashstr64(clenv->user);
+       while (lun) {
+           if (uhash == lun->hash) break;
+           lun = (lusernode *)lun->nod.next;
+       }
+       if (lun) {
+           /* Yes, make sure we observe limits if any */
+           if (!clenv->maxlogins || lun->logins < clenv->maxlogins) {
+               lun->logins++;
+               clenv->unode = lun;
+               if (clenv->anonymous) {
+                   if (!reply(fdb, 230, FALSE,
+                               "Restricted anonymous login ok"))
+                       nextstate = STATE_ERROR;
+               } else {
+                   if (!reply(fdb, 230, FALSE, "User login ok"))
+                       nextstate = STATE_ERROR;
+               }
+           } else {
+               if (!reply(fdb, 530, FALSE,
+                           "Maximum allowed logins exceeded for this user"))
+                   nextstate = STATE_ERROR;
+               else
+                   nextstate = STATE_CURRENT;
+           }
+       } else {
+           if (!clenv->maxlogins || 1 <= clenv->maxlogins) {
+               /* Add new user entry, and optionally record current tree size
+                */
+               if ((lun = (lusernode *)allocnode(lusers, FALSE))) {
+                   lun->hash = uhash;
+                   pth_mutex_init(&lun->lock);
+                   lun->logins = 1;
+                   if (clenv->maxhomesize) {
+                       if ((lun->homesize = treesize(clenv, clenv->home,
+                                       clenv->minfilesize)) == -1) {
+                           mmsyslog(0, LOGLEVEL,
+                                   "* auth_pass() - treesize(%s)",
+                                   clenv->home);
+                           nextstate = STATE_ERROR;
+                           lun = (lusernode *)freenode((node *)lun);
+                       }
+                   } else
+                       lun->homesize = 0;
+               } else {
+                   mmsyslog(0, LOGLEVEL,
+                           "* auth_pass() - allocnode(lusers)");
+                   nextstate = STATE_ERROR;
+               }
+               if (lun) {
+                   clenv->unode = lun;
+                   appendnode(lusers, (node *)lun);
+                   if (clenv->anonymous) {
+                       if (!reply(fdb, 230, FALSE,
+                                   "Restricted anonymous login ok"))
+                           nextstate = STATE_ERROR;
+                   } else {
+                       if (!reply(fdb, 230, FALSE, "User login ok"))
+                           nextstate = STATE_ERROR;
+                   }
+               }
+           } else {
+               if (!reply(fdb, 530, FALSE,
+                           "Maximum allowed logins exceeded for this user"))
+                   nextstate = STATE_ERROR;
+               else
+                   nextstate = STATE_CURRENT;
+           }
+       }
+
+       pth_mutex_release(&lusers_lock);
+    }
+
+    return (nextstate);
+}
+
+
+
+
+/* Main state functions */
+
+static int
+main_cwd(clientenv *clenv)
+{
+    char path[MMPATH_MAX], path2[MMPATH_MAX];
+    int t = MMPATH_DENIED;
+
+    if (valid_path(path, NULL, clenv->home, clenv->cwd, &clenv->buffer[3],
+               FALSE, TRUE)) {
+       /* Make sure directory exists and can be accessed securely */
+       snprintf(path2, MMPATH_MAX - 1, "%s%s", clenv->home, path);
+       if ((t = exists(path2, NULL, NULL, clenv->checkowner)) !=
+               MMPATH_DENIED) {
+           if (t == MMPATH_DIR) {
+               mm_strncpy(clenv->cwd, path, MMPATH_MAX - 1);
+               if (!directory_message(clenv, 250)) return (STATE_ERROR);
+               if (!reply(clenv->fdb, 250, FALSE, "Command successful"))
+                   return (STATE_ERROR);
+               return (STATE_CURRENT);
+           } else {
+               if (!reply(clenv->fdb, 550, FALSE,
+                           "No such file or directory"))
+                   return (STATE_ERROR);
+           }
+       } else {
+           if (!reply(clenv->fdb, 502, FALSE, "Permission denied"))
+               return (STATE_ERROR);
+       }
+    } else {
+       if (!reply(clenv->fdb, 550, FALSE, "Invalid pathname"))
+           return (STATE_ERROR);
+    }
+
+    clenv->errors++;
+    return (STATE_CURRENT);
+}
+
+
+static int
+main_cdup(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    char *args[2];
+
+    if ((mm_straspl(args, clenv->buffer, 1)) == 1) {
+       parent_directory(clenv->cwd);
+       if (!directory_message(clenv, 250)) nextstate = STATE_ERROR;
+       if (!reply(clenv->fdb, 250, FALSE, "Command successful"))
+           nextstate = STATE_ERROR;
+    } else {
+       if (!reply(clenv->fdb, 550, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_port(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT, i, port;
+    fdbuf *fdb = clenv->fdb;
+    char *args[7], ipaddr[16];
+    unsigned char p[7];
+    transfermsg *msg = &(clenv->tmsg);
+
+    /* PORT 127,0,0,1,255,26 */
+
+    /* First make sure that the command holds two space separated sections */
+    if ((mm_straspl(args, clenv->buffer, 2)) == 2) {
+       /* Now make sure that the second section holds 6 values */
+       if ((mm_strspl(args, args[1], 6, ',')) == 6) {
+           for (i = 0; i < 6; i++) p[i] = (char)atoi(args[i]);
+           /* Get port number */
+           port = (256 * p[4]) + p[5];
+           /* Get address */
+           sprintf(ipaddr, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
+           /* Perform sanity check */
+           if ((port > 1023) && (!mm_strcmp(ipaddr, clenv->c_ipaddr))) {
+               /* Everything seems valid make sure no transfers in progress */
+               if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+                   if (!msg->ongoing) {
+                       /* Send our PORT transfer request */
+                       msg->port = port;
+                       msg->ipaddr = ipaddr;
+                       msg->passive = FALSE;
+                       if (transfer_request(REQ_NEWPORT, TRUE, clenv)) {
+                           if (!reply(fdb, 200, FALSE,
+                                       "PORT command successful"))
+                               nextstate = STATE_ERROR;
+                       } else {
+                           if (!reply(fdb, 500, FALSE, "Error"))
+                               nextstate = STATE_ERROR;
+                           clenv->errors++;
+                           mmsyslog(0, LOGLEVEL,
+                                   "* main_port() - transfer_request(\
+REQ_NEWPORT)");
+                       }
+                   } else {
+                       if (!reply(fdb, 500, FALSE, "Transfer ongoing"))
+                           nextstate = STATE_ERROR;
+                       clenv->errors++;
+                   }
+               } else {
+                   if (!reply(fdb, 500, FALSE, "Error"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+                   mmsyslog(0, LOGLEVEL,
+                           "* main_port() - transfer_request(REQ_STATUS)");
+               }
+           } else {
+               mmsyslog(0, LOGLEVEL, "%08X Illegal port %s:%d",
+                       clenv->id, ipaddr, port);
+               if (!reply(fdb, 500, FALSE, "Illegal port or address"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 550, FALSE, "Command syntax error"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 550, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_lprt(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT, i, port;
+    fdbuf *fdb = clenv->fdb;
+    char *args[10], ipaddr[16];
+    unsigned char p[10];
+    transfermsg *msg = &(clenv->tmsg);
+
+    /* LPRT 4,4,127,0,0,1,2,204,36 */
+
+    /* First make sure that the command holds two space separated sections */
+    if ((mm_straspl(args, clenv->buffer, 2)) == 2) {
+       /* Now make sure that the second section holds 9 values */
+       if ((mm_strspl(args, args[1], 9, ',')) == 9) {
+           for (i = 0; i < 9; i++) p[i] = (char)atoi(args[i]);
+           if (p[0] == 4 && p[1] == 4 && p[6] == 2) {
+               /* Get port number */
+               port = (256 * p[7]) + p[8];
+               /* Get address */
+               sprintf(ipaddr, "%d.%d.%d.%d", p[2], p[3], p[4], p[5]);
+               /* Perform sanity check */
+               if ((port > 1023) && (!mm_strcmp(ipaddr, clenv->c_ipaddr))) {
+                   /* Everything seems valid make sure no transfers in
+                    * progress
+                    */
+                   if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+                       if (!msg->ongoing) {
+                           /* Send our PORT transfer request */
+                           msg->port = port;
+                           msg->ipaddr = ipaddr;
+                           msg->passive = FALSE;
+                           if (transfer_request(REQ_NEWPORT, TRUE, clenv)) {
+                               if (!reply(fdb, 200, FALSE,
+                                           "LPRT command successful"))
+                                   nextstate = STATE_ERROR;
+                           } else {
+                               if (!reply(fdb, 500, FALSE, "Error"))
+                                   nextstate = STATE_ERROR;
+                               clenv->errors++;
+                               mmsyslog(0, LOGLEVEL,
+                                       "* main_lprt() - transfer_request(\
+REQ_NEWPORT)");
+                           }
+                       } else {
+                           if (!reply(fdb, 500, FALSE, "Transfer ongoing"))
+                               nextstate = STATE_ERROR;
+                           clenv->errors++;
+                       }
+                   } else {
+                       if (!reply(fdb, 500, FALSE, "Error"))
+                           nextstate = STATE_ERROR;
+                       clenv->errors++;
+                       mmsyslog(0, LOGLEVEL,
+                               "* main_lprt() - transfer_request(\
+REQ_STATUS)");
+                   }
+               } else {
+                   mmsyslog(0, LOGLEVEL, "%08X Illegal port %s:%d",
+                           clenv->id, ipaddr, port);
+                   if (!reply(fdb, 500, FALSE, "Illegal port or address"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+               }
+           } else {
+               if (!reply(fdb, 522, FALSE, "Unimplemented protocol"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 550, FALSE, "Command syntax error"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 550, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_eprt(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT, port;
+    fdbuf *fdb = clenv->fdb;
+    char *args[5], ipaddr[16];
+    transfermsg *msg = &(clenv->tmsg);
+
+    /* EPRT |1|127.0.0.1|32035| */
+
+    /* First make sure that the command holds two space separated sections */
+    if ((mm_straspl(args, clenv->buffer, 2)) == 2) {
+       /* Now make sure that the second section holds 4 values */
+       if ((mm_strspl(args, args[1], 4, '|')) == 4) {
+           if (atoi(args[1]) == 1) {
+               /* Get port number, address is args[2] */
+               port = atoi(args[3]);
+               /* Perform sanity check */
+               if ((port > 1023 && port < 65536)
+                       && (!mm_strcmp(args[2], clenv->c_ipaddr))) {
+                   /* Everything seems valid, make sure no transfers in
+                    * progress
+                    */
+                   if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+                       if (!msg->ongoing) {
+                           /* Send our PORT transfer request */
+                           msg->port = port;
+                           msg->ipaddr = args[2];
+                           msg->passive = FALSE;
+                           if (transfer_request(REQ_NEWPORT, TRUE, clenv)) {
+                               if (!reply(fdb, 200, FALSE,
+                                           "EPRT command successful"))
+                                   nextstate = STATE_ERROR;
+                           } else {
+                               if (!reply(fdb, 500, FALSE, "Error"))
+                                   nextstate = STATE_ERROR;
+                               clenv->errors++;
+                               mmsyslog(0, LOGLEVEL,
+                                       "* main_eprt() - transfer_request(\
+REQ_NEWPORT)");
+                           }
+                       } else {
+                           if (!reply(fdb, 500, FALSE, "Transfer ongoing"))
+                               nextstate = STATE_ERROR;
+                           clenv->errors++;
+                       }
+                   } else {
+                       if (!reply(fdb, 500, FALSE, "Error"))
+                           nextstate = STATE_ERROR;
+                       clenv->errors++;
+                       mmsyslog(0, LOGLEVEL,
+                               "* main_eprt() - transfer_request(\
+REQ_STATUS)");
+                   }
+               } else {
+                   mmsyslog(0, LOGLEVEL, "%08X Illegal port %s:%d",
+                           clenv->id, ipaddr, port);
+                   if (!reply(fdb, 500, FALSE, "Illegal port or address"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+               }
+           } else {
+               if (!reply(fdb, 522, FALSE, "Unimplemented protocol"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 550, FALSE, "Command syntax error"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 550, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_pasv(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    transfermsg *msg = &(clenv->tmsg);
+    unsigned char a, b;
+
+    if (!clenv->buffer[4]) {
+       /* Make sure no transfers are in progress */
+       if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+           if (!msg->ongoing) {
+               /* Send our PASV transfer request */
+               msg->passive = TRUE;
+               if (transfer_request(REQ_NEWPORT, TRUE, clenv)) {
+                   a = msg->port / 256;
+                   b = msg->port % 256;
+                   if (!reply(fdb, 227, FALSE,
+                               "Entering passive mode (%s,%d,%d)",
+                               clenv->sipaddr, a, b))
+                       nextstate = STATE_ERROR;
+               } else {
+                   if (!reply(fdb, 500, FALSE, "Error"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+                   mmsyslog(0, LOGLEVEL,
+                           "* main_pasv() - transfer_request(REQ_NEWPORT)");
+               }
+           } else {
+               if (!reply(fdb, 500, FALSE, "Transfer ongoing"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 500, FALSE, "Error"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+           mmsyslog(0, LOGLEVEL,
+                   "* main_pasv() - transfer_request(REQ_STATUS)");
+       }
+    } else {
+       if (!reply(fdb, 501, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_lpsv(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    transfermsg *msg = &(clenv->tmsg);
+    unsigned char a, b;
+
+    if (!clenv->buffer[4]) {
+       /* Make sure no transfers are in progress */
+       if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+           if (!msg->ongoing) {
+               /* Send our PASV transfer request */
+               msg->passive = TRUE;
+               if (transfer_request(REQ_NEWPORT, TRUE, clenv)) {
+                   a = msg->port / 256;
+                   b = msg->port % 256;
+                   if (!reply(fdb, 228, FALSE,
+                               "Entering long passive mode (4,4,%s,2,%d,%d)",
+                               clenv->sipaddr, a, b))
+                       nextstate = STATE_ERROR;
+               } else {
+                   if (!reply(fdb, 500, FALSE, "Error"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+                   mmsyslog(0, LOGLEVEL,
+                           "* main_lpsv() - transfer_request(REQ_NEWPORT)");
+               }
+           } else {
+               if (!reply(fdb, 500, FALSE, "Transfer ongoing"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 500, FALSE, "Error"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+           mmsyslog(0, LOGLEVEL,
+                   "* main_lpsv() - transfer_request(REQ_STATUS)");
+       }
+    } else {
+       if (!reply(fdb, 501, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_epsv(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    transfermsg *msg = &(clenv->tmsg);
+
+    if (!clenv->buffer[4]) {
+       /* Make sure no transfers are in progress */
+       if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+           if (!msg->ongoing) {
+               /* Send our PASV transfer request */
+               msg->passive = TRUE;
+               if (transfer_request(REQ_NEWPORT, TRUE, clenv)) {
+                   if (!reply(fdb, 229, FALSE,
+                               "Entering extended passive mode (|||%d|)",
+                               msg->port))
+                       nextstate = STATE_ERROR;
+               } else {
+                   if (!reply(fdb, 500, FALSE, "Error"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+                   mmsyslog(0, LOGLEVEL,
+                           "* main_epsv() - transfer_request(REQ_NEWPORT)");
+               }
+           } else {
+               if (!reply(fdb, 500, FALSE, "Transfer ongoing"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 500, FALSE, "Error"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+           mmsyslog(0, LOGLEVEL,
+                   "* main_epsv() - transfer_request(REQ_STATUS)");
+       }
+    } else {
+       if (!reply(fdb, 501, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_type(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT, a;
+    fdbuf *fdb = clenv->fdb;
+    char *args[4];
+
+    if ((a = mm_straspl(args, clenv->buffer, 3)) == 2) {
+       if (!(mm_stricmp(args[1], "A"))) {
+           clenv->type = TYPE_ASCII;
+           if (!reply(fdb, 200, FALSE, "Type ASCII set"))
+               nextstate = STATE_ERROR;
+       } else if (!(mm_stricmp(args[1], "I"))) {
+           clenv->type = TYPE_IMAGE;
+           if (!reply(fdb, 200, FALSE, "Type IMAGE set"))
+               nextstate = STATE_ERROR;
+       } else if (!(mm_stricmp(args[1], "L"))) {
+           clenv->type = TYPE_LOCAL;
+           if (!reply(fdb, 200, FALSE, "Type LOCAL set (8 bits per byte)"))
+               nextstate = STATE_ERROR;
+       } else {
+           if (!reply(fdb, 504, FALSE, "Unimplemented type"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else if (a == 3) {
+       if (!(mm_stricmp(args[1], "L"))) {
+           if (atoi(args[2]) == 8) {
+               clenv->type = TYPE_LOCAL;
+               if (!reply(fdb, 200, FALSE,
+                           "Type LOCAL set (8 bits per byte)"))
+                   nextstate = STATE_ERROR;
+           } else {
+               if (!reply(fdb, 504, FALSE, "Only 8-bit bytes allowed"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 550, FALSE, "Command syntax error"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 550, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_stru(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    char *args[3];
+
+    if ((mm_straspl(args, clenv->buffer, 2)) == 2) {
+       if (!(mm_stricmp(args[1], "F"))) {
+           if (!reply(fdb, 200, FALSE, "Structure FILE set"))
+               nextstate = STATE_ERROR;
+       } else {
+           if (!reply(fdb, 504, FALSE, "Unimplemented structure"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 550, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_mode(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    char *args[3];
+
+    if ((mm_straspl(args, clenv->buffer, 2)) == 2) {
+       if (!(mm_stricmp(args[1], "S"))) {
+           if (!reply(fdb, 200, FALSE, "Mode STREAM set"))
+               nextstate = STATE_ERROR;
+       } else {
+           if (!reply(fdb, 502, FALSE, "Unimplemented mode"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 550, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_retr(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT, file;
+    fdbuf *fdb = clenv->fdb;
+    char path[MMPATH_MAX], cpath[MMPATH_MAX];
+    transfermsg *msg = &(clenv->tmsg);
+
+    if (valid_path(path, cpath, clenv->home, clenv->cwd, &clenv->buffer[4],
+               FALSE, FALSE)) {
+       if (exists(path, NULL, NULL, clenv->checkowner) == MMPATH_FILE) {
+           if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+               if (msg->port) {
+                   if (!msg->ongoing) {
+                       if ((file = open(path, O_RDONLY)) != -1) {
+                           msg->file = file;
+                           msg->list = 0;
+                           msg->download = TRUE;
+                           msg->path = cpath;
+                           msg->rrate = clenv->bw_out;
+                           msg->wrate = clenv->bw_in;
+                           if (!transfer_request
+                               (REQ_TRANSFER, TRUE, clenv)) {
+                               mmsyslog(0, LOGLEVEL,
+                                       "* main_retr() - transfer_request(\
+REQ_TRANSFER)");
+                               close(file);
+                           }
+                           if (clenv->stats) {
+                               mmstat(&clenv->pstat, STAT_UPDATE, 1,
+                                       "mmftpd.%s.%s", clenv->user, cpath);
+                           }
+                       } else {
+                           if (!reply(fdb, 425, FALSE, "Error"))
+                               nextstate = STATE_ERROR;
+                           clenv->errors++;
+                           mmsyslog(0, LOGLEVEL,
+                                   "* main_retr() - open(%s)", path);
+                       }
+                   } else {
+                       if (!reply(fdb, 425, FALSE,
+                                   "Transfer already ongoing"))
+                           nextstate = STATE_ERROR;
+                       clenv->errors++;
+                   }
+               } else {
+                   if (!reply(fdb, 425, FALSE,
+                               "Can't establish data connection"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+               }
+           } else {
+               if (!reply(fdb, 425, FALSE, "Error"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+               mmsyslog(0, LOGLEVEL,
+                       "* main_retr() - transfer_request(REQ_STATUS)");
+           }
+       } else {
+           if (!reply(fdb, 550, FALSE, "Not a plain file"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 550, FALSE, "No such file or directory"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+/* For functions that can create files, such as STOR, STOU, APPE, MKD and RNTO,
+ * we make sure to verify the length of the fullpathname before creating the
+ * file. This way, if the user supplied too long paths, or STOU would cause it
+ * to be too long, and thus truncated, the name should be considered invalid.
+ */
+static int
+main_stor(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT, file, i;
+    fdbuf *fdb = clenv->fdb;
+    char path[MMPATH_MAX], cpath[MMPATH_MAX];
+    transfermsg *msg = &(clenv->tmsg);
+    mode_t mode;
+    long fsize;
+
+    /* We only permit uploading if user has upload permissions. We however only
+     * allow overwriting an existing file if user also has modify permissions.
+     */
+    if (clenv->upload) {
+       if (valid_path(path, cpath, clenv->home, clenv->cwd,
+                   &clenv->buffer[4], FALSE, FALSE)
+               && (mm_strlen(path) < MMPATH_MAX - 2)) {
+           if ((i = exists(path, &fsize, NULL, clenv->checkowner)) !=
+                   MMPATH_DIR && i != MMPATH_DENIED) {
+               if ((i != MMPATH_NONE && clenv->modify) || i == MMPATH_NONE) {
+                   if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+                       if (msg->port) {
+                           if (!msg->ongoing) {
+                               mode = ~clenv->umask;
+                               mode &= ~0711;
+                               mode |= 0600;
+                               /* If file exists and was successfully
+                                * truncated, we have to update the current
+                                * tree size. A new file will be considered
+                                * minfilesize bytes. We open the file first
+                                * to prevent possible exploits a user may
+                                * use to exceed the quota, but, to make sure,
+                                * check for minfilesize first.
+                                */
+                               if (treesize_edit(clenv,
+                                           clenv->minfilesize)) {
+                                   if ((file = open(path,
+                                               O_WRONLY | O_CREAT | O_TRUNC,
+                                               mode)) != -1) {
+                                       if (i != MMPATH_NONE && fsize > 0)
+                                           treesize_edit(clenv,
+                                                (clenv->minfilesize - fsize));
+                                       if ((lchown(path, -1, clenv->gid))
+                                               == -1)
+                                           mmsyslog(0, LOGLEVEL,
+                                                   "* main_stor() - lchown(\
+%s)", path);
+                                       msg->file = file;
+                                       msg->list = 0;
+                                       msg->download = FALSE;
+                                       msg->path = cpath;
+                                       msg->rrate = clenv->bw_out;
+                                       msg->wrate = clenv->bw_in;
+                                       if (!transfer_request(REQ_TRANSFER,
+                                                   TRUE, clenv)) {
+                                           mmsyslog(0, LOGLEVEL,
+                                                   "* main_stor() -\
+ transfer_request(REQ_TRANSFER)");
+                                           close(file);
+                                       }
+                                   } else {
+                                       if (!reply(fdb, 425, FALSE, "Error"))
+                                           nextstate = STATE_ERROR;
+                                       clenv->errors++;
+                                       mmsyslog(0, LOGLEVEL,
+                                               "* main_stor() - open(%s)",
+                                               path);
+                                   }
+                               } else {
+                                   if (!reply(fdb, 502, FALSE,
+                                               "Quota exceeded"))
+                                       nextstate = STATE_ERROR;
+                                   clenv->errors++;
+                                   mmsyslog(0, LOGLEVEL,
+                                           "%08X Quota exceeded",
+                                           clenv->id);
+                               }
+                           } else {
+                               if (!reply(fdb, 425, FALSE,
+                                           "Transfer already ongoing"))
+                                   nextstate = STATE_ERROR;
+                               clenv->errors++;
+                           }
+                       } else {
+                           if (!reply(fdb, 425, FALSE,
+                                       "Can't establish data connection"))
+                               nextstate = STATE_ERROR;
+                           clenv->errors++;
+                       }
+                   } else {
+                       if (!reply(fdb, 425, FALSE, "Error"))
+                           nextstate = STATE_ERROR;
+                       clenv->errors++;
+                       mmsyslog(0, LOGLEVEL,
+                               "* main_stor() - transfer_request(\
+REQ_STATUS)");
+                   }
+               } else {
+                   if (!reply(fdb, 502, FALSE, "Permission denied"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+               }
+           } else {
+               if (!reply(fdb, 550, FALSE, "Not a plain file"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 550, FALSE, "Invalid filename"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 502, FALSE, "Permission denied"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_stou(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT, file, i, i2, i3;
+    fdbuf *fdb = clenv->fdb;
+    char path[MMPATH_MAX], cpath[MMPATH_MAX], prefix[MMPATH_MAX],
+       uniquepath[MMPATH_MAX], *tmp;
+    transfermsg *msg = &(clenv->tmsg);
+    mode_t mode;
+    unsigned int unique;
+
+    /* If user doesn't specify a name or path, a unique filename is generated
+     * on the current directory. Otherwise, the unique file ID will be appended
+     * as a postfix after the specified name at specified location.
+     */
+    if (clenv->upload) {
+       if (!clenv->buffer[4]) tmp = ".";
+       else tmp = &(clenv->buffer[4]);
+       if (valid_path(path, cpath, clenv->home, clenv->cwd, tmp, FALSE,
+                   FALSE)) {
+           /* Verify if specified path consists of a directory only, in which
+            * case we prefix the filename by "unique"
+            */
+           if (exists(path, NULL, NULL, clenv->checkowner) == MMPATH_DIR) {
+               /* Directory specified, generate our own filename prefix */
+               i = mm_strlen(path);
+               if (i && path[i - 1] != '/') {
+                   *prefix = '/';
+                   prefix[1] = 0;
+               } else *prefix = 0;
+               mm_strcat(prefix, "unique");
+           } else {
+               /* Keep the specified name as prefix */
+               *prefix = 0;
+           }
+           /* We are now looking for a unique filename we can create */
+           unique = rand();
+           i2 = 0;
+           do {
+               i2++;
+               i3 = rand() % 16;
+               for (i = 0; i < i3; i++) unique += rand();
+               snprintf(uniquepath, MMPATH_MAX - 1, "%s%s.%08X", path,
+                       prefix, unique);
+               i = mm_strlen(uniquepath);
+               if (!(i < MMPATH_MAX - 2)) break;
+           } while ((exists(uniquepath, NULL, NULL, clenv->checkowner) !=
+                       MMPATH_NONE) && i2 < 50);
+           if ((i2 < 50) && (i < MMPATH_MAX - 2)) {
+               /* We have found unique filename to create */
+               if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+                   if (msg->port) {
+                       if (!msg->ongoing) {
+                           mode = ~clenv->umask;
+                           mode &= ~0711;
+                           mode |= 0600;
+                           if (treesize_edit(clenv, clenv->minfilesize)) {
+                               if ((file = open(uniquepath,
+                                               O_WRONLY | O_CREAT,
+                                               mode)) != -1) {
+                                   if ((lchown(uniquepath, -1, clenv->gid))
+                                           == -1)
+                                       mmsyslog(0, LOGLEVEL,
+                                               "* main_stou() - lchown(%s)",
+                                               uniquepath);
+                                   msg->file = file;
+                                   msg->list = 0;
+                                   msg->download = FALSE;
+                                   snprintf(uniquepath, MMPATH_MAX - 1,
+                                           "%s%s.%08X", cpath, prefix,
+                                           unique);
+                                   msg->path = uniquepath;
+                                   msg->rrate = clenv->bw_out;
+                                   msg->wrate = clenv->bw_in;
+                                   if (!transfer_request(REQ_TRANSFER, TRUE,
+                                               clenv)) {
+                                       mmsyslog(0, LOGLEVEL,
+                                               "* main_stou() -\
+ transfer_request(REQ_TRANSFER)");
+                                       close(file);
+                                   }
+                               } else {
+                                   if (!reply(fdb, 425, FALSE, "Error"))
+                                       nextstate = STATE_ERROR;
+                                   clenv->errors++;
+                                   mmsyslog(0, LOGLEVEL,
+                                           "* main_stou() - open(%s)",
+                                           uniquepath);
+                               }
+                           } else {
+                               if (!reply(fdb, 502, FALSE, "Quota exceeded"))
+                                   nextstate = STATE_ERROR;
+                               clenv->errors++;
+                               mmsyslog(0, LOGLEVEL,
+                                       "%08X Quota exceeded", clenv->id);
+                           }
+                       } else {
+                           if (!reply(fdb, 425, FALSE,
+                                       "Transfer already ongoing"))
+                               nextstate = STATE_ERROR;
+                           clenv->errors++;
+                       }
+                   } else {
+                       if (!reply(fdb, 425, FALSE,
+                                   "Can't establish data connection"))
+                           nextstate = STATE_ERROR;
+                       clenv->errors++;
+                   }
+               } else {
+                   if (!reply(fdb, 425, FALSE, "Error"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+                   mmsyslog(0, LOGLEVEL,
+                           "* main_stou() - transfer_request(REQ_STATUS)");
+               }
+           } else {
+               if (!reply(fdb, 425, FALSE,
+                           "Coundn't generate unique filename"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+               mmsyslog(0, LOGLEVEL,
+                       "* main_stou() - Couldn't generate unique filename");
+           }
+       } else {
+           if (!reply(fdb, 550, FALSE, "Invalid filename"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 502, FALSE, "Permission denied"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_appe(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT, file = -1, i;
+    fdbuf *fdb = clenv->fdb;
+    char path[MMPATH_MAX], cpath[MMPATH_MAX];
+    transfermsg *msg = &(clenv->tmsg);
+    mode_t mode;
+
+    /* We only permit uploading if user has upload permissions. We however only
+     * allow appending to an existing file if user also has modify permissions.
+     */
+    if (clenv->upload) {
+       if (valid_path(path, cpath, clenv->home, clenv->cwd,
+                   &clenv->buffer[4], FALSE, FALSE)
+               && (mm_strlen(path) < MMPATH_MAX - 2)) {
+           if ((i = exists(path, NULL, NULL, clenv->checkowner)) !=
+                   MMPATH_DIR && i != MMPATH_DENIED) {
+               if ((i != MMPATH_NONE && clenv->modify)
+                       || i == MMPATH_NONE) {
+                   if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+                       if (msg->port) {
+                           if (!msg->ongoing) {
+                               if (i == MMPATH_NONE) {
+                                   mode = ~clenv->umask;
+                                   mode &= ~0711;
+                                   mode |= 0600;
+                                   if (treesize_edit(clenv,
+                                               clenv->minfilesize)) {
+                                       file = open(path, O_WRONLY | O_CREAT,
+                                               mode);
+                                   } else {
+                                       if (!reply(fdb, 502, FALSE,
+                                                   "Quota exceeded"))
+                                           nextstate = STATE_ERROR;
+                                       clenv->errors++;
+                                       mmsyslog(0, LOGLEVEL,
+                                               "%08X Quota exceeded",
+                                               clenv->id);
+                                   }
+                               } else
+                                   file = open(path, O_WRONLY | O_APPEND);
+                               if (file != -1) {
+                                   if (i == MMPATH_NONE) {
+                                       if ((lchown(path, -1, clenv->gid))
+                                               == -1)
+                                           mmsyslog(0, LOGLEVEL,
+                                                   "* main_appe() - lchown(\
+%s)", path);
+                                   }
+                                   msg->file = file;
+                                   msg->list = 0;
+                                   msg->download = FALSE;
+                                   msg->path = cpath;
+                                   msg->rrate = clenv->bw_out;
+                                   msg->wrate = clenv->bw_in;
+                                   if (!transfer_request(REQ_TRANSFER, TRUE,
+                                               clenv)) {
+                                       mmsyslog(0, LOGLEVEL,
+                                               "* main_appe() -\
+ transfer_request(REQ_TRANSFER)");
+                                       close(file);
+                                   }
+                               } else {
+                                   if (!reply(fdb, 425, FALSE, "Error"))
+                                       nextstate = STATE_ERROR;
+                                   clenv->errors++;
+                                   mmsyslog(0, LOGLEVEL,
+                                           "* main_appe() - open(%s)",
+                                           path);
+                               }
+                           } else {
+                               if (!reply(fdb, 425, FALSE,
+                                           "Transfer already ongoing"))
+                                   nextstate = STATE_ERROR;
+                               clenv->errors++;
+                           }
+                       } else {
+                           if (!reply(fdb, 425, FALSE,
+                                       "Can't establish data connection"))
+                               nextstate = STATE_ERROR;
+                           clenv->errors++;
+                       }
+                   } else {
+                       if (!reply(fdb, 425, FALSE, "Error"))
+                           nextstate = STATE_ERROR;
+                       clenv->errors++;
+                       mmsyslog(0, LOGLEVEL,
+                               "* main_appe() - transfer_request(\
+REQ_STATUS)");
+                   }
+               } else {
+                   if (!reply(fdb, 502, FALSE, "Permission denied"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+               }
+           } else {
+               if (!reply(fdb, 550, FALSE, "Not a plain file"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 550, FALSE, "Invalid filename"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 502, FALSE, "Permission denied"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_allo(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    char *args[3];
+
+    if ((mm_straspl(args, clenv->buffer, 2)) == 2) {
+       if (!reply(clenv->fdb, 202, FALSE, "Nothing done"))
+           nextstate = STATE_ERROR;
+    } else {
+       if (!reply(clenv->fdb, 550, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_rest(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    char *args[3];
+
+    if ((mm_straspl(args, clenv->buffer, 2)) == 2) {
+       clenv->rest = (off_t)atoll(args[1]);
+       if (!reply(clenv->fdb, 350, FALSE,
+                   "Restarting at %lld, waiting for STOR/RETR",
+                   clenv->rest))
+           nextstate = STATE_ERROR;
+    } else {
+       if (!reply(clenv->fdb, 550, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_rnfr(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT, t = MMPATH_DENIED;
+    fdbuf *fdb = clenv->fdb;
+    char path[MMPATH_MAX];
+
+    if (clenv->modify) {
+
+       /* First forget last filename we remembered if any */
+       if (clenv->rnfr) clenv->rnfr = mmstrfree(clenv->rnfr);
+
+       if (valid_path(path, NULL, clenv->home, clenv->cwd,
+                   &clenv->buffer[4], FALSE, FALSE)) {
+           /* Make sure directory exists and can be accessed securely */
+           if ((t = exists(path, NULL, NULL, clenv->checkowner)) !=
+                   MMPATH_NONE && t != MMPATH_DENIED) {
+               if (!(clenv->rnfr = mmstrdup(path)))
+                   mmsyslog(0, LOGLEVEL, "* main_rnfr() - strdup()");
+           }
+       }
+
+       if (clenv->rnfr) {
+           if (!reply(fdb, 350, FALSE, "Exists, specify new name"))
+               nextstate = STATE_ERROR;
+       } else {
+           if (!reply(fdb, 550, FALSE, "No such file or directory"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+
+    } else {
+       if (!reply(fdb, 502, FALSE, "Permission denied"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_rnto(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    char path[MMPATH_MAX];
+
+    if (clenv->modify) {
+       if (clenv->rnfr) {
+           if (valid_path(path, NULL, clenv->home, clenv->cwd,
+                       &clenv->buffer[4], FALSE, FALSE)
+                   && (mm_strlen(path) < MMPATH_MAX - 2)) {
+               /* Make sure file does not exist */
+               if (exists(path, NULL, NULL, clenv->checkowner) ==
+                       MMPATH_NONE) {
+                   /* Rename file */
+                   if ((rename(clenv->rnfr, path)) != -1) {
+                       if (!reply(fdb, 250, FALSE, "Command successful"))
+                           nextstate = STATE_ERROR;
+                   } else {
+                       if (!reply(fdb, 521, FALSE, "Error renaming file"))
+                           nextstate = STATE_ERROR;
+                       clenv->errors++;
+                       mmsyslog(0, LOGLEVEL,
+                               "* main_rnto() - rename(%s,%s)",
+                               clenv->rnfr, path);
+                   }
+               } else {
+                   if (!reply(fdb, 521, FALSE, "File exists"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+               }
+           } else {
+               if (!reply(fdb, 550, FALSE, "Command syntax error"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+           clenv->rnfr = mmstrfree(clenv->rnfr);
+       } else {
+           if (!reply(fdb, 503, FALSE, "RNFR expected first"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 502, FALSE, "Permission denied"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_abor(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    char *args[2];
+    transfermsg *msg = &(clenv->tmsg);
+
+    if ((mm_straspl(args, clenv->buffer, 1)) == 1) {
+       if (clenv->rest) clenv->rest = 0;
+       if (clenv->rnfr) clenv->rnfr = mmstrfree(clenv->rnfr);
+       if (!reply(clenv->fdb, 226, FALSE, "Aborted"))
+           nextstate = STATE_ERROR;
+       if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+           if (msg->ongoing) {
+               if (!transfer_request(REQ_ABORT, TRUE, clenv))
+                   mmsyslog(0, LOGLEVEL,
+                           "* main_abor() - transfer_request(REQ_ABORT)");
+           }
+       } else
+           mmsyslog(0, LOGLEVEL,
+                   "* main_abor() - transfer_request(REQ_STATUS)");
+    } else {
+       if (!reply(clenv->fdb, 550, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_dele(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    char path[MMPATH_MAX];
+    register int i, t;
+    long fsize;
+
+    if (clenv->modify) {
+       if (valid_path(path, NULL, clenv->home, clenv->cwd,
+                   &clenv->buffer[4], FALSE, FALSE)) {
+           if ((i = exists(path, &fsize, NULL, clenv->checkowner)) !=
+                   MMPATH_NONE && i != MMPATH_DENIED) {
+               if (i == MMPATH_FILE) t = unlink(path);
+               else t = rmdir(path);
+               if (t != -1) {
+                   if (i == MMPATH_FILE)
+                       treesize_edit(clenv, -fsize);
+                   else
+                       treesize_edit(clenv, -clenv->minfilesize);
+                   if (!reply(fdb, 250, FALSE, "Command successful"))
+                       nextstate = STATE_ERROR;
+               } else {
+                   if (!reply(fdb, 521, FALSE, "Error deleting file"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+                   mmsyslog(0, LOGLEVEL, "* main_dele() - unlink(%s)",
+                           path);
+               }
+           } else {
+               if (!reply(fdb, 550, FALSE, "No such file or directory"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 550, FALSE, "Command syntax error"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 502, FALSE, "Permission denied"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_rmd(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    char path[MMPATH_MAX];
+
+    if (clenv->modify) {
+       if (valid_path(path, NULL, clenv->home, clenv->cwd,
+                   &clenv->buffer[3], FALSE, FALSE)) {
+           if (exists(path, NULL, NULL, clenv->checkowner) == MMPATH_DIR) {
+               if (!(rmdir(path))) {
+                   treesize_edit(clenv, -clenv->minfilesize);
+                   if (!reply(fdb, 250, FALSE, "Command successful"))
+                       nextstate = STATE_ERROR;
+               } else {
+                   if (!reply(fdb, 521, FALSE,
+                               "Error deleting directory (not empty?)"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+                   mmsyslog(0, LOGLEVEL, "* main_rmd() - rmdir(%s)", path);
+               }
+           } else {
+               if (!reply(fdb, 550, FALSE, "No such file or directory"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 550, FALSE, "No such file or directory"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 502, FALSE, "Permission denied"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_mdtm(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    char path[MMPATH_MAX], timebuf[16];
+    register int t;
+
+    if (valid_path(path, NULL, clenv->home, clenv->cwd, &clenv->buffer[4],
+               FALSE, FALSE)) {
+       if ((t = exists(path, NULL, timebuf, clenv->checkowner)) !=
+               MMPATH_NONE && t != MMPATH_DENIED) {
+           if (t == MMPATH_FILE) {
+               if (!reply(fdb, 213, FALSE, timebuf))
+                   nextstate = STATE_ERROR;
+           } else {
+               if (!reply(fdb, 550, FALSE, "Not a regular file"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 550, FALSE, "No such file or directory"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 550, FALSE, "No such file or directory"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_pwd(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    char *args[2];
+
+    if ((mm_straspl(args, clenv->buffer, 1)) == 1) {
+       if (!reply(clenv->fdb, 257, FALSE, "\"%s\"", clenv->cwd))
+           nextstate = STATE_ERROR;
+    } else {
+       if (!reply(clenv->fdb, 550, FALSE, "Command syntax error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_mkd(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    char path[MMPATH_MAX];
+    mode_t mode;
+
+    if (clenv->modify) {
+       if (valid_path(path, NULL, clenv->home, clenv->cwd,
+                   &clenv->buffer[3], FALSE, FALSE) &&
+               (mm_strlen(path) < MMPATH_MAX - 2)) {
+           if (exists(path, NULL, NULL, clenv->checkowner) == MMPATH_NONE) {
+               mode = ~clenv->umask;
+               mode |= 0700;
+               if (treesize_edit(clenv, clenv->minfilesize)) {
+                   if ((mkdir(path, mode)) != -1) {
+                       if ((lchown(path, -1, clenv->gid)) == -1)
+                           mmsyslog(0, LOGLEVEL,
+                                   "* main_mkd() - lchown(%s)", path);
+                       if (!reply(fdb, 250, FALSE, "Command successful"))
+                           nextstate = STATE_ERROR;
+                   } else {
+                       if (!reply(fdb, 521, FALSE,
+                                   "Error creating directory"))
+                           nextstate = STATE_ERROR;
+                       clenv->errors++;
+                       mmsyslog(0, LOGLEVEL, "* main_mkd() - mkdir(%s)",
+                               path);
+                   }
+               } else {
+                   if (!reply(fdb, 502, FALSE, "Quota exceeded"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+                   mmsyslog(0, LOGLEVEL, "%08X Quota exceeded", clenv->id);
+               }
+           } else {
+               if (!reply(fdb, 550, FALSE, "File exists"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 550, FALSE, "Command syntax error"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 502, FALSE, "Permission denied"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_list(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    transfermsg *msg = &(clenv->tmsg);
+    char path[MMPATH_MAX], *tmp;
+
+    /* Make sure that there is no ongoing transfer, and that PORT or PASV
+     * was used first
+     */
+    if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+       if (msg->port) {
+           if (!msg->ongoing) {
+               if (!clenv->buffer[4]) tmp = ".";
+               else tmp = &(clenv->buffer[4]);
+               if (!valid_path(path, NULL, clenv->home, clenv->cwd, tmp,
+                           TRUE, FALSE)) {
+                   /* We need to tell the transfer thread to not send
+                    * anything
+                    */
+                   msg->list = 3;
+                   msg->path = NULL;
+               } else {
+                   /* Normal LIST */
+                   msg->list = 1;
+                   msg->path = path;
+               }
+               msg->rrate = clenv->bw_out;
+               msg->wrate = clenv->bw_in;
+               /* Send transfer request */
+               if (!transfer_request(REQ_TRANSFER, TRUE, clenv))
+                   mmsyslog(0, LOGLEVEL,
+                           "* main_list() - transfer_request(REQ_TRANSFER)");
+           } else {
+               if (!reply(fdb, 425, FALSE, "Transfer already ongoing"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 425, FALSE, "Can't establish data connection"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 425, FALSE, "Error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+       mmsyslog(0, LOGLEVEL,
+               "* main_list() - transfer_request(REQ_STATUS)");
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_nlst(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    transfermsg *msg = &(clenv->tmsg);
+    char path[MMPATH_MAX], *tmp;
+
+    /* Make sure that there is no ongoing transfer, and that PORT or PASV
+     * was used first
+     */
+    if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+       if (msg->port) {
+           if (!msg->ongoing) {
+               if (!clenv->buffer[4]) tmp = ".";
+               else tmp = &(clenv->buffer[4]);
+               if (!valid_path(path, NULL, clenv->home, clenv->cwd, tmp,
+                           TRUE, FALSE)) {
+                   /* We need to tell the transfer thread to not send
+                    * anything
+                    */
+                   msg->list = 3;
+                   msg->path = NULL;
+               } else {
+                   /* Normal LIST */
+                   msg->list = 2;
+                   msg->path = path;
+               }
+               msg->rrate = clenv->bw_out;
+               msg->wrate = clenv->bw_in;
+               /* Send transfer request */
+               if (!transfer_request(REQ_TRANSFER, TRUE, clenv))
+                   mmsyslog(0, LOGLEVEL,
+                           "* main_list() - transfer_request(REQ_TRANSFER)");
+           } else {
+               if (!reply(fdb, 425, FALSE, "Transfer already ongoing"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 425, FALSE, "Can't establish data connection"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 425, FALSE, "Error"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+       mmsyslog(0, LOGLEVEL,
+               "* main_list() - transfer_request(REQ_STATUS)");
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_site(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT, i;
+    fdbuf *fdb = clenv->fdb;
+    char *args[5], path[MMPATH_MAX];
+    mode_t mode;
+
+    if ((i = mm_straspl(args, clenv->buffer, 4)) >= 2) {
+       if (!(mm_stricmp(args[1], "UMASK"))) {
+           if (clenv->modify && clenv->mumask) {
+               if (i == 2) {
+                   if (!reply(fdb, 200, FALSE, "Current UMASK is %04o",
+                               clenv->umask))
+                       nextstate = STATE_ERROR;
+               } else if (i == 3) {
+                   sscanf(args[2], "%o", &clenv->umask);
+                   if (clenv->umask > 07000) clenv->umask = 0;
+                   clenv->umask &= ~0700;
+                   clenv->umask |= 07000;
+                   if (!reply(fdb, 200, FALSE, "UMASK set to %04o",
+                               clenv->umask))
+                       nextstate = STATE_ERROR;
+               } else {
+                   if (!reply(fdb, 550, FALSE, "Command syntax error"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+               }
+           } else {
+               if (!reply(fdb, 502, FALSE, "Permission denied"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else if (!(mm_stricmp(args[1], "CHMOD"))) {
+           if (clenv->modify && clenv->mumask) {
+               if (i == 4) {
+                   if (valid_path(path, NULL, clenv->home, clenv->cwd,
+                               args[3], FALSE, FALSE)) {
+                       if ((i = exists(path, NULL, NULL, clenv->checkowner))
+                               != MMPATH_NONE && i != MMPATH_DENIED) {
+                           sscanf(args[2], "%o", &mode);
+                           if (mode > 07000) mode = 0;
+                           mode &= ~07000;
+                           if (i == MMPATH_FILE) {
+                               /* Normal file, force 6 for user/owner */
+                               mode &= ~0700;
+                               mode |= 0600;
+                           } else {
+                               /* Directory, force 7 for user/owner */
+                               mode |= 0700;
+                           }
+                           /* On linux symbolic links don't support
+                            * permissions
+                            */
+#ifdef __GLIBC__
+                           if ((chmod(path, mode)) == -1)
+                               mmsyslog(0, LOGLEVEL,
+                                       "* main_site() - chmod(%s)",
+                                       path);
+#else
+                           if ((lchmod(path, mode)) == -1)
+                               mmsyslog(0, LOGLEVEL,
+                                       "* main_site() - lchmod(%s)",
+                                       path);
+#endif
+                           if (!reply(fdb, 200, FALSE,
+                                       "CHMOD %04o command successful",
+                                       mode))
+                               nextstate = STATE_ERROR;
+                       } else {
+                           if (!reply(fdb, 550, FALSE,
+                                       "No such file or directory"))
+                               nextstate = STATE_ERROR;
+                           clenv->errors++;
+                       }
+                   } else {
+                       if (!reply(fdb, 550, FALSE,
+                                   "No such file or directory"))
+                           nextstate = STATE_ERROR;
+                       clenv->errors++;
+                   }
+               } else {
+                   if (!reply(fdb, 550, FALSE, "Command syntax error"))
+                       nextstate = STATE_ERROR;
+                   clenv->errors++;
+               }
+           } else {
+               if (!reply(fdb, 502, FALSE, "Permission denied"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 500, FALSE, "Unknown command"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 500, FALSE, "Unknown command"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_size(clientenv *clenv)
+{
+    int nextstate = STATE_CURRENT;
+    fdbuf *fdb = clenv->fdb;
+    char path[MMPATH_MAX];
+    register int t;
+    long size;
+
+    if (valid_path(path, NULL, clenv->home, clenv->cwd, &clenv->buffer[4],
+               FALSE, FALSE)) {
+       if ((t = exists(path, &size, NULL, clenv->checkowner)) != MMPATH_NONE
+               && t != MMPATH_DENIED) {
+           if (t == MMPATH_FILE) {
+               if (!reply(fdb, 213, FALSE, "%ld", size))
+                   nextstate = STATE_ERROR;
+           } else {
+               if (!reply(fdb, 550, FALSE, "Not a regular file"))
+                   nextstate = STATE_ERROR;
+               clenv->errors++;
+           }
+       } else {
+           if (!reply(fdb, 550, FALSE, "No such file or directory"))
+               nextstate = STATE_ERROR;
+           clenv->errors++;
+       }
+    } else {
+       if (!reply(fdb, 550, FALSE, "No such file or directory"))
+           nextstate = STATE_ERROR;
+       clenv->errors++;
+    }
+
+    return (nextstate);
+}
+
+
+static int
+main_stat(clientenv *clenv)
+{
+    fdbuf *fdb = clenv->fdb;
+    char path[MMPATH_MAX], cpath[MMPATH_MAX];
+    transfermsg *msg = &(clenv->tmsg);
+
+    if (clenv->buffer[4]) {
+       /* A pathname was specified, we thus act like LIST but on control port
+        */
+       if (valid_path(path, cpath, clenv->home, clenv->cwd,
+                   &clenv->buffer[4], TRUE, FALSE)) {
+           if (!reply(fdb, 211, TRUE, "Status of '%s':", cpath))
+               return (STATE_ERROR);
+           if (!ls(fdb, path, clenv->user, clenv->group, FALSE, TRUE,
+                       clenv->checkowner))
+               mmsyslog(0, LOGLEVEL, "* main_stat() - ls(%s)", path);
+           if (!reply(fdb, 211, FALSE, "End of status information"))
+               return (STATE_ERROR);
+       } else {
+           if (!reply(fdb, 214, FALSE, "Invalid path"))
+               return (STATE_ERROR);
+           clenv->errors++;
+       }
+    } else {
+       /* Normal ftpd status report */
+       if (!reply(fdb, 211, TRUE, "FTP server status:"))
+           return (STATE_ERROR);
+       if (!fdbprintf(fdb, "    Version: %s (%s)\r\n", DAEMON_NAME,
+                   DAEMON_VERSION))
+           return (STATE_ERROR);
+       if (clenv->c_hostname) {
+           if (!fdbprintf(fdb, "    Connected to %s (%s)\r\n",
+                       clenv->c_hostname, clenv->c_ipaddr))
+               return (STATE_ERROR);
+       } else {
+           if (!fdbprintf(fdb, "    Connected to %s\r\n", clenv->c_ipaddr))
+               return (STATE_ERROR);
+       }
+       if (!fdbprintf(fdb, "    Logged in as %s\r\n", clenv->user))
+           return (STATE_ERROR);
+       if (!fdbputs(fdb, "    TYPE: "))
+           return (STATE_ERROR);
+       if (clenv->type == TYPE_ASCII) {
+           if (!fdbputs(fdb, "ASCII, FORM: Nonprint"))
+               return (STATE_ERROR);
+       } else if (clenv->type == TYPE_IMAGE) {
+           if (!fdbputs(fdb, "Image"))
+               return (STATE_ERROR);
+       } else if (clenv->type == TYPE_LOCAL) {
+           if (!fdbputs(fdb, "Local 8"))
+               return (STATE_ERROR);
+       } else {
+           if (!fdbputs(fdb, "*UNKNOWN*"))
+               return (STATE_ERROR);
+       }
+       if (!fdbputs(fdb, "; STRUcture: File; transfer MODE: Stream\r\n"))
+           return (STATE_ERROR);
+       if (clenv->anonymous) {
+           if (!fdbputs(fdb, "    Class: guest: type: GUEST\r\n"))
+               return (STATE_ERROR);
+       } else {
+           if (!fdbputs(fdb, "    Class: real, type: REAL\r\n"))
+               return (STATE_ERROR);
+       }
+       if (!fdbputs(fdb, "    Check PORT/LPRT/EPRT commands: enabled\r\n"))
+           return (STATE_ERROR);
+       if (!fdbprintf(fdb,
+                   "    Idle timeout: %d, maximum timeout: disabled\r\n",
+                   clenv->timeout / 1000))
+           return (STATE_ERROR);
+       if (!fdbputs(fdb, "    Maximum file size: unlimited\r\n"))
+           return (STATE_ERROR);
+       if (!fdbputs(fdb, "    MotD file: motd\r\n"))
+           return (STATE_ERROR);
+       if (!fdbputs(fdb,
+                   "    Modify commands (APPE, CHMOD, DELE, MKD, RMD, RNFR, \
+RNTO, UMASK): "))
+           return (STATE_ERROR);
+       if (clenv->modify) {
+           if (!fdbputs(fdb, "enabled"))
+               return (STATE_ERROR);
+       } else {
+           if (!fdbputs(fdb, "disabled"))
+               return (STATE_ERROR);
+       }
+       if (!fdbputs(fdb, "\r\n    Modify umask (UMASK): "))
+           return (STATE_ERROR);
+       if (clenv->mumask) {
+           if (!fdbputs(fdb, "enabled"))
+               return (STATE_ERROR);
+       } else {
+           if (!fdbputs(fdb, "disabled"))
+               return (STATE_ERROR);
+       }
+       if (!fdbputs(fdb, "\r\n    Upload commands (APPE, STOR, STOU): "))
+           return (STATE_ERROR);
+       if (clenv->upload) {
+           if (!fdbputs(fdb, "enabled"))
+               return (STATE_ERROR);
+       } else {
+           if (!fdbputs(fdb, "disabled"))
+               return (STATE_ERROR);
+       }
+       if (!fdbputs(fdb, "\r\n    Sanitize file names: enabled\r\n"))
+           return (STATE_ERROR);
+       if (!fdbputs(fdb, "    PASV/LPSV/EPSV connections: enabled\r\n"))
+           return (STATE_ERROR);
+       if (!fdbputs(fdb, "    Home directory size limit: "))
+           return (STATE_ERROR);
+       if (clenv->maxhomesize) {
+           off_t size;
+
+           if (!fdbprintf(fdb, "%lld bytes", clenv->maxhomesize))
+               return (STATE_ERROR);
+           if (!fdbputs(fdb, "\r\n    Current home directory size: "))
+               return (STATE_ERROR);
+           pth_mutex_acquire(&clenv->unode->lock, FALSE, NULL);
+           size = clenv->unode->homesize;
+           pth_mutex_release(&clenv->unode->lock);
+           if (!fdbprintf(fdb, "%lld bytes", size))
+               return (STATE_ERROR);
+       } else {
+           if (!fdbputs(fdb, "disabled"))
+               return (STATE_ERROR);
+       }
+       if (!fdbputs(fdb, "\r\n    Control rate get limit: "))
+           return (STATE_ERROR);
+       if (CONF.BANDWIDTH_OUT) {
+           if (!fdbprintf(fdb, "%ldKB/sec", CONF.BANDWIDTH_OUT))
+               return (STATE_ERROR);
+       } else {
+           if (!fdbputs(fdb, "disabled"))
+               return (STATE_ERROR);
+       }
+       if (!fdbputs(fdb, "\r\n    Control rate put limit: "))
+           return (STATE_ERROR);
+       if (CONF.BANDWIDTH_IN) {
+           if (!fdbprintf(fdb, "%ldKB/sec", CONF.BANDWIDTH_IN))
+               return (STATE_ERROR);
+       } else {
+           if (!fdbputs(fdb, "disabled"))
+               return (STATE_ERROR);
+       }
+       if (!fdbputs(fdb, "\r\n    Data    rate get limit: "))
+           return (STATE_ERROR);
+       if (clenv->bw_out) {
+           if (!fdbprintf(fdb, "%ldKB/sec", clenv->bw_out))
+               return (STATE_ERROR);
+       } else {
+           if (!fdbputs(fdb, "disabled"))
+               return (STATE_ERROR);
+       }
+       if (!fdbputs(fdb, "\r\n    Data    rate put limit: "))
+           return (STATE_ERROR);
+       if (clenv->bw_in) {
+           if (!fdbprintf(fdb, "%ldKB/sec", clenv->bw_in))
+               return (STATE_ERROR);
+       } else {
+           if (!fdbputs(fdb, "disabled"))
+               return (STATE_ERROR);
+       }
+       if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+           if (!fdbprintf(fdb,
+                       "\r\n    Bytes transfered: (control in: %lld out: \
+%lld),\r\n                      (data in: %lld out: %lld),\
+\r\n                      (files in: %lld, out: %lld)",
+                       fdbbytesr(fdb), fdbbytesw(fdb), msg->rbytes,
+                       msg->wbytes, msg->ulfiles, msg->dlfiles))
+               return (STATE_ERROR);
+       } else
+           return (STATE_ERROR);
+       if (!fdbprintf(fdb, "\r\n    Umask: %04o\r\n", clenv->umask))
+           return (STATE_ERROR);
+       if (!reply(fdb, 211, FALSE, "End of status information"))
+           return (STATE_ERROR);
+    }
+
+    return (STATE_CURRENT);
+}
+
+
+
+
+/* MAIN */
+int
+main(int argc, char **argv)
+{
+    char *conf_file = "/etc/mmftpd.conf";
+    pid_t uid;
+    gid_t *gids;
+    int ngids, ret = 0;
+    long facility;
+    CRES cres;
+    CARG *cargp;
+    CARG cargs[] = {
+       {CAT_STR, 1, 255, CAS_UNTOUCHED, "CHROOT_DIR", CONF.CHROOT_DIR},
+       {CAT_STR, 1, 255, CAS_UNTOUCHED, "PASSWD_FILE", CONF.PASSWD_FILE},
+       {CAT_STR, 1, 255, CAS_UNTOUCHED, "PID_PATH", CONF.PID_PATH},
+       {CAT_STR, 1, 31, CAS_UNTOUCHED, "USER", CONF.USER},
+       {CAT_STR, 1, 255, CAS_UNTOUCHED, "GROUPS", CONF.GROUPS},
+       {CAT_STR, 1, 31, CAS_UNTOUCHED, "LOG_FACILITY", CONF.LOG_FACILITY},
+       {CAT_STR, 1, 1023, CAS_UNTOUCHED, "SERVER_NAMES",
+           CONF.SERVER_NAMES},
+       {CAT_STR, 1, 1023, CAS_UNTOUCHED, "LISTEN_IPS", CONF.LISTEN_IPS},
+       {CAT_STR, 1, 255, CAS_UNTOUCHED, "WELCOME_FILE",
+           CONF.WELCOME_FILE},
+       {CAT_STR, 1, 255, CAS_UNTOUCHED, "MOTD_FILE", CONF.MOTD_FILE},
+       {CAT_STR, 1, 63, CAS_UNTOUCHED, "DISPLAY_FILE", CONF.DISPLAY_FILE},
+       {CAT_VAL, 1, 32, CAS_UNTOUCHED, "ASYNC_PROCESSES",
+           &CONF.ASYNC_PROCESSES},
+       {CAT_VAL, 1, 9999, CAS_UNTOUCHED, "ALLOC_BUFFERS",
+           &CONF.ALLOC_BUFFERS},
+       {CAT_VAL, 0, 4, CAS_UNTOUCHED, "LOG_LEVEL", &CONF.LOG_LEVEL},
+       {CAT_VAL, 1, 65535, CAS_UNTOUCHED, "LISTEN_PORT",
+           &CONF.LISTEN_PORT},
+       {CAT_VAL, 1, 1000, CAS_UNTOUCHED, "MAX_ERRORS", &CONF.MAX_ERRORS},
+       {CAT_VAL, 1, 99999, CAS_UNTOUCHED, "MAX_IPS", &CONF.MAX_IPS},
+       {CAT_VAL, 1, 99999, CAS_UNTOUCHED, "MAX_PER_IP", &CONF.MAX_PER_IP},
+       {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "CONNECTION_RATE",
+           &CONF.CONNECTION_RATE},
+       {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "CONNECTION_PERIOD",
+           &CONF.CONNECTION_PERIOD},
+       {CAT_VAL, 1, 99999, CAS_UNTOUCHED, "CONTROL_TIMEOUT",
+           &CONF.CONTROL_TIMEOUT},
+       {CAT_VAL, 1, 99999, CAS_UNTOUCHED, "DATA_TIMEOUT",
+           &CONF.DATA_TIMEOUT},
+       {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "BANDWIDTH_IN",
+           &CONF.BANDWIDTH_IN},
+       {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "BANDWIDTH_OUT",
+           &CONF.BANDWIDTH_OUT},
+       {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "GBANDWIDTH_IN",
+           &CONF.GBANDWIDTH_IN},
+       {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "GBANDWIDTH_OUT",
+           &CONF.GBANDWIDTH_OUT},
+       {CAT_VAL, 4, 256, CAS_UNTOUCHED, "REMEMBER_CWDS",
+           &CONF.REMEMBER_CWDS},
+       {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "RESOLVE_HOSTS",
+           &CONF.RESOLVE_HOSTS},
+       {CAT_END, 0, 0, 0, NULL, NULL}
+    };
+    CMAP cmap[] = {
+       {"LOG_AUTH", LOG_AUTH},
+       {"LOG_AUTHPRIV", LOG_AUTHPRIV},
+       {"LOG_CRON", LOG_CRON},
+       {"LOG_DAEMON", LOG_DAEMON},
+       {"LOG_FTP", LOG_FTP},
+       {"LOG_LPR", LOG_LPR},
+       {"LOG_MAIL", LOG_MAIL},
+       {"LOG_NEWS", LOG_NEWS},
+       {"LOG_SYSLOG", LOG_SYSLOG},
+       {"LOG_USER", LOG_USER},
+       {"LOG_UUCP", LOG_UUCP},
+       {NULL, 0}
+    };
+    struct async_func afuncs[] = {
+       {async_hashpw, sizeof(struct async_hashpw_msg)},
+       {async_treesize, sizeof(struct async_treesize_msg)},
+       {NULL, 0}
+    };
+
+    /* Advertize */
+    printf("\r\n+++ %s (%s)\r\n\r\n", DAEMON_NAME, DAEMON_VERSION);
+
+    /* Set defaults */
+    *CONF.CHROOT_DIR = 0;
+    mm_strcpy(CONF.PASSWD_FILE, "/etc/mmftpdpasswd");
+    mm_strcpy(CONF.PID_PATH, "/var/run/mmftpd.pid");
+    mm_strcpy(CONF.USER, "mmftpd");
+    mm_strcpy(CONF.GROUPS, "mmftpd mmstat");
+    mm_strcpy(CONF.LOG_FACILITY, "LOG_AUTHPRIV");
+    mm_strcpy(CONF.SERVER_NAMES, "ftp.localhost");
+    mm_strcpy(CONF.LISTEN_IPS, "127.0.0.1");
+    mm_strcpy(CONF.DISPLAY_FILE, "README");
+    *CONF.WELCOME_FILE = 0;
+    *CONF.MOTD_FILE = 0;
+    CONF.ASYNC_PROCESSES = 3;
+    CONF.ALLOC_BUFFERS = 1;
+    CONF.LOG_LEVEL = 3;
+    CONF.LISTEN_PORT = 21;
+    CONF.MAX_ERRORS = 16;
+    CONF.MAX_IPS = 64;
+    CONF.MAX_PER_IP = 1;
+    CONF.CONNECTION_RATE = 10;
+    CONF.CONNECTION_PERIOD = 30;
+    CONF.CONTROL_TIMEOUT = 300;
+    CONF.DATA_TIMEOUT = 300;
+    CONF.BANDWIDTH_IN = 1;
+    CONF.BANDWIDTH_OUT = 1;
+    CONF.GBANDWIDTH_IN = 0;
+    CONF.GBANDWIDTH_OUT = 0;
+    CONF.REMEMBER_CWDS = 16;
+    CONF.RESOLVE_HOSTS = FALSE;
+
+    /* Read config file */
+    if (argc == 2)
+       conf_file = argv[1];
+    if (!mmreadcfg(conf_file, cargs, &cres)) {
+       /* Error parsing configuration file, report which */
+       printf("\nError parsing '%s'\n", conf_file);
+       printf("Error  : %s\n", mmreadcfg_strerr(cres.CR_Err));
+       if (*(cres.CR_Data)) printf("Data   : %s\n", cres.CR_Data);
+       if (cres.CR_Number != -1) {
+           cargp = &cargs[cres.CR_Number];
+           printf("Keyword: %s\n", cargp->CA_KW);
+           printf("Minimum: %ld\n", cargp->CA_Min);
+           printf("Maximum: %ld\n", cargp->CA_Max);
+       }
+       printf("\n");
+       exit(-1);
+    }
+
+    /* Post parsing */
+    if (!mmmapstring(cmap, CONF.LOG_FACILITY, &facility)) {
+       printf("\nUnknown syslog facility %s\n\n", CONF.LOG_FACILITY);
+       exit(-1);
+    }
+    LOGLEVEL = CONF.LOG_LEVEL;
+    if ((uid = mmgetuid(CONF.USER)) == -1) {
+       printf("\nUnknown user '%s'\n\n", CONF.USER);
+       exit(-1);
+    }
+    if (!(gids = mmgetgidarray(&ngids, CONF.GROUPS))) {
+       printf("\nOne of following groups unknown: '%s'\n\n", CONF.GROUPS);
+       exit(-1);
+    }
+
+    /* Finally init everything */
+    openlog(DAEMON_NAME, LOG_PID | LOG_NDELAY, facility);
+
+#ifndef NODROPPRIVS
+    if ((getuid())) {
+       printf("\nOnly the super user may start this daemon\n\n");
+       mmsyslog(0, LOGLEVEL, "* Only superuser can start me");
+       exit(-1);
+    }
+#else /* NODROPPRIVS */
+    if ((getuid()) == 0) {
+       printf("\nCompiled with NODROPPRIVS, refusing to run as uid 0\n\n");
+       mmsyslog(0, LOGLEVEL, "* NODROPPRIVS, refusing to run as uid 0");
+       exit(-1);
+    }
+#endif /* !NODROPPRIVS */
+
+    mmstat_initialize(); /* Required in case we chroot(2) */
+
+    make_daemon(CONF.PID_PATH, CONF.CHROOT_DIR);
+    async_init(afuncs, CONF.ASYNC_PROCESSES, uid, gids, ngids);
+    packcommands(commands, 3);
+
+    pth_init();
+    async_init_pth();
+    pth_mutex_init(&portnum_lock);
+    pth_mutex_init(&lusers_lock);
+    pth_mutex_init(&clenvs_lock);
+    portnum = 0;
+
+    /* We use those for our transfer ASYNC thread, joinable because when we
+     * eventually request it to quit via a message we want to make sure it
+     * ended properly waiting for it with pth_join(). I have decided to use
+     * a second thread dedicated to file transfers, this way I can respect
+     * the RFC on listening to ABOR and STAT easily on the control connection
+     * while transfers are ongoing, with ease and modularity.
+     */
+    tthreadattr = pth_attr_new();
+    pth_attr_set(tthreadattr, PTH_ATTR_JOINABLE, TRUE);
+
+    /* Initialize our pools */
+    /* Client environment nodes */
+    clenvs = openlist(malloc, free, sizeof(clientenv),
+           65535 * CONF.ALLOC_BUFFERS, 0);
+    /* User-shared state nodes */
+    lusers = openlist(malloc, free, sizeof(lusernode),
+           32768 * CONF.ALLOC_BUFFERS, 0);
+
+    if (clenvs && lusers) {
+       fdbcinit(&fdbc, CONF.GBANDWIDTH_IN * 1024,
+               CONF.GBANDWIDTH_OUT * 1024);
+       /* Finally start our TCP main daemon */
+       tcp_server("421 Server too busy, try again\r\n", CONF.SERVER_NAMES,
+               CONF.LISTEN_IPS, uid, gids, ngids, CONF.MAX_IPS,
+               CONF.MAX_PER_IP, CONF.CONNECTION_RATE, CONF.CONNECTION_PERIOD,
+               CONF.CONTROL_TIMEOUT, CONF.LISTEN_PORT, CONF.RESOLVE_HOSTS,
+               handleclient);
+       fdbcdestroy(&fdbc);
+    } else {
+       mmsyslog(0, LOGLEVEL, "* Out of memory");
+       ret = -1;
+    }
+
+    mmfreegidarray(gids);
+    if (lusers) closelist(lusers);
+    if (clenvs) closelist(clenvs);
+
+    exit(ret);
+}
+
+
+/* Used to initialize command table hashes */
+static void
+packcommands(struct command *cmd, size_t min)
+{
+    while (cmd->name) {
+       cmd->hash = mm_strpack32(cmd->name, min);
+       cmd++;
+    }
+}
+
+
+/* Function used to return standard RFC result strings to the client,
+ * and returns FALSE on error
+ */
+static bool
+reply(fdbuf *fdb, int code, bool cont, char *fmt, ...)
+{
+    char buf[1024];
+    va_list arg_ptr;
+    bool ok = TRUE;
+
+    if (cont) snprintf(buf, 6, "%03d-", code);
+    else snprintf(buf, 6, "%03d ", code);
+
+    if (fmt) {
+       va_start(arg_ptr, fmt);
+       vsnprintf(&buf[4], 1018, fmt, arg_ptr);
+       va_end(arg_ptr);
+       if (!fdbprintf(fdb, "%s\r\n", buf)) ok = FALSE;
+       mmsyslog(3, LOGLEVEL, "> %s", buf);
+    } else if (!fdbprintf(fdb, "%s\r\n", buf))
+       ok = FALSE;
+
+    return (ok);
+}
+
+
+/* This function reads a valid (non-blank, non-comment) line from fdb,
+ * and parses the columns of that line, filling array with the pointers
+ * to each trim string. It returns 0 if the number of columns of that line
+ * is out of mincols,maxcols bounds, the number of columns of the line on
+ * success, or -1 on EOF. str string provided should be at least 256 bytes
+ * long.
+ */
+static int
+readconfline(char *str, fdbuf *fdb, int mincols, int maxcols, char **array)
+{
+    char *ptr;
+    int len, col;
+
+    while ((len = fdbgets(fdb, str, 255, FALSE)) > -1) {
+       if (len) {
+           /* Comment or empty line? */
+           ptr = str;
+           while (*ptr && (*ptr == ' ' || *ptr == '\t')) ptr++;
+           if (!*ptr || *ptr == '#' || *ptr == ';') continue;
+
+           col = mm_straspl(array, str, maxcols);
+
+           if ((col <= maxcols) && (col >= mincols)) return (col);
+
+           return (0);
+       }
+    }
+
+    return (-1);
+}
+
+
+/* Given a command string, it replaces by spaces any - parameters as some
+ * broken clients tend to specify some to FTP commands, notably with LIST.
+ * Note that we only strip options specified before anything else, to permit
+ * filenames using space followed by -. Of course this function should be
+ * called on a string starting with optional parameters, after the command.
+ */
+static void
+stripoptions(char *str)
+{
+    bool space;
+
+    /* First locate end of command */
+    while (*str && *str != ' ')
+       str++;
+
+    if (*str) {
+       space = FALSE;
+       while (*str) {
+           /* Find first printable char */
+           while (*str && (*str == ' ' || *str == '\t')) {
+               space = TRUE;
+               str++;
+           }
+           /* Is it an option? */
+           if (space && *str == '-') {
+               /* Option found, replace chars by spaces until not a printable
+                * char
+                */
+               space = FALSE;
+               while (*str && *str != ' ' && *str != '\t') *str++ = ' ';
+           } else break;
+       }
+    }
+}
+
+
+/* This function is given an absolute path to a directory which the user
+ * has access to, and calculates the size of it's whole contents in bytes.
+ * Returns size of total contents, or -1 on error.
+ * NOTE: alloca() allocates using the stack, contrary to malloc(). Any stack
+ * the function uses is automatically freed when function ends, freeing it
+ * would be a mistake.
+ * XXX This function can take alot of stack space, especially that it
+ * allocates strings on the stack as well. I could provide a safer function
+ * in this respect if it was proven necessary. This currently has the
+ * adventage of being quite fast.
+ */
+static off_t
+recursive_treesize(char *path, off_t minfilesize)
+{
+    off_t size = 0, tmp;
+    DIR *dh;
+    struct dirent *de;
+    struct stat st;
+    int len;
+    char *newpath;
+
+    len = mm_strlen(path);
+    len--;
+    if (len && path[len] == '/') path[len] = '\0';
+
+    if ((dh = opendir(path))) {
+
+       while ((de = readdir(dh))) {
+
+           if ((mm_strcmp(de->d_name, "."))
+                   && (mm_strcmp(de->d_name, ".."))) {
+               len = mm_strlen(path) + mm_strlen(de->d_name) + 2;
+               if ((newpath = (char *)alloca(len + 2))) {
+                   snprintf(newpath, len, "%s/%s", path, de->d_name);
+                   if (!(lstat(newpath, &st))) {
+                       if (st.st_size < minfilesize) size += minfilesize;
+                       else size += st.st_size;
+
+                       if (S_ISDIR(st.st_mode)) {
+                           if ((tmp = recursive_treesize(newpath,
+                                           minfilesize)) != -1)
+                               size += tmp;
+                           else {
+                               closedir(dh);
+                               return (-1);
+                           }
+                       }
+
+                   }
+               } else {
+                   closedir(dh);
+                   return (-1);
+               }
+           }
+
+       }
+
+       closedir(dh);
+    } else
+       return (-1);
+
+    return (size);
+}
+
+
+/* This function permits easy editing and checking around the current user's
+ * home directory. An optional modifyer is provided (negative or positive
+ * number), which is applied to the current limit if permitted. TRUE is
+ * returned on success or FALSE if limits would be exceeded.
+ */
+static bool
+treesize_edit(clientenv *clenv, off_t modify)
+{
+    off_t cur;
+    bool ret = TRUE;
+
+    if (clenv->maxhomesize) {
+       pth_mutex_acquire(&clenv->unode->lock, FALSE, NULL);
+       cur = clenv->unode->homesize;
+       cur += modify;
+       if (cur < 0) cur = 0;
+       if (cur <= clenv->maxhomesize) clenv->unode->homesize = cur;
+       else ret = FALSE;
+       pth_mutex_release(&clenv->unode->lock);
+    }
+
+    return (ret);
+}
+
+
+/* Check if a user is in out passwd file, if so fill in clenv.
+ * Also sets the current directory to /
+ */
+static bool
+checkuser(char *name, clientenv *clenv)
+{
+    bool found = FALSE;
+    int fd, cols;
+    char line[256], *columns[16];
+    fdbuf *fdb;
+    fdfuncs fdf = {
+       malloc,
+       free,
+       pth_poll,
+       pth_read,
+       pth_write,
+       pth_sleep,
+       pth_usleep
+    };
+
+    if (clenv->user) clenv->user = mmstrfree(clenv->user);
+    if (clenv->passwd) clenv->passwd = mmstrfree(clenv->passwd);
+    if (clenv->home) clenv->home = mmstrfree(clenv->home);
+    if (clenv->group) clenv->group = mmstrfree(clenv->group);
+
+    if ((fd = open(CONF.PASSWD_FILE, O_RDONLY)) != -1) {
+       if ((fdb = fdbopen(&fdf, NULL, fd, FDB_BUFSIZE, 0, 0, 0, -1, -1))
+               != NULL) {
+
+           while ((cols = readconfline(line, fdb, 15, 15, columns)) != -1) {
+               if (cols == 15) {
+                   if (!(mm_strcmp(columns[CONF_USER], name))) {
+
+                       if ((clenv->user = mmstrdup(columns[CONF_USER]))) {
+                           if ((clenv->passwd =
+                                       mmstrdup(columns[CONF_PASS]))) {
+                               if ((clenv->home = mmstrdup(
+                                               columns[CONF_HOME]))) {
+                                   if ((clenv->group = mmstrdup(
+                                                   columns[CONF_GROUP]))) {
+                                       if ((clenv->gid = mmgetgid(
+                                                       columns[CONF_GROUP]))
+                                               != -1) {
+                                           mm_strcpy(clenv->cwd, "/");
+                                           clenv->stats = (atoi(
+                                                       columns[CONF_STATS]));
+                                           clenv->checkowner = (atoi(
+                                                       columns[CONF_CHKOWN]));
+                                           clenv->upload = (atoi(
+                                                       columns[CONF_UPLOAD]));
+                                           clenv->modify = (atoi(
+                                                       columns[CONF_MODIFY]));
+                                           clenv->mumask = (atoi(
+                                                       columns[CONF_MUMASK]));
+                                           sscanf(columns[CONF_UMASK],
+                                                   "%o", &clenv->umask);
+                                           if (clenv->umask > 07000)
+                                               clenv->umask = 0;
+                                           clenv->umask &= ~0700;
+                                           clenv->umask |= 07000;
+                                           clenv->maxlogins = atol(
+                                                   columns[CONF_MAXLOGINS]);
+                                           clenv->maxhomesize = atol(
+                                                   columns[CONF_MAXHOMESIZE])
+                                               * 1024;
+                                           clenv->minfilesize = atol(
+                                                   columns[CONF_MINFILESIZE]);
+                                           clenv->bw_in = atol(
+                                                   columns[CONF_BW_IN]);
+                                           clenv->bw_out = atol(
+                                                   columns[CONF_BW_OUT]);
+                                           found = TRUE;
+                                           break;
+                                       } else
+                                           mmsyslog(0, LOGLEVEL,
+                                                   "* checkuser() - Unknown \
+group %s", columns[CONF_GROUP]);
+                                       clenv->group = mmstrfree(
+                                               clenv->group);
+                                   }
+                                   clenv->home = mmstrfree(clenv->home);
+                               }
+                               clenv->passwd = mmstrfree(clenv->passwd);
+                           }
+                           clenv->user = mmstrfree(clenv->user);
+                       }
+                       mmsyslog(0, LOGLEVEL, "* checkuser() - strdup()");
+
+                   }
+               } else {
+                   mmsyslog(0, LOGLEVEL,
+                           "* checkuser() - Wrongly formatted '%s' file!",
+                           CONF.PASSWD_FILE);
+                   break;
+               }
+           }
+
+           fdbclose(fdb);
+       } else
+           mmsyslog(0, LOGLEVEL, "* checkuser() - Out of memory");
+       close(fd);
+    } else
+       mmsyslog(0, LOGLEVEL,
+               "* checkuser() - Unable to open '%s'!", CONF.PASSWD_FILE);
+
+    return (found);
+}
+
+
+/* Allocate and prepare a clenv. Returns NULL on error */
+static clientenv *
+alloc_clientenv(void)
+{
+    clientenv *clenv;
+
+    pth_mutex_acquire(&clenvs_lock, FALSE, NULL);
+    clenv = (clientenv *)allocnode(clenvs, TRUE);
+    pth_mutex_release(&clenvs_lock);
+
+    if (clenv) {
+       if (!*CONF.DISPLAY_FILE || (clenv->visited = openfifo64(malloc,
+                       free, CONF.REMEMBER_CWDS))) {
+           mmstat_init(&clenv->vstat, FALSE);
+           mmstat_init(&clenv->pstat, TRUE);
+           return (clenv);
+       }
+       free_clientenv(clenv);
+    }
+
+    return (NULL);
+}
+
+
+/* Frees all resources allocated by a clenv */
+static clientenv *
+free_clientenv(clientenv *clenv)
+{
+    if (clenv->visited) closefifo64(clenv->visited);
+    if (clenv->user) mmstrfree(clenv->user);
+    if (clenv->tuser) mmstrfree(clenv->tuser);
+    if (clenv->passwd) mmstrfree(clenv->passwd);
+    if (clenv->home) mmstrfree(clenv->home);
+    if (clenv->group) mmstrfree(clenv->group);
+    if (clenv->rnfr) mmstrfree(clenv->rnfr);
+    if (clenv->unode) {
+       pth_mutex_acquire(&lusers_lock, FALSE, NULL);
+       clenv->unode->logins--;
+       if (clenv->unode->logins < 1) {
+           unlinknode(lusers, (node *)clenv->unode);
+           /* May be required by some POSIX thread implementations */
+           /* pth_mutex_destroy(&clenv->unode->lock); */
+           freenode((node *)clenv->unode);
+       }
+       pth_mutex_release(&lusers_lock);
+    }
+
+    pth_mutex_acquire(&clenvs_lock, FALSE, NULL);
+    freenode((node *)clenv);
+    pth_mutex_release(&clenvs_lock);
+
+    return (NULL);
+}
+
+
+/* Starts the transfer thread of the client thread */
+static bool
+start_transfer_thread(clientenv *clenv)
+{
+    /* I do not understand why libpth doesn't provide a function to set the
+     * reply port, or to init the message structure. Moreover, it theoretically
+     * shouldn't need the message size if it simply swaps messages around port
+     * linked list FIFOs via the message node. This currently seems the best
+     * way to go about it
+     */
+    clenv->tmsg.msg.m_size = sizeof(transfermsg);
+    clenv->tmsg.msg.m_replyport = clenv->rport;
+
+    if ((clenv->tthread = pth_spawn(tthreadattr, (void *)transferthread,
+                   clenv))) {
+       if (transfer_request(REQ_NONE, TRUE, clenv)) return (TRUE);
+       pth_join(clenv->tthread, NULL);
+    }
+
+    return (FALSE);
+}
+
+
+/* Orders the transfer thread to quit, and waits until it exits */
+static void
+stop_transfer_thread(clientenv *clenv)
+{
+    if (clenv->sport) {
+       /* Send a quit request and wait for reply */
+       if (!transfer_request(REQ_QUIT, TRUE, clenv))
+           mmsyslog(0, LOGLEVEL,
+                   "* stop_transfer_thread() - transfer_request(REQ_QUIT)");
+       /* If the transfer thread refused to quit for whatever reason
+        * (unlikely) this could freeze the current thread indefinitely.
+        * Join thread as we want to make sure it exits before we do
+        */
+       pth_join(clenv->tthread, NULL);
+    }
+}
+
+
+/* Can be used to either send a request to our transfer thread,
+ * wait for results from it, or both. Note that this will wait for a maximum
+ * delay of 120 seconds for an incoming results reply message. This normally
+ * should never occur however.
+ */
+static bool
+transfer_request(int req, bool waitreply, clientenv *clenv)
+{
+    int res = FALSE;
+    transfermsg *msg = &(clenv->tmsg);
+    pth_event_t ring;
+
+    if (req != REQ_NONE) {
+       msg->request = req;
+       msg->result = FALSE;
+       pth_msgport_put(clenv->sport, (pth_message_t *)msg);
+    }
+    if (waitreply) {
+       ring = pth_event(PTH_EVENT_TIME, pth_timeout(120, 0));
+       pth_event_concat(clenv->rring, ring, NULL);
+       while (1) {
+           if ((pth_wait(clenv->rring))) {
+               if ((pth_event_occurred(ring))) {
+                   /* Timeout occured waiting for reply */
+                   res = FALSE;
+                   break;
+               }
+               if ((pth_event_occurred(clenv->rring))) {
+                   if ((msg = (transfermsg *)pth_msgport_get(clenv->rport))) {
+                       res = msg->result;
+                       break;
+                   }
+               }
+           }
+       }
+       pth_event_isolate(clenv->rring);
+       pth_event_free(ring, PTH_FREE_ALL);
+    } else
+       res = TRUE;
+
+    return (res);
+}
+
+
+/* Reads a text file and outputs it's contents to specified fdb */
+static bool
+file_out(fdbuf *fdb, char *filename)
+{
+    bool res = FALSE;
+    fdbuf *fh;
+    int fd, len;
+    char tline[256];
+    fdfuncs fdf = {
+       malloc,
+       free,
+       pth_poll,
+       pth_read,
+       pth_write,
+       pth_sleep,
+       pth_usleep
+    };
+
+    if ((fd = open(filename, O_RDONLY)) != -1) {
+       if ((fh = fdbopen(&fdf, NULL, fd, FDB_BUFSIZE, 0, 0, 0, -1, -1))
+               != NULL) {
+           res = TRUE;
+           while ((len = fdbgets(fh, tline, 255, FALSE)) > -1)
+               if (!fdbprintf(fdb, "    %s\r\n", tline)) break;
+           fdbclose(fh);
+       } else
+           mmsyslog(0, LOGLEVEL, "* file_out() - Out of memory");
+       close(fd);
+    }
+
+    return (res);
+}
+
+
+/* This function consists of an attempt to save bandwidth and not spam the
+ * user constantly by possible directory messages, if any. We maintain a FIFO
+ * buffer with a maximum of last CONF.REMEMBER_CWDS visited directory hashes.
+ */
+static bool
+directory_message(clientenv *clenv, int code)
+{
+    bool res = TRUE, found;
+    char path[MMPATH_MAX];
+    u_int64_t dhash, tmp;
+
+    if (*CONF.DISPLAY_FILE) {
+       if (valid_path(path, NULL, clenv->home, clenv->cwd,
+                   CONF.DISPLAY_FILE, FALSE, FALSE)
+               && exists(path, NULL, NULL, clenv->checkowner)
+               == MMPATH_FILE) {
+           /* Make sure that this directory was not visited recently. */
+           dhash = hashstr64(clenv->cwd);
+           found = FALSE;
+           {
+               /* Cache in registers pointers to data that need to be used
+                * every loop instance for faster lookup
+                */
+               register u_int64_t *h, *b, *e, *p, *t;
+               register int cnt;
+
+               h = &dhash;
+               b = clenv->visited->buffer;
+               e = clenv->visited->endbuffer;
+               p = clenv->visited->tail;
+               t = clenv->visited->head;
+               cnt = 0;
+               while (p != t) {
+                   if (*p == *h) {
+                       found = TRUE;
+                       break;
+                   }
+                   p++;
+                   if (p == e) p = b;
+                   cnt++;
+                   if (cnt > 64) {
+                       cnt = 0;
+                       pth_yield(NULL);
+                   }
+               }
+           }
+           if (!found) {
+               /* This directory was not visited recently, send out file */
+               if (!reply(clenv->fdb, code, TRUE, NULL))
+                   res = FALSE;
+               file_out(clenv->fdb, path);
+           }
+           /* Store current directory hash at end of FIFO, free an entry
+            * if required.
+            */
+           if (statfifo64(clenv->visited) == CONF.REMEMBER_CWDS)
+               getfifo64(&tmp, clenv->visited);
+           putfifo64(clenv->visited, dhash);
+       }
+    }
+
+    return (res);
+}
+
+
+/* This is the main function that is called to serve a client.
+ * It comports the main loop and state switcher. The system I use here should
+ * be quite fast, as only looping once though the commands list is required,
+ * because each state has it's jump table.
+ */
+static int
+handleclient(unsigned long cid, int fd, clientlistnode *clientlnode,
+       struct iface *iface, struct async_clenv *aclenv)
+{
+    char buffer[1024], ipaddr[20], sipaddr[20];
+    register char *tmp;
+    int state, nstate, timeout, dstatus;
+    clientenv *clenv = NULL;
+    struct sockaddr_in *sinaddr, sname;
+    socklen_t slen;
+    fdbuf *fdb;
+    transfermsg *msg;
+    unsigned long time_total, num;
+    int64_t control_in, control_out, data_in, data_out, dl_files, ul_files;
+    time_t time_start, time_end;
+    fdfuncs fdf = {
+       malloc,
+       free,
+       pth_poll,
+       pth_read,
+       pth_write,
+       pth_sleep,
+       pth_usleep
+    };
+
+    timeout = clientlnode->timeout;
+    control_in = control_out = data_in = data_out = dl_files = ul_files = 0;
+    dstatus = MMS_RESOURCE_ERROR;
+
+    if (res_init()) mmsyslog(0, LOGLEVEL, "* handleclient() - res_init()");
+
+    /* Obtain IP address of client */
+    sinaddr = (struct sockaddr_in *)&clientlnode->client;
+    mm_strcpy(ipaddr, "0.0.0.0");
+    inet_ntop(AF_INET, &(sinaddr->sin_addr), ipaddr, 19);
+
+    /* Obtain IP address of server */
+    slen = sizeof(struct sockaddr_in);
+    mm_strcpy(sipaddr, "0.0.0.0");
+    if (!(getsockname(fd, (struct sockaddr *)&sname, &slen))) {
+       inet_ntop(AF_INET, &(sname.sin_addr), sipaddr, 19);
+       tmp = sipaddr;
+       while (*tmp) {
+           if (*tmp == '.') *tmp = ',';
+           tmp++;
+       }
+    }
+
+    if (clientlnode->hostname)
+       /* Log user's IP and hostname */
+       mmsyslog(1, LOGLEVEL, "%08X Connect: [%s] - (%s)", cid, ipaddr,
+               clientlnode->hostname);
+    else
+       /* Log user's IP */
+       mmsyslog(1, LOGLEVEL, "%08X Connect: [%s]", cid, ipaddr);
+
+    time_start = time(NULL);
+
+    if ((fdb = fdbopen(&fdf, &fdbc, fd, FDB_BUFSIZE, FDB_BUFSIZE,
+                   CONF.BANDWIDTH_IN * 1024, CONF.BANDWIDTH_OUT * 1024,
+                   timeout, timeout))) {
+
+       /* Allocate our clientenv to share with state functions */
+       if ((clenv = alloc_clientenv())) {
+
+           /* Open our reply port and start our file transfer thread */
+           pth_mutex_acquire(&portnum_lock, FALSE, NULL);
+           num = portnum++;
+           pth_mutex_release(&portnum_lock);
+           snprintf(buffer, 15, "%lu", num);
+           if ((clenv->rport = pth_msgport_create(buffer))) {
+               clenv->rring = pth_event(PTH_EVENT_MSG, clenv->rport);
+
+               /* Init our clientenv as required */
+               clenv->fdb = fdb;
+               clenv->buffer = buffer;
+               clenv->type = TYPE_ASCII;
+               clenv->errors = 0;
+               clenv->timeout = timeout;
+               clenv->c_hostname = clientlnode->hostname;
+               clenv->c_ipaddr = ipaddr;
+               clenv->id = cid;
+               clenv->sipaddr = sipaddr;
+               clenv->uipaddr = sname.sin_addr.s_addr;
+               clenv->iface = iface;
+               clenv->aclenv = aclenv;
+
+               mmstat(&clenv->vstat, STAT_UPDATE, 1,
+                       "mmftpd.current.connections");
+               mmstat(&clenv->pstat, STAT_UPDATE, 1,
+                       "mmftpd.total.connections");
+
+               if (start_transfer_thread(clenv)) {
+
+                   msg = &(clenv->tmsg);
+
+                   if (*CONF.WELCOME_FILE) {
+                       reply(fdb, 220, TRUE, NULL);
+                       if (!file_out(fdb, CONF.WELCOME_FILE))
+                           mmsyslog(0, LOGLEVEL,
+                                   "Error opening WELCOME_FILE \"%s\"",
+                                   CONF.WELCOME_FILE);
+                   }
+
+                   reply(fdb, 220, FALSE, "%s FTP server (%s (%s)) ready",
+                           iface->hostname, DAEMON_NAME, DAEMON_VERSION);
+                   state = STATE_AUTH;
+                   dstatus = MMS_NORMAL;
+
+                   /* Main state switcher loop */
+                   while (TRUE) {
+                       register ssize_t len;
+                       register int i = 0;
+                       register int32_t chash;
+                       register bool valid;
+
+                       fdbflushw(fdb);
+                       if ((len = fdbgets(fdb, buffer, 1000, TRUE)) > -1) {
+
+                           /* If there were too many errors, exit */
+                           if (clenv->errors > CONF.MAX_ERRORS) {
+                               reply(fdb, 421, FALSE, "Too many errors");
+                               dstatus = MMS_MANY_ERRORS;
+                               break;
+                           }
+
+                           /* Verify if command matches one of our commands */
+                           valid = FALSE;
+                           if ((chash = mm_strpack32(buffer, 3)) != -1) {
+                               for (i = 0; commands[i].name; i++) {
+                                   if (commands[i].hash == chash) {
+                                       valid = TRUE;
+                                       break;
+                                   }
+                               }
+                           }
+
+                           if (valid) {
+                               register int (*func)(clientenv *);
+
+                               mmsyslog(commands[i].loglevel, LOGLEVEL,
+                                       "%08X %s", cid, buffer);
+
+                               /* First eliminate any - options after
+                                * command and before parameters, we should
+                                * ignore them all.
+                                */
+                               stripoptions(buffer);
+
+                               if ((func = states[state].functions[i])) {
+
+                                   /* Process command in current state */
+                                   nstate = func(clenv);
+                                   /* Check new state */
+                                   if (nstate == STATE_END
+                                           || nstate == STATE_ERROR)
+                                       break;
+                                   if (nstate != STATE_CURRENT)
+                                       state = nstate;
+
+                               } else {
+                                   /* Unimplemented command for this state */
+                                   clenv->errors++;
+                                   if (!reply(fdb, states[state].errcode,
+                                               FALSE, states[state].errtext))
+                                       break;
+                               }
+
+                           } else {
+                               mmsyslog(3, LOGLEVEL, "%08X %s", cid, buffer);
+                               clenv->errors++;
+                               if (!reply(fdb, 500, FALSE, "Unknown command"))
+                                   break;
+                           }
+
+                       } else {
+                           /* Input error */
+                           if (len == FDB_TIMEOUT) {
+                               /* Make sure no transfers are ongoing, only
+                                * timeout if none.
+                                */
+                               if (transfer_request(REQ_STATUS, TRUE,
+                                           clenv)) {
+                                   if (msg->ongoing) continue;
+                               } else
+                                   mmsyslog(0, LOGLEVEL,
+                                           "* handleclient() - \
+transfer_request()");
+                               dstatus = MMS_INPUT_TIMEOUT;
+                               break;
+                           } else if (len == FDB_ERROR) {
+                               dstatus = MMS_INPUT_ERROR;
+                               break;
+                           } else {
+                               dstatus = MMS_UNKNOWN;
+                               break;
+                           }
+                       }
+
+                   }
+
+                   /* Obtain status information from transfer thread and
+                    * kill it
+                    */
+                   if (transfer_request(REQ_STATUS, TRUE, clenv)) {
+                       data_in = msg->rbytes;
+                       data_out = msg->wbytes;
+                       dl_files = msg->dlfiles;
+                       ul_files = msg->ulfiles;
+                   }
+                   stop_transfer_thread(clenv);
+               } else
+                   mmsyslog(0, LOGLEVEL,
+                           "* handleclient() - Could not start transfer \
+thread");
+
+               pth_event_free(clenv->rring, PTH_FREE_ALL);
+               while ((pth_msgport_get(clenv->rport)));
+               pth_msgport_destroy(clenv->rport);
+           } else
+               mmsyslog(0, LOGLEVEL,
+                       "* handleclient() - Could not open reply port");
+
+           control_in = fdbbytesr(fdb);
+           control_out = fdbbytesw(fdb);
+
+           mmstat_transact(&clenv->vstat, TRUE);
+           if (clenv->login)
+               mmstat(&clenv->vstat, STAT_UPDATE, -1, "mmftpd.who.%s",
+                       clenv->user);
+           mmstat(&clenv->vstat, STAT_UPDATE, -1,
+                   "mmftpd.current.connections");
+           mmstat_transact(&clenv->vstat, FALSE);
+
+           mmstat_transact(&clenv->pstat, TRUE);
+           mmstat(&clenv->pstat, STAT_UPDATE, dl_files,
+                   "mmftpd.total.downloads");
+           mmstat(&clenv->pstat, STAT_UPDATE, ul_files,
+                   "mmftpd.total.uploads");
+           mmstat(&clenv->pstat, STAT_UPDATE, data_in,
+                   "mmftpd.total.data-in");
+           mmstat(&clenv->pstat, STAT_UPDATE, data_out,
+                   "mmftpd.total.data-out");
+           mmstat(&clenv->pstat, STAT_UPDATE, control_in,
+                   "mmftpd.total.control-in");
+           mmstat(&clenv->pstat, STAT_UPDATE, control_out,
+                   "mmftpd.total.control-out");
+           mmstat_transact(&clenv->pstat, FALSE);
+
+           /* Free our state-shared clenv */
+           free_clientenv(clenv);
+       } else
+           mmsyslog(0, LOGLEVEL,
+                   "* handleclient() - Could not allocate state-shared \
+clenv");
+
+       fdbclose(fdb);
+    } else
+       mmsyslog(0, LOGLEVEL, "* handleclient() - open_fdbuf()");
+
+    time_end = time(NULL);
+    time_total = time_end - time_start;
+    mmsyslog(1, LOGLEVEL,
+            "%08X Closed: [%s] - (Seconds: %ld, Control in: %lld out: %lld, \
+Data in: %lld out: %lld, Files in: %lld out: %lld, Status: %s)", cid, ipaddr,
+            time_total, control_in, control_out, data_in, data_out,
+            ul_files, dl_files, mms_rstring(dstatus));
+
+    return (0);
+}
+
+
+
+
+/* This consists of the transfer server thread. One is spawned for each ftp
+ * client we are serving, and the main client server thread uses it as a slave
+ * daemon, managing PORT/PASV transfers. This permits the client to continue
+ * serving basic FTP commands, including ABOR, asynchronously, except of course
+ * other transfer requests when one is ongoing already.
+ */
+static void
+transferthread(void *args)
+{
+    clientenv *clenv = args;
+    fdbuf *fdb;
+    char buffer[16];
+    bool ok = FALSE;
+    transfermsg *msg;
+    unsigned long num;
+    fdfuncs fdf = {
+       malloc,
+       free,
+       pth_poll,
+       pth_read,
+       pth_write,
+       pth_sleep,
+       pth_usleep
+    };
+
+    /* First open our server message port. libpth is annoying a bit for this
+     * as it does not permit NULL for the port name to create private message
+     * ports, unlike AmigaOS, even though it was inspired from it. So we
+     * simply use a variable which we increase to generate the port name,
+     * although this is of course not particularly efficient.
+     */
+    pth_mutex_acquire(&portnum_lock, FALSE, NULL);
+    num = portnum++;
+    pth_mutex_release(&portnum_lock);
+    snprintf(buffer, 15, "%lu", num);
+    /* The fd, as well as input/output bandwidth parameters are unusable at
+       this time. We set them internally when we receive transfer requests.
+     */
+    if ((fdb = fdbopen(&fdf, &fdbc, -1, FDB_BUFSIZE, FDB_BUFSIZE, 1024, 1024,
+                   CONF.DATA_TIMEOUT * 1000, CONF.DATA_TIMEOUT * 1000))) {
+       if ((clenv->sport = pth_msgport_create(buffer))) {
+           /* Reply that we started thread properly */
+           ok = TRUE;
+           clenv->tmsg.result = TRUE;
+           pth_msgport_put(clenv->rport, (pth_message_t *)&(clenv->tmsg));
+
+           /* Process our tasks */
+           transferthread_main(clenv, fdb);
+
+           while ((msg = (transfermsg *)pth_msgport_get(clenv->sport))) {
+               msg->result = FALSE;
+               pth_msgport_reply((pth_message_t *)msg);
+           }
+           pth_msgport_destroy(clenv->sport);
+       } else
+           mmsyslog(0, LOGLEVEL,
+                   "* transferthread() - pth_msgport_create()");
+       fdbclose(fdb);
+    } else
+       mmsyslog(0, LOGLEVEL, "* transferthread() - fdbopen()");
+
+    if (!ok) {
+       /* Reply that thread couldn't be setup properly */
+       clenv->tmsg.result = FALSE;
+       pth_msgport_put(clenv->rport, (pth_message_t *)&(clenv->tmsg));
+    }
+
+    pth_exit(NULL);
+}
+
+
+/* This device provides a few states and commands:
+ * 
+ * STATES
+ * ------
+ * 1) Waiting for requests, no transfer ongoing, no connection establishing,
+ *    listening to REQ_QUIT, REQ_ABORT, REQ_STATUS, REQ_NEWPORT, REQ_TRANSFER.
+ *    Of course this also has to handle the PASV listening port
+ * 2) Establishing a connection and listening to REQ_QUIT, REQ_ABORT and
+ *    REQ_STATUS (we will still report ongoing=TRUE at this point)
+ * 3) Ongoing transfer, and listening to REQ_QUIT, REQ_ABORT and REQ_STATUS
+ * 
+ * REQUESTS
+ * --------
+ * REQ_QUIT) May be used anytime, causes the transfer thread to abort ongoing
+ *           transfer, if any, and to cleanly exit
+ * REQ_ABORT) Anytime, forces ongoing transfer to end, if any, and abort any
+ *            pending REQ_TRANSFER (PORT/PASV)
+ * REQ_STATUS) Anytime, permits to know if any ongoing transfers in progress,
+ *             and if a PORT/PASV connection is pending
+ * REQ_NEWPORT) Only allowed in state 1, tells the transfer thread that
+ *              next transfer will be in PORT or PASV mode, etc
+ * REQ_TRANSFER) Tells the thread, only after REQ_NEWPORT was used, to attempt
+ *               a transfer. Direction has to be specified, as well as the file
+ *               descriptor of the file to be read from or written to. If
+ *               list>0 however, this means that the client will be sent a
+ *               file listing instead, list=1 for LIST, list=2 for NLST.
+ * 
+ * This consists of a pretty large messy function, but it works (-:
+ * It is to be noted that it itself writes a few replies on the control port
+ * when appropriate, to simplify the ASYNC design between the control and data
+ * threads/ports. This way the control thread does not need to be notified
+ * when a data transfer ends. Moreover, the control thread does all necessary
+ * sanity checking before these functions are called (eg: port range limit,
+ * files). The exception is that when a passive data connection is established
+ * a necessary check is performed to ensure that only the expected client
+ * connected (check only performed by IP address as the FTP protocol does not
+ * provide any better means).
+ */
+static void
+transferthread_main(clientenv *clenv, fdbuf *fdb)
+{
+    int state = 1, l, reason;  /* Default starting state is 1 */
+    register char *from, *to;
+    char ipaddr[20], buffer[T_BUFSIZE], *from2;
+    pth_event_t ring, ring1;
+    transfermsg *msg;
+    struct sockaddr_in server, client, addr;
+    struct in_addr iaddr;
+    socklen_t len, addrl;
+    bool cont;
+
+    /* We need our own copy of these */
+    int port = 0, fdpasv = -1, fd = -1, file = -1;
+    char path[MMPATH_MAX];
+    bool passive = FALSE, ongoing = FALSE, download = TRUE, list = 0;
+    int64_t dlfiles = 0, ulfiles = 0;
+    *path = 0;
+    fdbparam_set(fdb, -1, FDBP_FD);
+
+    /* Transfer device entry setup */
+    ring = pth_event(PTH_EVENT_MSG, clenv->sport);
+
+    /* Main device loop */
+    while (state) {
+
+       if (state == 1) {       /* INITIAL STATE */
+
+           /* State entry setup */
+           /* Make sure everything is reset */
+           port = 0;
+           passive = ongoing = FALSE;
+           download = TRUE;
+           list = 0;
+           *path = 0;
+           if (fd != -1) {
+               fdbflushw(fdb);
+               shutdown(fd, SHUT_RDWR);
+               close(fd);
+               fd = -1;
+               fdbparam_set(fdb, -1, FDBP_FD);
+           }
+           if (file != -1) {
+               close(file);
+               file = -1;
+           }
+           if (fdpasv != -1) {
+               close(fdpasv);
+               fdpasv = -1;
+           }
+
+           /* State main loop */
+           while (state == 1) {
+               if ((pth_wait(ring))) {
+                   if ((pth_event_occurred(ring))) {
+                       while ((msg = (transfermsg *)pth_msgport_get(
+                                       clenv->sport))) {
+                           /* Process request and reply */
+                           switch (msg->request) {
+                           case REQ_QUIT:
+                               msg->result = TRUE;
+                               state = 0;
+                               break;
+                           case REQ_ABORT:
+                               /* As we handle PASV port, do this */
+                               if (fdpasv != -1) {
+                                   close(fdpasv);
+                                   fdpasv = -1;
+                               }
+                               port = 0;
+                               passive = ongoing = FALSE;
+                               download = TRUE;
+                               list = 0;
+                               *path = 0;
+                               msg->result = TRUE;
+                               break;
+                           case REQ_STATUS:
+                               msg->port = port;
+                               msg->passive = passive;
+                               msg->ongoing = ongoing;
+                               msg->download = download;
+                               msg->list = list;
+                               msg->rbytes = fdbbytesr(fdb);
+                               msg->wbytes = fdbbytesw(fdb);
+                               msg->dlfiles = dlfiles;
+                               msg->ulfiles = ulfiles;
+                               msg->result = TRUE;
+                               break;
+                           case REQ_NEWPORT:
+                               port = 0;
+                               if (fd != -1) {
+                                   fdbflushw(fdb);
+                                   shutdown(fd, SHUT_RDWR);
+                                   close(fd);
+                                   fd = -1;
+                                   fdbparam_set(fdb, -1, FDBP_FD);
+                               }
+                               /* As we handle PASV port, do this */
+                               if (fdpasv != -1) {
+                                   close(fdpasv);
+                                   fdpasv = -1;
+                               }
+                               if (msg->passive) {
+                                   /* PASV request, we need to open a high
+                                    * port and start listening for a
+                                    * connection on it
+                                    */
+                                   msg->result = FALSE;        /* Default */
+                                   if ((fdpasv = socket(AF_INET, SOCK_STREAM,
+                                                   0)) != -1) {
+                                       mm_memclr(&server, sizeof(server));
+                                       server.sin_family = AF_INET;
+                                       server.sin_addr.s_addr =
+                                           clenv->uipaddr;
+                                       server.sin_port = 0;
+                                       if (bind(fdpasv,
+                                                   (struct sockaddr *)&server,
+                                                   sizeof(server)) != -1) {
+                                           /* Get port number we were able to
+                                            * bind
+                                            */
+                                           len = sizeof(struct sockaddr_in);
+                                           if (getsockname(fdpasv,
+                                               (struct sockaddr *)&server,
+                                               (socklen_t *)&len) != -1) {
+                                               if (!(listen(fdpasv, 0))) {
+                                                   port = ntohs(
+                                                           server.sin_port);
+                                                   passive = TRUE;
+                                                   msg->port = port;
+                                                   msg->result = TRUE;
+                                                   break;
+                                               } else
+                                                   mmsyslog(0, LOGLEVEL,
+                                                           "* \
+transferthread_main(1) - listen()");
+                                           } else
+                                               mmsyslog(0, LOGLEVEL, "* \
+transferthread_main(1) - getsockname()");
+                                       } else
+                                           mmsyslog(0, LOGLEVEL,
+                                                   "* transferthread_main(\
+1) - bind()");
+                                   } else
+                                       mmsyslog(0, LOGLEVEL,
+                                               "* transferthread_main(1) - \
+socket()");
+                               } else {
+                                   /* Normal PORT, just remember necessary
+                                    * things. The caller should have
+                                    * performed address and port sanity
+                                    * checking already.
+                                    */
+                                   port = msg->port;
+                                   passive = FALSE;
+                                   msg->result = TRUE;
+                               }
+                               break;
+                           case REQ_TRANSFER:
+                               msg->result = FALSE;    /* Default */
+                               if (port) {
+                                   fdbparam_set(fdb, msg->rrate * 1024,
+                                           FDBP_RRATE);
+                                   fdbparam_set(fdb, msg->wrate * 1024,
+                                           FDBP_WRATE);
+                                   if (msg->path) mm_strncpy(path, msg->path,
+                                           MMPATH_MAX - 1);
+                                   else *path = 0;
+                                   if (msg->list) {
+                                       list = msg->list;
+                                       msg->result = TRUE;
+                                   } else {
+                                       if (msg->file != -1) {
+                                           file = msg->file;
+                                           download = msg->download;
+                                           msg->result = TRUE;
+                                       }
+                                   }
+                                   if (msg->result) state = 2;
+                               } else msg->result = FALSE;
+                               break;
+                           default:
+                               msg->result = FALSE;
+                           }
+                           pth_msgport_reply((pth_message_t *)msg);
+                       }
+                   }
+               }
+           }
+
+           /* State exit cleanup */
+
+       } else if (state == 2) {        /* CONNECTION STATE */
+
+           /* State entry setup */
+           ongoing = TRUE;
+           /* Setup our connect timeout event */
+           ring1 = pth_event(PTH_EVENT_TIME, pth_timeout(CONF.DATA_TIMEOUT,
+                       0));
+           pth_event_concat(ring, ring1, NULL);
+
+           /* State main loop */
+           while (state == 2) {
+               if (passive) {
+                   /* Accept connection from client */
+                   addrl = sizeof(struct sockaddr);
+                   fd = pth_accept_ev(fdpasv, (struct sockaddr *)&addr,
+                           &addrl, ring);
+               } else {
+                   /* Start connection to the client */
+                   if ((fd = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
+                       mm_memclr(&client, sizeof(struct sockaddr_in));
+                       if ((inet_aton(clenv->c_ipaddr, &iaddr))) {
+                           client.sin_family = AF_INET;
+                           client.sin_addr.s_addr = iaddr.s_addr;
+                           client.sin_port = htons(port);
+                           /* Finally attempt connection */
+                           if ((pth_connect_ev(fd,
+                                           (struct sockaddr *)&client,
+                                           sizeof(struct sockaddr_in),
+                                           ring)) == -1) {
+                               mmsyslog(1, LOGLEVEL,
+                                       "%08X PORT data connection failiure",
+                                       clenv->id);
+                               reply(clenv->fdb, 425, FALSE,
+                                       "Can't establish data connection");
+                               fdbflushw(clenv->fdb);
+                               clenv->errors++;
+                               state = 1;
+                               break;
+                           }
+                       } else {
+                           mmsyslog(0, LOGLEVEL,
+                                   "* transferthread_main(2) - inet_aton()");
+                           state = 1;
+                           break;
+                       }
+                   } else {
+                       mmsyslog(0, LOGLEVEL,
+                               "* transferthread_main(2) - socket()");
+                       reply(clenv->fdb, 425, FALSE,
+                               "Can't establish data connection");
+                       fdbflushw(clenv->fdb);
+                       clenv->errors++;
+                       state = 1;
+                       break;
+                   }
+               }
+               /* Verify if connection timeout occured */
+               if ((pth_event_occurred(ring1))) {
+                   mmsyslog(1, LOGLEVEL, "%08X Data connection timeout",
+                           clenv->id);
+                   reply(clenv->fdb, 425, FALSE,
+                           "Can't establish data connection");
+                   fdbflushw(clenv->fdb);
+                   clenv->errors++;
+                   state = 1;
+                   break;
+               }
+               /* Check for any request events and process them if any */
+               if ((pth_event_occurred(ring))) {
+                   while ((msg = (transfermsg *)pth_msgport_get(
+                                   clenv->sport))) {
+                       /* Process request and reply */
+                       switch (msg->request) {
+                       case REQ_QUIT:
+                           msg->result = TRUE;
+                           state = 0;
+                           break;
+                       case REQ_ABORT:
+                           msg->result = TRUE;
+                           state = 1;
+                           break;
+                       case REQ_STATUS:
+                           msg->port = port;
+                           msg->passive = passive;
+                           msg->ongoing = ongoing;
+                           msg->download = download;
+                           msg->list = list;
+                           msg->rbytes = fdbbytesr(fdb);
+                           msg->wbytes = fdbbytesw(fdb);
+                           msg->dlfiles = dlfiles;
+                           msg->ulfiles = ulfiles;
+                           msg->result = TRUE;
+                           break;
+                       default:
+                           msg->result = FALSE;
+                       }
+                       pth_msgport_reply((pth_message_t *)msg);
+                   }
+               }
+               if (state == 2) {
+                   /* Verify if connection was established */
+                   if (fd != -1) {
+                       fdbparam_set(fdb, fd, FDBP_FD);
+                       if (passive) {
+                           /* Make sure that it's really the client we expect
+                            */
+                           *ipaddr = 0;
+                           inet_ntop(AF_INET, &(addr.sin_addr), ipaddr, 19);
+                           if (!(mm_strcmp(clenv->c_ipaddr, ipaddr)))
+                               state = 3;
+                           else {
+                               reply(clenv->fdb, 425, FALSE,
+                                       "Can't establish data connection");
+                               fdbflushw(clenv->fdb);
+                               clenv->errors++;
+                               state = 1;
+                           }
+                       } else state = 3;
+                       break;
+                   }
+               }
+           }
+
+           /* State exit cleanup */
+           pth_event_isolate(ring);
+           pth_event_free(ring1, PTH_FREE_ALL);
+
+       } else if (state == 3) {        /* TRANSFER STATE */
+
+           /* State entry setup */
+           ongoing = TRUE;
+           reason = TR_OK;
+
+           /* State main loop */
+           while (state == 3) {
+
+               /* We transfer the data while still answering a few requests */
+               if (list) {
+                   shutdown(fd, SHUT_RD);
+                   reply(clenv->fdb, 150, FALSE,
+                         "Opening ASCII mode data connection for '/bin/ls'");
+                   fdbflushw(clenv->fdb);
+                   /* List request, simply send the data to the connected
+                    * client
+                    */
+                   if (list == 1) {    /* LIST */
+                       if (!ls(fdb, path, clenv->user, clenv->group, FALSE,
+                                   TRUE, clenv->checkowner))
+                           mmsyslog(0, LOGLEVEL,
+                                   "* transferthread_main(3) - ls(%s)",
+                                   path);
+                   } else if (list == 2) {     /* NLST */
+                       if (!ls(fdb, path, clenv->user, clenv->group, TRUE,
+                                   TRUE, clenv->checkowner))
+                           mmsyslog(0, LOGLEVEL,
+                                   "* transferthread_main(3) - ls(%s)",
+                                   path);
+                   }
+                   state = 1;
+                   break;
+               }
+
+               /* Normal file transfer with an inactivity timeout,
+                * to transfer bytes between file and fd, in specified
+                * direction
+                */
+               if (clenv->type == TYPE_ASCII)
+                   reply(clenv->fdb, 150, FALSE,
+                           "Opening ASCII mode data connection for '%s'",
+                           path);
+               else
+                   reply(clenv->fdb, 150, FALSE,
+                           "Opening BINARY mode data connection for '%s'",
+                           path);
+               fdbflushw(clenv->fdb);
+
+               if (download) { /* SERVER TO CLIENT TRANSFER */
+
+                   dlfiles++;
+                   shutdown(fd, SHUT_RD);      /* Prevent read from socket */
+                   if (clenv->rest) {
+                       lseek(file, clenv->rest, SEEK_SET);
+                       clenv->rest = 0;
+                   }
+
+                   cont = TRUE;
+                   if (clenv->type == TYPE_IMAGE) fdbflushw(fdb);
+                   while (cont) {
+                       if ((clenv->type ==
+                                   TYPE_IMAGE ? fdbrcleartosend(fdb) :
+                                   fdbcleartosend(fdb))) {
+                           /* Client ready to be written some bytes to */
+                           if ((l = pth_read(file, buffer, T_BUFSIZE)) > 0) {
+                               if (clenv->type == TYPE_ASCII) {
+                                   /* ASCII xfer, perform \n to \r\n
+                                    * conversion
+                                    */
+                                   from = from2 = to = buffer;
+                                   to += l;
+                                   while (from < to && from2 < to) {
+                                       if (*from == '\n') {
+                                           if ((l = fdbwrite(fdb, from2,
+                                                           from -
+                                                           from2)) < 0)
+                                               break;
+                                           if ((l = fdbwrite(fdb, "\r\n", 2))
+                                                   < 0)
+                                               break;
+                                           from2 = from + 1;
+                                       }
+                                       from++;
+                                   }
+                                   if (from2 < to)
+                                       l = fdbwrite(fdb, from2, to - from2);
+                                   if (l < 1) {
+                                       state = 1;
+                                       cont = FALSE;
+                                       if ((fdberrno(fdb)) == FDB_TIMEOUT)
+                                           reason = TR_TIMEOUT;
+                                       else if ((fdberrno(fdb)) == FDB_ERROR)
+                                           reason = TR_LOST;
+                                       break;
+                                   }
+                               } else {
+                                   /* IMAGE/binary transfer, write as-is */
+                                   if ((fdb->fdberrno = fdbrwrite(fdb,
+                                                   buffer, l)) != l) {
+                                       state = 1;
+                                       cont = FALSE;
+                                       if ((fdberrno(fdb)) == FDB_TIMEOUT)
+                                           reason = TR_TIMEOUT;
+                                       else if ((fdberrno(fdb)) == FDB_ERROR)
+                                           reason = TR_LOST;
+                                       break;
+                                   }
+                               }
+                           } else {
+                               /* EOF reading local file */
+                               state = 1;
+                               cont = FALSE;
+                               break;
+                           }
+                       } else {
+                           state = 1;
+                           cont = FALSE;
+                           if ((fdberrno(fdb)) == FDB_TIMEOUT)
+                               reason = TR_TIMEOUT;
+                           else if ((fdberrno(fdb)) == FDB_ERROR)
+                               reason = TR_LOST;
+                           break;
+                       }
+                       /* Verify if we received any important events */
+                       if ((pth_event_occurred(ring))) {
+                           while ((msg = (transfermsg *)pth_msgport_get(
+                                           clenv->sport))) {
+                               /* Process request and reply */
+                               switch (msg->request) {
+                               case REQ_QUIT:
+                                   msg->result = TRUE;
+                                   state = 0;
+                                   cont = FALSE;
+                                   break;
+                               case REQ_ABORT:
+                                   msg->result = TRUE;
+                                   state = 1;
+                                   cont = FALSE;
+                                   reason = TR_ABORTED;
+                                   break;
+                               case REQ_STATUS:
+                                   msg->port = port;
+                                   msg->passive = passive;
+                                   msg->ongoing = ongoing;
+                                   msg->download = download;
+                                   msg->list = list;
+                                   msg->rbytes = fdbbytesr(fdb);
+                                   msg->wbytes = fdbbytesw(fdb);
+                                   msg->dlfiles = dlfiles;
+                                   msg->ulfiles = ulfiles;
+                                   msg->result = TRUE;
+                                   break;
+                               default:
+                                   msg->result = FALSE;
+                               }
+                               pth_msgport_reply((pth_message_t *)msg);
+                           }
+                       }
+                   }
+
+               } else {        /* CLIENT TO SERVER TRANSFER */
+
+                   ulfiles++;
+                   cont = TRUE;
+                   fdbflushr(fdb);
+                   while (cont) {
+                       if (fdbrdataready(fdb)) {
+                           /* Data ready to read from client */
+                           if ((l = fdbrread(fdb, buffer, T_BUFSIZE)) > 0) {
+
+                               if (clenv->type == TYPE_ASCII) {
+
+                                   /* ASCII transfer, perform \r\n to \n
+                                    * conversion, stripping all \r, and check
+                           &nbs